用 LangChain + 通义千问,打造一个能查天气、能搜新闻的多任务问答助手
实战用 LangChain 通义千问打造一个能查天气、能搜新闻的多任务问答助手前段时间拿到一个基于 LangChain 构建的多任务问答助手项目本来跑的是 OpenAI 的 gpt-4o在国内网络环境下体验不佳。索性花了一个晚上把它整体迁移到国产大模型——阿里云通义千问qwen-max把架构、代码、踩坑记录都整理了出来。这篇文章不讲大道理全部是能跑通的代码和真实运行日志希望对想入门 LangChain 工具调用Function Calling的同学有帮助。为什么写这篇先看三个现状LangChain 的教程多但完整可运行的项目少——多数教程只讲一个llm.invoke()就结束了工具调用、日志、配置管理这些工程细节全靠自己摸索。OpenAI 的项目在国内迁移有真实需求——API 访问不稳定、key 获取门槛高而国产模型通义千问已经提供了 OpenAI 兼容接口切换成本极低。能查天气 能搜新闻的 Agent 是绝佳的入门案例——麻雀虽小五脏俱全配置层、日志层、工具层、代理层全都有。这篇文章将带你完整拆解这个项目并给出国产化迁移的完整方案。一、这个助手能做什么项目是一个CLI 交互式问答助手支持三类能力能力示例提问背后调用的工具 日常对话“你好你能做什么”无LLM 直接回答️ 天气查询“查询北京今天的天气”高德地图天气 API 信息搜索“搜索最新的人工智能新闻”Tavily 搜索 API核心机制是Function Calling函数调用LLM 不再是只会说话的聊天机器人它能根据用户问题自己决定是否需要调用工具、调用哪个工具、传什么参数。用户提问 查询北京天气 ↓ qwen-max 分析这个问题需要工具 → 决定调用 weather_query(city_name北京) ↓ 程序执行高德 API拿到天气数据 ↓ LLM 把结构化数据组织成自然语言回答北京今天 32°C晴...二、整体架构教科书式的分层项目虽然不大但分层非常清晰是一个标准的分层架构Layered Architecture┌───────────────────────────────────────────────┐ │ 入口层 main.pyCLI 交互循环 │ ├───────────────────────────────────────────────┤ │ 代理层 agents/qa_agent.pyQAAgent │ ├───────────────────────────────────────────────┤ │ 工具层 tools/高德天气 Tavily 搜索 │ ├───────────────────────────────────────────────┤ │ 配置层 config/settings.pyPydantic │ │ 基础设施 core/logger.pyloguru 日志 │ └───────────────────────────────────────────────┘依赖方向是单向的tools → agents → main配置层和日志层作为基础设施被各层共享没有循环依赖。这在中小型项目中是非常正确的选择——不要一上来就上微服务分层架构足够应对 90% 的场景。 工程经验分层架构的核心价值是依赖单向。如果哪天你发现tools里的代码反过来 import 了agents说明分层已经坏了要尽早拆。三、环境准备一套依赖 一个 .env3.1 依赖安装项目依赖集中在requirements.txt核心只有几个pipinstalllangchain langchain-openai langchain-core\tavily-python python-dotenv pydantic pydantic-settings\loguru-ihttps://pypi.tuna.tsinghua.edu.cn/simple如果已有 conda 环境建议在独立环境如llmops中安装避免污染全局 Python。3.2 环境变量一套 key 走天下项目所有密钥统一放在.env中由python-dotenv加载。这里有个关键点——迁移到国产模型后我们让 DashScope 的 key 伪装成 OpenAI 的 key# OpenAI 兼容 API 配置实际指向阿里云 DashScope OPENAI_API_KEYsk-你的DashScope密钥 OPENAI_BASE_URLhttps://dashscope.aliyuncs.com/compatible-mode/v1 # LLM 模型名通义千问 LLM_MODELqwen-max # 高德地图天气查询 AMAP_API_KEY你的高德Web服务key # Tavily 搜索 TAVILY_API_KEY你的Tavily key为什么能这样因为通义千问提供了 OpenAI 兼容模式——接口路径、请求格式、返回结构都和 OpenAI 一致。所以 LangChain 的ChatOpenAI类不需要换只需要换 base_url 和模型名。四、核心模块逐层拆解4.1 配置层Pydantic 单例模式配置模块用 Pydantic 做数据校验这是企业级项目的标准做法classAPISettings(BaseSettings):openai_api_key:strField(...,descriptionAPI密钥)amap_api_key:strField(...,description高德API密钥)validator(openai_api_key,amap_api_key)defvalidate_api_keys(cls,v):验证API密钥不能为空ifnotvorv.strip():raiseValueError(API密钥不能为空)returnv.strip()classSettings:全局配置管理器 - 单例模式_instanceNone_initializedFalsedef__new__(cls):ifcls._instanceisNone:cls._instancesuper().__new__(cls)returncls._instance两个亮点Pydantic 校验key 为空、端口越界、日志级别非法都会在启动时直接报错而不是运行到一半才炸单例模式整个程序只有一份配置实例避免多处读取导致的不一致4.2 日志层loguru 四路输出日志模块是项目里最专业的部分用 loguru 配置了四路输出输出目标记录内容保留策略控制台全部日志带颜色—app_日期.log应用全量日志30 天按天轮转error_日期.log仅 ERROR 级别90 天api_日期.logAPI 调用专项日志7 天zip 压缩logger.add(sys.stdout,formatgreen{time:YYYY-MM-DD HH:mm:ss}/green | level{level: 8}/level | {message},levelINFO,colorizeTrue)logger.add(os.path.join(log_dir,error_{time:YYYY-MM-DD}.log),levelERROR,rotation00:00,retention90 days,compressionzip) 工程经验日志不是print 的升级版而是排障的第一现场。API 调用日志单独落文件、错误日志长保留、按天轮转压缩——这三条够用 90% 的场景。4.3 工具层统一返回结构每个外部 API 封装成一个类所有工具返回统一的{success, data, error}结构classAmapWeatherTool:defget_weather(self,city_name:str)-Dict[str,Any]:try:...return{success:True,data:formatted_data}exceptrequests.exceptions.Timeout:return{success:False,error:请求超时请稍后重试}exceptExceptionase:return{success:False,error:f获取天气信息失败:{str(e)}}同时用 Pydantic 定义工具的参数 Schema让 LLM 知道该传什么参数classWeatherQuery(BaseModel):天气查询工具city_name:strField(...,description要查询天气的城市名称例如北京、上海、广州等) 工程经验统一的返回结构让上层代码永远不需要判断这个工具返回了什么形状只需检查 success。这是工具层设计的黄金法则。4.4 代理层LLM 自己决定要不要调工具这是整个项目最核心的机制——bind_toolsself.llmChatOpenAI(modelos.getenv(LLM_MODEL,qwen-plus),# qwen-maxapi_keysettings.api.openai_api_key,base_urlsettings.api.openai_base_url,# DashScope 兼容端点temperature0.3,max_tokens1000)self.llm_with_toolsself.llm.bind_tools(self.tools)bind_tools把工具列表绑定到 LLM 上之后 LLM 的返回值中会包含tool_calls字段——LLM 自己决定要不要调用工具responseself.llm_with_tools.invoke(user_input)ifresponse.tool_calls:# LLM 认为需要调用工具fortool_callinresponse.tool_calls:tool_nametool_call[name]tool_argstool_call[args]# 执行对应工具 ...else:# 普通对话直接回答final_responseself.general_chain.invoke({query:user_input})用一张图理解整个调用流程不需要工具需要工具用户提问LLM 判断通用对话链prompt | llm | output解析 tool_calls执行工具天气 / 搜索LLM 格式化工具结果自然语言回答五、国产化迁移从 gpt-4o 到 qwen-max迁移过程其实只有三步全程半小时第 1 步改模型名DashScope 的 OpenAI 兼容层不支持gpt-4o等 OpenAI 模型名必须换成通义千问的模型标识# 修改前modelgpt-4o# 修改后从环境变量读取更灵活modelos.getenv(LLM_MODEL,qwen-plus)# .env 中设为 qwen-max第 2 步改 base_url# 修改前 OPENAI_BASE_URLhttps://api.openai.com/v1 # 修改后 OPENAI_BASE_URLhttps://dashscope.aliyuncs.com/compatible-mode/v1第 3 步补依赖 跑起来环境里缺了tavily-python安装后直接运行pipinstalltavily-python-ihttps://pypi.tuna.tsinghua.edu.cn/simple python main.py注意ChatOpenAI类完全不用换——这就是 OpenAI 兼容模式的最大价值应用层零改动。六、运行效果实录日常对话您: hello 正在思考... 助手: 你好有什么可以帮助你的吗如果想了解天气可以告诉我查询XX城市天气 如果需要搜索信息可以说搜索XX。 ⏱️ 处理时间: 1577.2ms工具调用自动识别意图 自动传参您: 查询北京今天的天气 正在思考... 检测到工具调用: 1个 调用工具: weather_query, 参数: {city_name: 北京} 助手: 您好看起来在尝试获取北京今天的天气信息时遇到了一些技术问题...看第二行输出——qwen-max 自动识别出查天气意图自动填好了city_name北京参数。这就是 Function Calling 的魅力意图识别、参数抽取全由模型完成代码只需要执行。注示例中高德 key 未配置工具返回了错误但链路是通的。配置真实 key 后即可拿到温度、天气、风力等完整数据。七、踩坑记录真实经历坑 1DashScope 不支持 gpt-4o ❌迁移时第一反应是只改 key 就行结果报模型不存在。DashScope 兼容层只认通义自家的模型名qwen-plus / qwen-max / qwen-turbo。教训模型名必须同步替换。坑 2Windows 控制台中文乱码 ❌运行日志全是锟斤拷。原因Windows cmd 默认 GBK 编码Python 输出 UTF-8。解决python-Xutf8 main.py# Python 3.7 可用坑 3Tavily 测试 key 超限 ❌项目里 Tavily key 硬编码在代码里tvly-dev-xxx早就超了使用额度搜索返回usage limit。这暴露了一个架构问题——密钥不应硬编码在代码中应统一收敛到.env。坑 4依赖版本断档 ❌requirements.txt锁定langchain0.1.17而环境里是 1.x。好在项目只用到了ChatOpenAI/ChatPromptTemplate/StrOutputParser/bind_tools这些跨版本稳定的核心 API直接跑通。教训锁定版本要慎重过度锁定反而增加迁移成本。八、架构点评亮点与槽点✅ 值得学习的亮点分层清晰、依赖单向tools → agents → main基础设施层独立配置有校验Pydantic 启动即校验错误前置暴露日志专业四路输出 轮转 压缩可直接上生产工具返回结构统一{success, data, error}让上层零判断成本⚠️ 可以改进的槽点密钥硬编码Tavily key 写死在工具类默认参数里应收敛到.env工具分发用 if/elif 写死新增工具要改主流程代码应改为工具名 → 函数映射表或直接用 LangChain Agent 标准机制多轮对话无记忆conversation_history一直在记录却从未传入 LLM多轮名不副实命名不一致.env.example写的是和风天气QWEATHER_API_KEY实际用的是高德AMAP_API_KEY——历史迭代没清理九、总结与下一步这个项目麻雀虽小五脏俱全——配置校验、结构化日志、工具封装、Function Calling、国产模型迁移一个 Agent 应用该有的要素都有了。如果你正在入门 LangChain非常推荐照着这个结构搭一个自己的助手。下一步可以玩的方向 把 if/elif 分发改成工具映射表支持热插拔新工具 把对话历史真正注入 LLM实现多轮记忆 加一个FastAPI 接口层从 CLI 变成 Web 服务 把知识库接进来LlamaIndex 切片 RAG升级成企业问答机器人如果这篇文章对你有帮助欢迎点赞、在看、转发让更多人看到国产大模型 LangChain 的实战姿势文中项目代码结构完整、可直接复现。关注公众号回复多任务助手获取完整源码与配置说明。