国庆节10月1日
--


把前面的知识拼成一个能用的助手:五个工具(天气/计算/时间/货币/搜索)、用类封装 Agent、维护对话历史,以及自动演示+交互式主程序

前几篇把 Agent 的零件都拆开讲完了:创建、传模型、绑工具、写提示词、结构化输出、流式输出。这一篇把它们拼成一个能用的东西——多功能智能助手。
开发一个多功能智能助手,支持:
代码分成四步,正好对应我们前面学过的内容:
| 步骤 | 对应知识点 |
|---|---|
| 1. 模型的初始化 | 模型的创建 |
| 2. 工具的定义 | Tools工具定义与调用 |
| 3. Agent 的创建(面向对象封装) | 创建第一个智能体 |
| 4. 主程序(演示 + 交互) | 这一篇的重点 |
1import os2from dotenv import load_dotenv3from langchain.chat_models import init_chat_model4
5load_dotenv(override=True)6
7model = init_chat_model(8 model="deepseek-v4-flash",9 model_provider="openai",10 api_key=os.getenv("DEEPSEEK_API_KEY"),11 base_url=os.getenv("DEEPSEEK_BASE_URL"),12)每个工具都只做一件事,并用规范的 docstring 写清 Args / Returns / Examples——这些文字就是模型选择工具、填参数的依据。
| 工具 | 参数 | 返回什么 |
|---|---|---|
get_weather(city) | 城市名称 | 温度、天气状况、空气质量 |
calculator(expression) | 数学表达式 | 计算结果 |
get_time_info(query_type) | current/date/tomorrow/yesterday/weekday | 时间信息 |
convert_currency(amount, from_curr, to_curr) | 金额 + 源/目标货币 | 转换结果 |
search_info(keyword, category) | 关键词 + 分类 | 产品/新闻搜索结果 |
几个值得学的写法:
1from langchain_core.tools import tool2import math3from datetime import datetime, timedelta4
5@tool6def get_weather(city: str) -> str:7 """获取指定城市的实时天气信息8
9 支持中国主要城市的天气查询10
11 Args:12 city: 城市名称,如"北京"、"上海"、"深圳"等13
14 Returns:15 包含温度、天气状况、空气质量的详细信息16
17 Examples:18 get_weather("北京") 返回 "多云,15-22℃,空气质量良"19 """20 weather_db = {21 "北京": "多云,15-22℃,空气质量良,湿度 45%",22 "上海": "晴天,18-25℃,空气质量优,湿度 60%",23 "深圳": "小雨,22-28℃,空气质量优,湿度 75%",24 }25 result = weather_db.get(city)26 if result:27 return f"{city}:{result}"28 return f"抱歉,暂不支持查询{city}的天气信息。当前支持:北京、上海、深圳"① 查不到时要给出”可用的范围”——别只返回”没有数据”,模型没法据此修正。
1@tool2def calculator(expression: str) -> str:3 """执行数学计算4
5 支持基本运算符(+、-、*、/、**)和常用数学函数6
7 Args:8 expression: 数学表达式,可以包含:9 - 基本运算:2 + 3, 10 * 5, 100 / 410 - 幂运算:2 ** 1011 - 函数:sqrt(16), abs(-5), pow(2, 3)12
13 Returns:14 计算结果或错误信息15
16 Examples:17 calculator("2 + 3 * 4") 返回 "14"18 calculator("sqrt(16)") 返回 "4.0"19 """20 try:21 safe_functions = {22 "sqrt": math.sqrt, "pow": pow, "abs": abs, "round": round,23 "sin": math.sin, "cos": math.cos, "tan": math.tan, "log": math.log,24 "pi": math.pi, "e": math.e,25 }26 result = eval(expression, {"__builtins__": {}}, safe_functions)27 return f"{expression} = {result}"28 except Exception as e:29 return f"计算出错:{str(e)}\n提示:请检查表达式格式,支持的函数有 sqrt, abs, pow, sin, cos, tan, log"② eval 要”锁死”环境:第二个参数传 {"__builtins__": {}}(禁用内置函数),第三个参数只放白名单数学函数——这是让模型能算数、又不至于执行任意代码的常见做法。
1@tool2def get_time_info(query_type: str = "current") -> str:3 """获取时间相关信息4
5 Args:6 query_type: 查询类型7 - "current": 当前时间8 - "date": 今天日期9 - "tomorrow": 明天日期10 - "yesterday": 昨天日期11 - "weekday": 星期几12
13 Returns:14 时间信息字符串15
16 Examples:17 get_time_info("current") 返回 "2025年1月25日 14:30:25"18 get_time_info("weekday") 返回 "星期六"19 """20 now = datetime.now()21 if query_type == "current":22 return now.strftime("当前时间:%Y年%m月%d日 %H:%M:%S")23 elif query_type == "date":24 return now.strftime("今天是:%Y年%m月%d日")25 elif query_type == "tomorrow":26 return (now + timedelta(days=1)).strftime("明天是:%Y年%m月%d日")27 elif query_type == "yesterday":28 return (now - timedelta(days=1)).strftime("昨天是:%Y年%m月%d日")29 elif query_type == "weekday":30 weekdays = ["星期一", "星期二", "星期三", "星期四", "星期五", "星期六", "星期日"]31 return f"今天是{weekdays[now.weekday()]}"32 return f"不支持的查询类型:{query_type}。支持:current, date, tomorrow, yesterday, weekday"③ 用”枚举式参数”代替多个工具:一个 get_time_info 顶五个时间工具——工具越少,模型越不容易选错(第 14 篇的常见问题)。
1@tool2def convert_currency(amount: float, from_curr: str, to_curr: str) -> str:3 """货币转换工具4 支持主要货币之间的实时汇率转换5
6 Args:7 amount: 金额数值8 from_curr: 源货币代码(CNY/USD/EUR/GBP/JPY/HKD)9 to_curr: 目标货币代码(CNY/USD/EUR/GBP/JPY/HKD)10
11 Returns:12 转换结果13
14 Examples:15 convert_currency(100, "CNY", "USD") 返回 "100 CNY = 14.00 USD"16 """17 exchange_rates = {"CNY": 1.0, "USD": 0.14, "EUR": 0.13, "GBP": 0.11, "JPY": 20.8, "HKD": 1.09}18 currency_names = {"CNY": "人民币", "USD": "美元", "EUR": "欧元",19 "GBP": "英镑", "JPY": "日元", "HKD": "港币"}20
21 from_curr, to_curr = from_curr.upper(), to_curr.upper()22 if from_curr not in exchange_rates:23 return f"不支持的源货币:{from_curr}。支持的货币:CNY, USD, EUR, GBP, JPY, HKD"24 if to_curr not in exchange_rates:25 return f"不支持的目标货币:{to_curr}。支持的货币:CNY, USD, EUR, GBP, JPY, HKD"26
27 cny_amount = amount / exchange_rates[from_curr] # 先换成 CNY 再换目标货币28 result_amount = cny_amount * exchange_rates[to_curr]29 return f"{amount} {currency_names[from_curr]}({from_curr})= {result_amount:.2f} {currency_names[to_curr]}({to_curr})"1@tool2def search_info(keyword: str, category: str = "all") -> str:3 """搜索各类信息4
5 Args:6 keyword: 搜索关键词7 category: 搜索分类8 - "product": 搜索产品9 - "news": 搜索新闻10 - "all": 搜索所有11
12 Returns:13 搜索结果14 """15 products = {16 "手机": "iPhone 15 (¥5999), 小米14 (¥3999), 华为Mate60 (¥6999)",17 "笔记本": "MacBook Pro (¥12999), ThinkPad X1 (¥9999), 华为MateBook (¥7999)",18 "耳机": "AirPods Pro (¥1999), Sony WH-1000XM5 (¥2499)",19 }20 news = {21 "AI": "1. GPT-5 即将发布 2. AI 芯片市场增长 30% 3. 新AI法规出台",22 "科技": "1. 量子计算新突破 2. 6G 技术测试 3. 新能源汽车销量创新高",23 }24
25 results = []26 if category in ["product", "all"]:27 for key, value in products.items():28 if keyword in key:29 results.append(f"【产品】{key}:{value}")30 if category in ["news", "all"]:31 for key, value in news.items():32 if keyword in key or keyword in value:33 results.append(f"【新闻】{key} 相关:{value}")34
35 return "\n".join(results) if results else f"未找到关于 '{keyword}' 的{category}信息"注意 search_info 的 category: str = "all" 和 get_time_info 的 query_type: str = "current"——参数带默认值时,模型可以不传(第 11 篇的”默认值”特性在工具里同样适用)。
这一步是实战精华:把”模型 + 工具 + 提示词 + 对话历史”打包成一个类,对外只暴露 chat() 和 reset()。
1from langchain.agents import create_agent2
3class SmartAssistant:4 """多功能智能助手"""5
6 def __init__(self):7 self.model = model8
9 self.tools = [get_weather, calculator, get_time_info, convert_currency, search_info]10
11 system_prompt = """你是一个多功能智能助手,可以帮助用户:12
13 🌤️ 查询天气:使用 get_weather 工具14 🔢 数学计算:使用 calculator 工具15 ⏰ 时间查询:使用 get_time_info 工具16 💱 货币转换:使用 convert_currency 工具17 🔍 信息搜索:使用 search_info 工具18
19 重要提示:20 1. 仔细阅读用户问题,确定需要使用哪个工具21 2. 如果需要多个工具,按顺序调用22 3. 总是用友好、专业的语气回答23 4. 如果工具返回了数据,要用通俗易懂的语言解释给用户24 5. 如果无法完成任务,诚实地告诉用户原因25
26 请始终使用中文回答。"""27
28 self.agent = create_agent(29 model=self.model,30 tools=self.tools,31 system_prompt=system_prompt,32 )33
34 self.messages = [] # 对话历史35
36 def chat(self, user_input: str) -> str:37 """对话接口"""38 self.messages.append({"role": "user", "content": user_input}) # 1. 追加用户消息39
40 result = self.agent.invoke({"messages": self.messages}) # 2. 调用 agent(带上历史)41
42 self.messages = result["messages"] # 3. 用返回的完整消息列表更新历史43
44 for msg in reversed(self.messages): # 4. 从后往前找最后一条有内容的 AI 消息45 if msg.type == "ai" and msg.content:46 return msg.content47
48 return "抱歉,我无法处理这个请求。"49
50 def reset(self):51 """重置对话历史"""52 self.messages = []这段代码里有四个必须理解的细节:
| 细节 | 为什么这么写 |
|---|---|
self.messages 一直保留 | Agent 本身不带记忆,想让它”记得上文”就得每次把历史一起传进去 |
用 result["messages"] 覆盖历史 | 返回的是含工具调用全过程的完整列表,覆盖后才和模型看到的上下文一致 |
reversed(...) 从后往前找 | 最后一条消息不一定是”有内容的 AI 消息”(可能是工具调用的空 content) |
msg.content 判空 | AIMessage 在”发起工具调用”时 content 是空的,不能直接当回答返回 |
再加上第 17 篇的流式输出,就能把这个助手升级成”打字机式”回复——chat() 里把 invoke 换成 stream(..., stream_mode="messages") 再逐片打印即可。
1def main():2 assistant = SmartAssistant()3
4 print("=" * 40)5 print("🤖 多功能智能助手(LangChain 1.2)")6 print("=" * 40)7 print("\n输入 'quit' 退出,输入 'reset' 重置对话\n")8
9 # 先自动跑几条演示,确认每个工具都能被正确调用10 demos = [11 "北京今天天气怎么样?",12 "帮我算一下 (25 + 17) * 3",13 "现在几点了?",14 "100 美元等于多少人民币?",15 ]16
17 for demo in demos:18 print(f"👤 {demo}")19 response = assistant.chat(demo)20 print(f"🤖 {response}\n")21
22 assistant.reset()23
24 # 交互模式25 while True:26 user_input = input("\n👤 你: ")27 if user_input.lower() == "quit":28 print("再见!👋")29 break30 if user_input.lower() == "reset":31 assistant.reset()32 print("✅ 对话已重置")33 continue34 if not user_input.strip():35 continue36 print(f"🤖 助手: {assistant.chat(user_input)}")37
38if __name__ == "__main__":39 main()设计上值得抄的两点:
demos 自动演示:每次运行先把每个工具跑一遍——既是演示,也是”冒烟测试”,一眼看出哪个工具挂了quit / reset 指令:一个退出、一个清空对话历史;reset 在调试”多轮对话跑偏”时特别有用课程运行后打出来的完整输出(2026-06-03 那次运行,四条 demo 都命中了工具):
1========================================2🤖 多功能智能助手(LangChain 1.2)3========================================4
5我可以帮你:6🌤️ 查询天气7🔢 数学计算8⏰ 时间查询9💱 货币转换10🔍 信息搜索11
12输入 'quit' 退出,输入 'reset' 重置对话13
14👤 北京今天天气怎么样?15🤖 北京今天天气是:**多云,15–22℃**,**空气质量良**,湿度 **45%**。16整体来说比较适合外出,建议穿**轻薄外套**会更舒服。17
18👤 帮我算一下 (25 + 17) * 319🤖 计算结果是:**126**。20
21👤 现在几点了?22🤖 现在是:**2026年06月03日 19:45:40**。23
24👤 100 美元等于多少人民币?25🤖 **100 美元(USD)约等于 714.29 人民币(CNY)**。26
27========================================28🤖 进入交互模式29========================================30再见!👋输出里”进入交互模式”那一行的 emoji 在课程 PDF 里没能还原出来(🤖 / 💬 都可能),不影响任何逻辑——照上面主程序的写法跑一遍,自己看到的就是真实的。
对照代码能看到几件事:
| 输出片段 | 说明 |
|---|---|
🤖 北京今天天气是:**多云,15–22℃**…建议穿**轻薄外套** | 工具返回的是原始数据(多云,15-22℃,空气质量良,湿度 45%),最后这句”建议穿轻薄外套”是模型自己加的——这就是系统提示词里”用通俗易懂的语言解释给用户”的效果 |
计算结果是:**126** | (25+17)*3 = 126,calculator 工具算对了(工具返回是 (25 + 17) * 3 = 126,模型复述时只留了数字) |
100 美元(USD)约等于 714.29 人民币(CNY) | 用 convert_currency 的汇率表手算能对上:100 / 0.14 × 1.0 = 714.29 |
末尾的 再见!👋 | 进入交互模式后直接输入 quit 的退出语 |
上面贴的是精简版主程序的运行效果。课程原版 main() 在循环前后各多打印了一块内容:
1print("\n我可以帮你:")2print(" 🌤️ 查询天气")3print(" 🔢 数学计算")4print(" ⏰ 时间查询")5print(" 💱 货币转换")6print(" 🔍 信息搜索")7...8print("=" * 40)9print("🤖 进入交互模式")10print("=" * 40)也就是输出里”我可以帮你:“那一段和”进入交互模式”前后的两条分隔线——想让界面更像”产品”,照抄这两块即可(不影响任何逻辑)。
Args / Returns / Examples 规范写全——这些文字就是模型选择(该用哪个)工具、填参数的依据calculator 用 eval 时要”锁死”环境:第二个参数传 {"__builtins__": {}} 禁用内置函数,第三个参数只放白名单里的数学函数query_type)代替多个工具的好处:工具越少,模型越不容易选错100 / 0.14)search_info 的 category 和 get_time_info 的 query_type 都用了这个特性SmartAssistant 类对外只暴露 chat() 和 reset() 两个方法;self.messages 用来保存对话历史,因为 Agent 本身不带记忆,想让助手”记得上文”就得每次把历史一起传进去chat() 里用 result["messages"] 覆盖历史(返回的是含工具调用全过程的完整列表);取回答时用 reversed(...) 从后往前找 msg.type == "ai" and msg.content——因为发起工具调用时 AI 消息的 content 是**空('')**的demos 列表既做演示、也相当于一次”冒烟测试”;quit 指令退出、reset 指令清空对话历史;课程输出里”建议穿轻薄外套”这类话是模型自己加的(系统提示词里”用通俗易懂的语言解释给用户”的效果) 2-1 给助手加第 6 个工具
新增一个”单位换算”之类的工具(主题自己选),要求:参数带默认值、遇到不认识的值时返回”支持哪些单位”;按 Args / Returns / Examples 规范写 docstring;把它加进助手的工具列表,然后问一个只有新工具能回答的问题,确认它被正确调用(把消息链里的工具调用名打印出来)。
一级 · 思路:工具”能不能被正确调用”取决于 docstring 写得清不清楚;参数默认值可以让模型少填一个参数
二级 · 方法:@tool + 写全 Args / Returns / Examples,再把它加进 self.tools
三级 · 骨架:先把新工具单独 invoke 一次确认逻辑没问题,再交给 Agent;确认被调用可以用 [c["name"] for m in result["messages"] for c in (getattr(m, "tool_calls", None) or [])]
2-2 写两个”安全”的工具
写两个工具:
① 数学计算工具:支持 + - * / ** 和 sqrt / pow / abs / round / sin / cos / tan / log、常量 pi / e;执行表达式时要禁用 Python 内置函数、只放开白名单里的数学函数;出错时返回”计算出错 + 支持哪些函数”。
② 时间信息工具:用一个枚举式参数顶五个功能(当前时间 / 今天日期 / 明天 / 昨天 / 星期几),参数带默认值,传不认识的值时返回支持列表。
两个工具都不经过模型,直接单独调用做验证:算 (25 + 17) * 3、算 sqrt(16) + abs(-5)、故意算一个危险表达式(如 __import__('os').system('echo hi'))、不带参数调用时间工具、传一个不支持的时间类型。
一级 · 思路:eval 是”能算数但危险”的典型,锁环境和白名单要一起做;枚举式参数把五个功能合成一个工具,模型更不容易选错
二级 · 方法:eval(expression, {"__builtins__": {}}, safe_functions);@tool def get_time_info(query_type: str = "current") + datetime.now() / timedelta(days=1)
三级 · 骨架:单独验证就是 工具.invoke({"expression": "..."})——返回字符串、不会真的执行系统命令(__import__ 也被禁掉了);时间格式用 strftime("当前时间:%Y年%m月%d日 %H:%M:%S"),星期几用 weekdays[now.weekday()]
2-3 把助手封装成一个类
把”模型 + 工具 + 系统提示词 + 对话历史”打包成一个类,对外只暴露两个方法:
① 初始化:准备工具列表(天气 / 计算 / 时间 / 货币 / 搜索)、系统提示词(写清”先判断该用哪个工具 → 需要多个就按顺序调用 → 用友好专业的语气、通俗易懂的语言解释结果 → 无法完成时如实说明”)、Agent 和一个空的对话历史;
② chat(user_input):先把用户输入追加进历史 → 带着完整历史调用 Agent → 用返回的完整消息列表覆盖历史 → 从后往前找最后一条有内容的 AI 消息返回(都找不到就返回一句兜底话);
③ reset():清空对话历史。
写完后不调模型也要能自测:实例化这个类、打印两个方法的签名、往历史里塞几条假消息再 reset() 看是否清空。
一级 · 思路:四个细节决定它能不能”记得上文”——历史要一直留着、历史要用返回值覆盖、找回答要从后往前、AI 消息要判空
二级 · 方法:create_agent(model=..., tools=..., system_prompt=...);result["messages"];reversed(self.messages) + msg.type == "ai" and msg.content
三级 · 骨架:初始化里 self.messages = [];自测用 inspect.signature(SmartAssistant.chat) 打印签名,完全不需要跑模型
2-4 手算校验货币转换工具
不经过模型,单独调用货币转换工具,验证三件事:
① 100 美元换人民币,把结果和汇率表手算的结果对比(表里 USD = 0.14);
② 再换一个币种对(如 1000 人民币换日元),同样手算核对;
③ 传一个不支持的货币代码(如 KRW),确认返回值是”可用的范围”提示、而不是抛异常。
一级 · 思路:这类”纯逻辑工具”不必动用模型,直接调用能最快验证算法对不对
二级 · 方法:convert_currency.invoke({"amount": 100, "from_curr": "USD", "to_curr": "CNY"})
三级 · 骨架:算法是”先换成 CNY 再换目标货币”,所以 100 USD → 100 / 0.14 × 1.0;金额格式化用 f"{result:.2f}";代码还做了 .upper(),小写也能认
3-1 把 SmartAssistant 跑起来并读懂它
.env 里配好密钥),运行并确认 4 条演示(天气 / 计算 / 时间 / 货币)都能得到正确回答reset 后再问”那杭州呢”,看它是不是失去了上下文chat() 里加一行打印消息条数,观察每次问答后消息列表涨了几条(想想为什么会涨这么多)一级 · 思路:这道题的重点是”看见历史是怎么起作用的”
二级 · 方法:多轮对话能否连贯,全靠 self.messages 里带着上文;reset() 清空后再问同一句话就会”失忆”
三级 · 骨架:print(f"[消息列表现在有 {len(self.messages)} 条]");一轮里”用户消息 + AI 工具调用 + 工具返回 + 最终回答”都会进去,条数增长很快——这正是第 9 章要解决的”上下文太长”问题
1# ==================== 2-1 第 6 个工具(单位换算)====================2from langchain_core.tools import tool3
4@tool5def convert_unit(value: float, from_unit: str = "米", to_unit: str = "厘米") -> str:6 """单位换算工具7
8 Args:9 value: 要换算的数值10 from_unit: 源单位,支持:米/厘米/千米/千克/克11 to_unit: 目标单位,支持:米/厘米/千米/千克/克12
13 Returns:14 换算结果15
16 Examples:17 convert_unit(2, "米", "厘米") 返回 "2.0 米 = 200.0 厘米"18 """19 factors = {"米": 1.0, "厘米": 0.01, "千米": 1000.0, "千克": 1.0, "克": 0.001}20 if from_unit not in factors or to_unit not in factors:21 return f"不支持的单位:{from_unit} → {to_unit}。支持的:{', '.join(factors)}"22 return f"{value} {from_unit} = {value * factors[from_unit] / factors[to_unit]} {to_unit}"23
24print(convert_unit.invoke({"value": 2, "from_unit": "米", "to_unit": "厘米"})) # 2.0 米 = 200.0 厘米25# 加进 self.tools 后问"2 米等于多少厘米?",消息链里的工具调用名就是 convert_unit26
27# ==================== 2-2 两个"安全"的工具 ====================28import math29from datetime import datetime, timedelta30
31@tool32def calculator(expression: str) -> str:33 """执行数学计算34
35 Args:36 expression: 数学表达式,支持 + - * / ** 与 sqrt/pow/abs/round/sin/cos/tan/log、pi/e37 """38 safe_functions = {39 "sqrt": math.sqrt, "pow": pow, "abs": abs, "round": round,40 "sin": math.sin, "cos": math.cos, "tan": math.tan, "log": math.log,41 "pi": math.pi, "e": math.e,42 }43 try:44 result = eval(expression, {"__builtins__": {}}, safe_functions)45 return f"{expression} = {result}"46 except Exception as e:47 return f"计算出错:{e}\n提示:支持的函数有 sqrt, abs, pow, sin, cos, tan, log"48
49@tool50def get_time_info(query_type: str = "current") -> str:51 """获取时间相关信息52
53 Args:54 query_type: current 当前时间 / date 今天日期 / tomorrow 明天 / yesterday 昨天 / weekday 星期几55 """56 now = datetime.now()57 if query_type == "date":58 return now.strftime("今天是:%Y年%m月%d日")59 elif query_type == "tomorrow":60 return (now + timedelta(days=1)).strftime("明天是:%Y年%m月%d日")61 elif query_type == "yesterday":62 return (now - timedelta(days=1)).strftime("昨天是:%Y年%m月%d日")63 elif query_type == "weekday":64 weekdays = ["星期一", "星期二", "星期三", "星期四", "星期五", "星期六", "星期日"]65 return f"今天是{weekdays[now.weekday()]}"66 elif query_type == "current":67 return now.strftime("当前时间:%Y年%m月%d日 %H:%M:%S")68 return f"不支持的查询类型:{query_type}。支持:current, date, tomorrow, yesterday, weekday"69
70# 单独验证(不走模型)71print(calculator.invoke({"expression": "(25 + 17) * 3"})) # (25 + 17) * 3 = 12672print(calculator.invoke({"expression": "sqrt(16) + abs(-5)"})) # sqrt(16) + abs(-5) = 9.073print(calculator.invoke({"expression": "__import__('os').system('echo hi')"}))74# 计算出错:name '__import__' is not defined … ← 内置函数被禁掉了75print(get_time_info.invoke({})) # 当前时间:…(默认值 current 生效)76print(get_time_info.invoke({"query_type": "不存在的类型"})) # 不支持的查询类型:…77
78# ==================== 2-3 SmartAssistant 类 ====================79import inspect80
81from langchain.agents import create_agent82
83class SmartAssistant:84 """多功能智能助手"""85
86 def __init__(self):87 self.tools = [get_weather, calculator, get_time_info, convert_currency, search_info]88 self.agent = create_agent(89 model=model,90 tools=self.tools,91 system_prompt=(92 "你是一个多功能智能助手。请先判断该用哪个工具,再调用它;"93 "如果需要多个工具,按顺序调用;用友好专业的语气、通俗易懂的语言回答;"94 "无法完成时如实说明原因。请始终使用中文回答。"95 ),96 )97 self.messages = [] # 对话历史98
99 def chat(self, user_input: str) -> str:100 """对话接口"""101 self.messages.append({"role": "user", "content": user_input}) # 1. 追加用户消息102 result = self.agent.invoke({"messages": self.messages}) # 2. 带上历史调用103 self.messages = result["messages"] # 3. 用返回的完整列表覆盖历史104 for msg in reversed(self.messages): # 4. 从后往前找最后一条有内容的 AI 消息105 if msg.type == "ai" and msg.content:106 return msg.content107 return "抱歉,我无法处理这个请求。"108
109 def reset(self):110 """重置对话历史"""111 self.messages = []112
113# 不调模型的自测114assistant = SmartAssistant()115print(inspect.signature(SmartAssistant.chat)) # (self, user_input: str) -> str116print(inspect.signature(SmartAssistant.reset)) # (self)117assistant.messages = [{"role": "user", "content": "假消息"}]118assistant.reset()119print(len(assistant.messages)) # 0 ← reset 生效120
121# ==================== 2-4 手算校验货币转换 ====================122print(convert_currency.invoke({"amount": 100, "from_curr": "USD", "to_curr": "CNY"}))123# 100.0 美元(USD)= 714.29 人民币(CNY) ← 手算 100 / 0.14 * 1.0 = 714.29 ✓124print(convert_currency.invoke({"amount": 1000, "from_curr": "CNY", "to_curr": "JPY"}))125# 1000.0 人民币(CNY)= 20800.00 日元(JPY) ← 手算 1000 / 1.0 * 20.8 = 20800.0 ✓126print(convert_currency.invoke({"amount": 100, "from_curr": "KRW", "to_curr": "CNY"}))127# 不支持的源货币:KRW。支持的货币:CNY, USD, EUR, GBP, JPY, HKD ← 不抛异常128
129# ==================== 3-1 完整脚本 ====================130import math131import os132from datetime import datetime, timedelta133
134from dotenv import load_dotenv135from langchain.agents import create_agent136from langchain.chat_models import init_chat_model137from langchain_core.tools import tool138
139load_dotenv(override=True)140
141model = init_chat_model(142 model="deepseek-v4-flash",143 model_provider="openai",144 api_key=os.getenv("DEEPSEEK_API_KEY"),145 base_url=os.getenv("DEEPSEEK_BASE_URL"),146)147
148# ---------- 工具定义(天气 / 搜索,其余三个见上)----------149@tool150def get_weather(city: str) -> str:151 """获取指定城市的实时天气信息152
153 Args:154 city: 城市名称,如"北京"、"上海"、"深圳"等155 """156 weather_db = {"北京": "多云,15-22℃,空气质量良", "杭州": "晴天,17-24℃,空气质量优"}157 if city in weather_db:158 return f"{city}:{weather_db[city]}"159 return f"抱歉,暂不支持查询{city}的天气信息。当前支持:北京、杭州"160
161@tool162def search_info(keyword: str, category: str = "all") -> str:163 """搜索各类信息164
165 Args:166 keyword: 搜索关键词167 category: 搜索分类:product 产品 / news 新闻 / all 全部168 """169 products = {"手机": "iPhone 15 (¥5999), 小米14 (¥3999)", "耳机": "AirPods Pro (¥1999)"}170 news = {"AI": "1. GPT-5 即将发布 2. AI 芯片市场增长 30%"}171 results = []172 if category in ["product", "all"]:173 results += [f"【产品】{k}:{v}" for k, v in products.items() if keyword in k]174 if category in ["news", "all"]:175 results += [f"【新闻】{k} 相关:{v}" for k, v in news.items() if keyword in k or keyword in v]176 return "\n".join(results) if results else f"未找到关于 '{keyword}' 的{category}信息"177
178# ---------- 主程序 ----------179def main():180 assistant = SmartAssistant()181
182 print("=" * 40)183 print("🤖 多功能智能助手(LangChain 1.2)")184 print("=" * 40)185 print("\n输入 'quit' 退出,输入 'reset' 重置对话\n")186
187 demos = [188 "北京今天天气怎么样?",189 "帮我算一下 (25 + 17) * 3",190 "现在几点了?",191 "100 美元等于多少人民币?",192 ]193 for demo in demos:194 print(f"👤 {demo}")195 print(f"🤖 {assistant.chat(demo)}\n")196
197 assistant.reset() # 让 demo 不污染交互198 print("=" * 40)199 print("🤖 进入交互模式")200 print("=" * 40)201
202 while True:203 user_input = input("\n👤 你: ")204 if user_input.lower() == "quit":205 print("再见!👋")206 break207 if user_input.lower() == "reset":208 assistant.reset()209 print("✅ 对话已重置")210 continue211 if not user_input.strip():212 continue213 print(f"🤖 助手: {assistant.chat(user_input)}")214
215if __name__ == "__main__":216 main()217# 3-1 第 4 问:在 chat() 里加一行 print(f"[消息列表现在有 {len(self.messages)} 条]")218# 一轮问答后通常是 4 条(用户消息 + AI 工具调用 + 工具返回 + 最终回答)如果你喜欢,那么欢迎来到我的世界!
了解更多暂未播放



"生活不是等待暴风雨过去,而是学会在雨中跳舞。"—— 塞维涅夫人


