ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

AI智能体开发实战:从LLM工具调用到天气查询应用部署

AI智能体开发实战:从LLM工具调用到天气查询应用部署 AI 智能体开发在 2024 年已经成为技术热点但很多开发者面临的问题是概念听起来很酷实际动手时却不知道从哪开始LLM、Agent、RAG、Function Calling 这些术语背后到底对应什么代码和配置。本文将以一个可运行的天气查询智能体为例带你完成从环境准备、核心模块开发、工具集成到生产部署的全流程重点解释每个环节的设计逻辑和常见坑点。如果你已经了解 Python 基础语法想用 4 到 6 周时间系统掌握 AI 应用开发这篇文章会提供一条从实验到项目的实践路径。最终完成的智能体不仅能理解用户对天气的模糊描述还能调用真实 API 返回结构化数据并且具备简单的错误处理和扩展能力。1. 先理解 AI 智能体的核心组成和工作流程AI 智能体不是简单的聊天机器人它的核心能力是理解用户意图、决定需要执行哪些操作、调用工具获取信息、处理结果并最终生成回答。这个决策和执行过程涉及几个关键组件。1.1 LLM 在智能体中的角色是意图理解和决策中枢大语言模型是智能体的“大脑”但它不直接执行具体任务。以天气查询为例当用户输入“北京今天需要带伞吗”LLM 需要解析出几个关键信息地点是“北京”时间是“今天”用户真实需求是“判断是否下雨”。这个解析过程称为意图识别。LLM 接着要决定是否需要调用外部工具。如果对话历史中已经有北京今天的天气数据它可能直接回答如果没有它就需要决定调用天气查询函数。这个决策能力来自对 LLM 的特定提示工程和函数调用规范的训练。1.2 工具调用是智能体与外部世界交互的核心方式智能体通过工具与外部系统交互。工具可以是简单的函数如查询数据库也可以是复杂的 API 调用如获取实时天气。工具调用规范通常包括工具名称、描述、参数 schema 和认证方式。常见的工具调用模式有两种一种是 LangChain 提供的 Tools 抽象另一种是 LLM 原生的 Function Calling。前者更适合复杂的工作流编排后者通常延迟更低且与模型厂商更新保持同步。1.3 记忆机制让智能体能够处理多轮对话单次问答无法满足复杂需求。智能体需要记忆之前的对话内容、工具调用结果和用户偏好。记忆可以分为短期记忆当前会话和长期记忆跨会话持久化。实现记忆的典型方式包括在提示词中嵌入对话历史、使用向量数据库存储和检索相关历史、或者设计结构化的会话存储。记忆机制直接影响智能体的连贯性和个性化程度。1.4 智能体与普通 AI 应用的关键区别在决策自主性普通 AI 应用通常被动响应用户请求而智能体能够主动规划任务步骤。例如当用户说“帮我安排一次北京三日游”智能体可能会自主分解为查询天气、查找景点、推荐酒店、规划路线等子任务并按顺序或并行执行。这种自主性来自提示工程中明确的角色设定和目标描述以及 LLM 的任务分解能力。评估智能体质量时不仅要看最终结果是否正确还要看其决策过程是否合理高效。2. 搭建开发环境选择适合实验和生产的工具链智能体开发需要平衡快速迭代和后期部署需求。下面这套工具链既适合学习阶段验证想法也容易迁移到生产环境。2.1 Python 环境与关键库版本锁定智能体开发对版本敏感不同版本的库可能在接口和功能上有较大差异。建议使用 Python 3.9 或 3.10这两个版本在 AI 库兼容性和稳定性方面表现最好。# 创建并激活虚拟环境 python -m venv ai_agent_env source ai_agent_env/bin/activate # Linux/Mac # ai_agent_env\Scripts\activate # Windows # 安装核心依赖 pip install openai1.3.0 pip install langchain0.0.350 pip install python-dotenv1.0.0为什么选择这些版本OpenAI 1.x 版本提供了更规范的客户端接口LangChain 0.0.350 在工具调用和 Agent 运行方面相对稳定python-dotenv 则用于管理 API 密钥等敏感配置。2.2 配置 API 密钥与环境变量永远不要将 API 密钥硬编码在代码中。使用.env文件管理配置并在代码中通过环境变量读取。# 创建 .env 文件内容如下 OPENAI_API_KEY你的实际API密钥 WEATHER_API_KEY你的天气API密钥# config.py - 配置文件 import os from dotenv import load_dotenv load_dotenv() OPENAI_API_KEY os.getenv(OPENAI_API_KEY) WEATHER_API_KEY os.getenv(WEATHER_API_KEY) if not OPENAI_API_KEY: raise ValueError(请设置 OPENAI_API_KEY 环境变量)2.3 项目结构设计为可扩展模式即使是学习项目良好的结构也能避免后期重构。建议按功能模块划分目录。weather_agent/ ├── agents/ # 智能体核心逻辑 │ ├── __init__.py │ └── weather_agent.py ├── tools/ # 工具定义 │ ├── __init__.py │ └── weather_tools.py ├── config.py # 配置管理 ├── requirements.txt # 依赖列表 └── main.py # 入口文件这种结构的好处是工具可以独立开发和测试智能体逻辑集中管理配置统一处理。当需要添加新功能时只需在相应目录创建新模块。2.4 测试环境与生产环境的配置分离开发阶段可以使用模拟数据或免费 API生产环境则需要考虑速率限制、错误处理和监控。在配置文件中区分环境# config.py import os ENV os.getenv(ENVIRONMENT, development) if ENV production: API_BASE_URL https://api.weatherapi.com/v1 TIMEOUT 30 else: API_BASE_URL https://api.weatherapi.com/v1 # 或使用模拟服务 TIMEOUT 103. 实现天气查询工具从简单函数到健壮 API 调用工具是智能体的手脚需要同时考虑功能正确性和异常处理。我们以实现天气查询工具为例展示如何设计一个生产可用的工具。3.1 设计工具的函数签名和返回值格式工具应该具有清晰的输入输出约定这样智能体才能正确解析和使用。对于天气查询我们需要地点参数返回结构化的天气信息。# tools/weather_tools.py import requests import json from config import WEATHER_API_KEY, API_BASE_URL, TIMEOUT def get_current_weather(location: str) - str: 获取指定城市的当前天气情况 Args: location: 城市名称如北京或Shanghai Returns: JSON 格式的字符串包含温度、天气状况、湿度等信息 try: # 构建请求参数 params { key: WEATHER_API_KEY, q: location, aqi: no # 不查询空气质量简化响应 } response requests.get( f{API_BASE_URL}/current.json, paramsparams, timeoutTIMEOUT ) response.raise_for_status() # 检查HTTP错误 data response.json() # 提取关键信息 weather_info { location: data[location][name], temperature: data[current][temp_c], condition: data[current][condition][text], humidity: data[current][humidity], wind_speed: data[current][wind_kph] } return json.dumps(weather_info, ensure_asciiFalse) except requests.exceptions.RequestException as e: return f查询天气时出错: {str(e)} except KeyError as e: return f解析天气数据时出错: 缺少关键字段 {str(e)}这个实现包含了几个重要细节明确的类型注解、详细的文档字符串、完整的异常处理、关键数据提取和 JSON 序列化。3.2 为工具调用添加缓存和限流机制频繁调用外部 API 可能触发速率限制同时也会增加成本和延迟。添加简单的缓存机制可以显著提升体验。# tools/weather_tools.py import time from functools import lru_cache lru_cache(maxsize100) def get_current_weather_cached(location: str) - str: 带缓存功能的天气查询相同地点10分钟内不会重复调用API # 缓存逻辑已由lru_cache处理 return get_current_weather(location) # 可以自定义更复杂的缓存策略 class WeatherCache: def __init__(self, ttl600): # 默认10分钟 self.cache {} self.ttl ttl def get(self, location): if location in self.cache: data, timestamp self.cache[location] if time.time() - timestamp self.ttl: return data # 缓存不存在或已过期 data get_current_weather(location) self.cache[location] (data, time.time()) return data生产环境中缓存策略需要根据数据更新频率和用户需求进行调优。天气数据可以缓存 10-30 分钟而股票价格可能只能缓存几分钟。3.3 验证工具单独工作的正确性在集成到智能体之前必须单独测试工具功能。创建简单的测试脚本# test_weather_tool.py from tools.weather_tools import get_current_weather def test_weather_tool(): # 测试正常情况 result get_current_weather(北京) print(北京天气:, result) # 测试错误情况 result get_current_weather(不存在的城市) print(错误处理:, result) if __name__ __main__: test_weather_tool()运行测试应该能看到结构化的天气数据或清晰的错误信息。这个步骤能帮助我们在早期发现 API 密钥、网络连接或数据解析问题。4. 构建智能体核心连接 LLM 与工具调用有了可靠的工具后我们需要让 LLM 能够理解何时以及如何调用这些工具。这里使用 OpenAI 的 Function Calling 功能它比 LangChain 更轻量且响应更快。4.1 定义工具的描述信息供 LLM 理解LLM 需要通过自然语言描述来理解每个工具的功能和参数。这些描述直接影响智能体能否正确选择工具。# agents/weather_agent.py import json from openai import OpenAI from config import OPENAI_API_KEY # 工具描述必须清晰准确 weather_tool_description { type: function, function: { name: get_current_weather, description: 获取指定城市的当前天气情况包括温度、天气状况、湿度等信息, parameters: { type: object, properties: { location: { type: string, description: 城市名称如北京或Shanghai } }, required: [location] } } } class WeatherAgent: def __init__(self): self.client OpenAI(api_keyOPENAI_API_KEY) self.tools [weather_tool_description] self.conversation_history [] def add_to_history(self, role, content): 维护对话历史 self.conversation_history.append({role: role, content: content}) # 限制历史长度避免token超限 if len(self.conversation_history) 10: self.conversation_history self.conversation_history[-6:]工具描述中的几个关键点名称要唯一且具描述性功能说明要明确使用场景参数定义要详细但不过于复杂。4.2 实现智能体的决策和工具调用循环智能体的核心逻辑是一个循环分析用户输入 - 决定是否调用工具 - 执行工具 - 基于结果生成回答。# agents/weather_agent.py class WeatherAgent: # ... 初始化代码 ... def process_query(self, user_input: str) - str: 处理用户查询的核心方法 # 准备对话上下文 messages self.conversation_history.copy() messages.append({role: user, content: user_input}) # 第一步让LLM决定是否需要调用工具 response self.client.chat.completions.create( modelgpt-3.5-turbo-1106, # 支持function calling的版本 messagesmessages, toolsself.tools, tool_choiceauto # 让模型自动决定 ) message response.choices[0].message messages.append(message) # 将LLM的响应加入历史 # 第二步如果LLM决定调用工具执行工具调用 if message.tool_calls: for tool_call in message.tool_calls: function_name tool_call.function.name function_args json.loads(tool_call.function.arguments) if function_name get_current_weather: # 实际调用天气工具 from tools.weather_tools import get_current_weather tool_result get_current_weather(function_args[location]) # 将工具执行结果加入对话历史 messages.append({ role: tool, tool_call_id: tool_call.id, content: tool_result }) # 第三步让LLM基于工具结果生成最终回答 second_response self.client.chat.completions.create( modelgpt-3.5-turbo-1106, messagesmessages ) final_response second_response.choices[0].message.content else: final_response message.content # 更新对话历史 self.add_to_history(user, user_input) self.add_to_history(assistant, final_response) return final_response这个三段式流程决策-执行-生成是大多数智能体的基础模式。关键优势在于LLM 只需要决定要做什么具体的工具执行和错误处理由代码负责。4.3 处理工具调用中的异常和边界情况工具调用可能失败智能体需要妥善处理各种异常情况而不是直接崩溃。# agents/weather_agent.py class WeatherAgent: # ... 其他代码 ... def safe_tool_call(self, function_name, function_args): 安全的工具调用包含错误处理 try: if function_name get_current_weather: from tools.weather_tools import get_current_weather result get_current_weather(function_args[location]) # 检查工具返回的是否是错误信息 if 出错 in result or 错误 in result: return f工具执行失败: {result} return result except Exception as e: return f工具调用异常: {str(e)} def process_query(self, user_input: str) - str: # ... 前面的代码 ... if message.tool_calls: for tool_call in message.tool_calls: function_name tool_call.function.name function_args json.loads(tool_call.function.arguments) tool_result self.safe_tool_call(function_name, function_args) messages.append({ role: tool, tool_call_id: tool_call.id, content: tool_result }) # ... 后面的代码 ...这种设计保证了即使外部服务不可用智能体也能给出有意义的错误提示而不是暴露技术细节或直接停止工作。5. 运行测试与效果验证完成代码实现后需要系统性地测试智能体的各项能力。测试应该覆盖正常流程、边界情况和错误处理。5.1 设计覆盖不同场景的测试用例有效的测试应该模拟真实用户的各种输入方式验证智能体能否正确理解意图并调用合适的工具。# test_agent.py from agents.weather_agent import WeatherAgent def run_test_cases(): agent WeatherAgent() test_cases [ # 正常查询 北京今天天气怎么样, # 模糊查询 我需要知道上海的天气, # 包含额外上下文 我明天要去广州出差天气如何, # 错误地点 查询一个不存在的城市的天气, # 多轮对话 北京呢, # 跟进查询 # 非天气问题 你会做什么, ] for i, query in enumerate(test_cases, 1): print(f\n 测试用例 {i} ) print(f用户: {query}) response agent.process_query(query) print(f智能体: {response}) # 添加间隔避免API速率限制 import time time.sleep(1) if __name__ __main__: run_test_cases()预期应该看到对于天气查询智能体调用工具并返回结构化信息对于跟进查询它能利用对话历史理解北京指代之前的话题对于非天气问题它应该礼貌说明自己的能力范围。5.2 验证工具调用的正确性和效率除了功能正确还需要关注性能指标特别是工具调用的延迟和成功率。# performance_test.py import time from agents.weather_agent import WeatherAgent def performance_test(): agent WeatherAgent() queries [北京天气, 上海天气, 广州天气] total_time 0 success_count 0 for query in queries: start_time time.time() try: response agent.process_query(query) end_time time.time() elapsed end_time - start_time total_time elapsed if 温度 in response or 天气 in response: success_count 1 print(f✓ {query}: {elapsed:.2f}秒) else: print(f✗ {query}: 响应内容异常) except Exception as e: print(f✗ {query}: 执行失败 - {e}) print(f\n成功率: {success_count}/{len(queries)}) print(f平均响应时间: {total_time/len(queries):.2f}秒) if __name__ __main__: performance_test()在开发环境中平均响应时间应该在 2-5 秒之间。如果超过这个范围需要检查网络延迟、API 限流或代码逻辑问题。5.3 分析智能体的决策过程和质量通过查看详细的调试信息我们可以了解 LLM 的决策逻辑从而优化工具描述和提示词。# agents/weather_agent.py class WeatherAgent: def __init__(self, debugFalse): # ... 其他初始化 ... self.debug debug def process_query(self, user_input: str) - str: # ... 前面的代码 ... if self.debug and message.tool_calls: print(DEBUG: LLM决定调用工具:, [t.function.name for t in message.tool_calls]) print(DEBUG: 工具参数:, [json.loads(t.function.arguments) for t in message.tool_calls]) # ... 后面的代码 ...启用调试模式后可以看到 LLM 是如何解析用户意图的这有助于改进工具描述和提示工程。6. 常见问题排查与优化建议实际部署智能体时会遇到各种问题下面列出典型问题的排查路径和解决方案。6.1 工具调用相关的问题排查工具调用失败是最常见的问题需要系统性地检查各个环节。问题现象可能原因检查方式解决方案LLM 不调用工具工具描述不清晰或用户意图不明确检查调试输出查看LLM的决策过程改进工具描述增加示例或明确使用场景工具参数错误参数格式或类型不匹配检查工具调用时的参数解析日志调整参数schema增加参数验证API 调用失败网络问题、认证失败或配额不足单独测试工具函数检查错误信息验证API密钥、网络连接和调用配额响应超时外部服务响应慢或网络延迟添加超时监控和日志调整超时设置添加重试机制6.2 性能优化和成本控制策略随着使用量增加性能和成本成为关键考虑因素。缓存策略优化根据数据更新频率设计多级缓存。天气数据可以缓存 10 分钟用户配置可以缓存更长时间。# 实现带TTL的缓存装饰器 import functools import time def cached_with_ttl(ttl_seconds600): def decorator(func): cache {} functools.wraps(func) def wrapper(*args, **kwargs): key str(args) str(kwargs) if key in cache: result, timestamp cache[key] if time.time() - timestamp ttl_seconds: return result result func(*args, **kwargs) cache[key] (result, time.time()) return result return wrapper return decorator批量处理优化当需要查询多个地点的天气时可以设计批量查询接口减少 API 调用次数。成本监控记录每次 LLM 调用和工具调用的开销设置每日预算和告警阈值。6.3 对话质量和一致性的提升方法智能体的回答应该准确、有用且风格一致。提示词工程优化在系统消息中明确智能体的角色和能力范围。system_message 你是一个专业的天气助手专门帮助用户查询天气信息。 你的能力包括 - 查询全球城市的当前天气 - 提供温度、湿度、风力等详细信息 - 根据天气情况给出实用建议 如果你无法回答非天气相关问题请礼貌地说明你的专长范围。 保持回答专业、简洁、有用。 回答模板化对于结构化数据使用模板确保信息呈现的一致性。def format_weather_response(weather_data): 将天气数据格式化为易读的回答 data json.loads(weather_data) return f {data[location]}当前天气 ️ 温度{data[temperature]}°C ☁️ 状况{data[condition]} 湿度{data[humidity]}% ️ 风速{data[wind_speed]} km/h 7. 生产环境部署与扩展方向学习环境的智能体需要经过一系列改造才能满足生产要求。以下是关键的生产化考量点。7.1 安全性加固和访问控制生产环境必须考虑安全因素防止未授权访问和滥用。API 密钥管理使用专业的密钥管理服务定期轮转密钥避免硬编码。输入验证和过滤对所有用户输入进行验证防止注入攻击。def validate_location(location: str) - bool: 验证地点参数是否合法 if not location or len(location) 50: return False # 只允许字母、数字和常见标点 import re pattern r^[a-zA-Z0-9\s\-,\.]$ return bool(re.match(pattern, location))速率限制基于用户或 IP 实施调用频率限制。7.2 监控、日志和可观测性生产系统需要完整的监控体系来保证可用性和快速排错。结构化日志记录关键操作和错误信息。import logging logging.basicConfig( levellogging.INFO, format%(asctime)s - %(name)s - %(levelname)s - %(message)s ) logger logging.getLogger(weather_agent) def process_query(self, user_input: str) - str: logger.info(f处理查询: {user_input}) try: # ... 处理逻辑 ... logger.info(查询处理完成) return result except Exception as e: logger.error(f处理查询时出错: {e}) return 系统暂时无法处理您的请求性能指标收集监控响应时间、成功率、工具调用次数等关键指标。7.3 扩展为多工具智能体架构单一天气查询工具只能解决特定问题真正的智能体应该能够根据需求调用不同的工具。工具注册机制设计统一的工具注册和发现接口。class ToolRegistry: def __init__(self): self.tools {} def register_tool(self, name, description, function): self.tools[name] { description: description, function: function } def get_tool_descriptions(self): return [tool[description] for tool in self.tools.values()] def call_tool(self, name, arguments): if name not in self.tools: raise ValueError(f未知工具: {name}) return self.tools[name][function](**arguments)技能组合与任务分解让智能体能够处理复杂任务如规划北京三日游需要组合天气查询、景点推荐、路线规划等多个工具。智能体开发是一个迭代过程从最小可行产品开始逐步增加工具、优化提示词、改进用户体验。这个天气查询智能体提供了完整的技术框架可以在此基础上扩展更多实用功能最终构建出真正理解用户需求并能主动协助完成任务的AI助手。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进