Hello-Agents 学习笔记(三)5 分钟实现第一个 Agent——智能旅行助手实践

上一篇学习了智能体的基本概念、运行机制以及 Thought-Action-Observation(TAO)循环,这次开始真正动手,实现一个可以调用工具完成任务的智能旅行助手。

整个案例虽然不复杂,但已经完整体现了 Agent 的基本工作流程。


一、项目目标

本次实现的是一个简单的旅行助手。

用户输入:

帮我查询今天北京的天气,然后根据天气推荐一个旅游景点。

对于人来说,这是一个很普通的问题。

但是对于 Agent,它需要自己完成多个步骤:

  1. 理解用户需求
  2. 查询天气
  3. 根据天气搜索景点
  4. 综合结果返回答案

整个过程并不是一次回答完成,而是在多个循环中逐步推进。


二、准备工作

整个项目依赖三个库:

pip install requests tavily-python openai

分别负责:

  • requests:调用天气接口
  • tavily:联网搜索景点
  • openai:调用大语言模型

除此之外,还需要准备:

  • LLM API Key(我使用的是阿里百炼/Qwen)
  • Tavily API Key(用于联网搜索)

2.1 LLM API Key

大模型服务平台百炼控制台

创建api和免费额度

2.2 Tavily API Key

打开浏览器,访问 Tavily

注册并获取 API 密钥

三、prompt

驱动真实 LLM 的关键在于提示工程(Prompt Engineering)。我们需要设计一个“指令模板”,告诉 LLM 它应该扮演什么角色、拥有哪些工具、以及如何格式化它的思考和行动。

prompt.py

AGENT_SYSTEM_PROMPT = """ 你是一个智能旅行助手。你的任务是分析用户的请求,并使用可用工具一步步地解决问题。 # 可用工具: - `get_weather(city: str)`: 查询指定城市的实时天气。 - `get_attraction(city: str, weather: str)`: 根据城市和天气搜索推荐的旅游景点。 # 输出格式要求: 你的每次回复必须严格遵循以下格式,包含一对Thought和Action: Thought: [你的思考过程和下一步计划] Action: [你要执行的具体行动] Action的格式必须是以下之一: 1. 调用工具:function_name(arg_name="arg_value") 2. 结束任务:Finish[最终答案] # 重要提示: - 每次只输出一对Thought-Action - Action必须在同一行,不要换行 - 当收集到足够信息可以回答用户问题时,必须使用 Action: Finish[最终答案] 格式结束 请开始吧! """

四、实现两个工具

为了让 Agent 能真正完成任务,需要给它提供工具。

① 查询天气

通过 wttr.in 提供的天气接口获取实时天气。

工具最终返回类似:

北京当前天气:Sunny,26℃

这样 Agent 就获得了第一条 Observation。

② 搜索景点

第二个工具使用 Tavily Search。

它会根据:

  • 城市
  • 天气

联网搜索适合游玩的景点。

例如:

北京 Sunny

返回:

  • 颐和园
  • 长城

Agent 不需要知道景点信息,它只负责调用工具。

tools.py

import requests import os from tavily import TavilyClient def get_weather(city: str) -> str: """ 通过调用 wttr.in API 查询真实的天气信息。 """ # API端点,我们请求JSON格式的数据 url = f"https://wttr.in/{city}?format=j1" try: # 发起网络请求 response = requests.get(url) # 检查响应状态码是否为200 (成功) response.raise_for_status() # 解析返回的JSON数据 data = response.json() # 提取当前天气状况 current_condition = data['current_condition'][0] weather_desc = current_condition['weatherDesc'][0]['value'] temp_c = current_condition['temp_C'] # 格式化成自然语言返回 return f"{city}当前天气:{weather_desc},气温{temp_c}摄氏度" except requests.exceptions.RequestException as e: # 处理网络错误 return f"错误:查询天气时遇到网络问题 - {e}" except (KeyError, IndexError) as e: # 处理数据解析错误 return f"错误:解析天气数据失败,可能是城市名称无效 - {e}" def get_attraction(city: str, weather: str) -> str: """ 根据城市和天气,使用Tavily Search API搜索并返回优化后的景点推荐。 """ # 1. 从环境变量中读取API密钥 api_key = os.environ.get("TAVILY_API_KEY") if not api_key: return "错误:未配置TAVILY_API_KEY环境变量。" # 2. 初始化Tavily客户端 tavily = TavilyClient(api_key=api_key) # 3. 构造一个精确的查询 query = f"'{city}' 在'{weather}'天气下最值得去的旅游景点推荐及理由" try: # 4. 调用API,include_answer=True会返回一个综合性的回答 response = tavily.search(query=query, search_depth="basic", include_answer=True) # 5. Tavily返回的结果已经非常干净,可以直接使用 # response['answer'] 是一个基于所有搜索结果的总结性回答 if response.get("answer"): return response["answer"] # 如果没有综合性回答,则格式化原始结果 formatted_results = [] for result in response.get("results", []): formatted_results.append(f"- {result['title']}: {result['content']}") if not formatted_results: return "抱歉,没有找到相关的旅游景点推荐。" return "根据搜索,为您找到以下信息:\n" + "\n".join(formatted_results) except Exception as e: return f"错误:执行Tavily搜索时出现问题 - {e}" # 将所有工具函数放入一个字典,方便后续调用 available_tools = { "get_weather": get_weather, "get_attraction": get_attraction, }

五、接入大语言模型

llm.py

from openai import OpenAI class OpenAICompatibleClient: """ 一个用于调用任何兼容OpenAI接口的LLM服务的客户端。 """ def __init__(self, model: str, api_key: str, base_url: str): self.model = model self.client = OpenAI(api_key=api_key, base_url=base_url) def generate(self, prompt: str, system_prompt: str) -> str: """调用LLM API来生成回应。""" print("正在调用大语言模型...") try: messages = [ {'role': 'system', 'content': system_prompt}, {'role': 'user', 'content': prompt} ] response = self.client.chat.completions.create( model=self.model, messages=messages, stream=False ) answer = response.choices[0].message.content print("大语言模型响应成功。") return answer except Exception as e: print(f"调用LLM API时发生错误: {e}") return "错误:调用语言模型服务时出错。"

六、Agent Loop

整个程序真正的核心,就是不断执行下面这个循环:

用户输入 ↓ Thought (分析任务) ↓ Action (调用工具) ↓ Observation (获取结果) ↓ 继续下一轮

直到输出:

Finish[]

任务结束。

整个流程对应的就是经典的:

Thought → Action → Observation

main.py

import re import os from prompt import AGENT_SYSTEM_PROMPT from tools import available_tools from llm import OpenAICompatibleClient # --- 1. 配置LLM客户端 --- # 请根据您使用的服务,将这里替换成对应的凭证和地址 API_KEY = "API_KEY" BASE_URL = "https://dashscope.aliyuncs.com/compatible-mode/v1" MODEL_ID = "qwen3.7-plus" TAVILY_API_KEY = "TAVILY_API_KEY" os.environ['TAVILY_API_KEY'] =TAVILY_API_KEY llm = OpenAICompatibleClient( model=MODEL_ID, api_key=API_KEY, base_url=BASE_URL ) # --- 2. 初始化 --- user_prompt = "你好,请帮我查询一下今天北京的天气,然后根据天气推荐一个合适的旅游景点。" prompt_history = [f"用户请求: {user_prompt}"] print(f"用户输入: {user_prompt}\n" + "=" * 40) # --- 3. 运行主循环 --- for i in range(5): # 设置最大循环次数 print(f"--- 循环 {i + 1} ---\n") # 3.1. 构建Prompt full_prompt = "\n".join(prompt_history) # 3.2. 调用LLM进行思考 llm_output = llm.generate(full_prompt, system_prompt=AGENT_SYSTEM_PROMPT) # 模型可能会输出多余的Thought-Action,需要截断 match = re.search(r'(Thought:.*?Action:.*?)(?=\n\s*(?:Thought:|Action:|Observation:)|\Z)', llm_output, re.DOTALL) if match: truncated = match.group(1).strip() if truncated != llm_output.strip(): llm_output = truncated print("已截断多余的 Thought-Action 对") print(f"模型输出:\n{llm_output}\n") prompt_history.append(llm_output) # 3.3. 解析并执行行动 action_match = re.search(r"Action: (.*)", llm_output, re.DOTALL) if not action_match: observation = "错误: 未能解析到 Action 字段。请确保你的回复严格遵循 'Thought: ... Action: ...' 的格式。" observation_str = f"Observation: {observation}" print(f"{observation_str}\n" + "=" * 40) prompt_history.append(observation_str) continue action_str = action_match.group(1).strip() if action_str.startswith("Finish"): final_answer = re.match(r"Finish\[(.*)\]", action_str).group(1) print(f"任务完成,最终答案: {final_answer}") break tool_name = re.search(r"(\w+)\(", action_str).group(1) args_str = re.search(r"\((.*)\)", action_str).group(1) kwargs = dict(re.findall(r'(\w+)="([^"]*)"', args_str)) if tool_name in available_tools: observation = available_tools[tool_name](**kwargs) else: observation = f"错误:未定义的工具 '{tool_name}'" # 3.4. 记录观察结果 observation_str = f"Observation: {observation}" print(f"{observation_str}\n" + "=" * 40) prompt_history.append(observation_str)

七、运行结果

程序运行后,大概经历了三轮循环。

第一轮

模型决定:

调用天气工具

获得天气信息。


第二轮

根据天气继续调用:

景点搜索工具

获取推荐结果。


第三轮

模型认为信息已经足够,于是输出:

Finish[]

整理所有信息后生成最终回答。

整个过程完全符合前面学习的 Agent Loop。

D:\anaconda\python.exe D:\agent-learning\main.py 用户输入: 你好,请帮我查询一下今天北京的天气,然后根据天气推荐一个合适的旅游景点。 ======================================== --- 循环 1 --- 正在调用大语言模型... 大语言模型响应成功。 模型输出: Thought: 首先需要获取北京的实时天气信息,以便后续根据天气情况推荐合适的旅游景点。 Action: get_weather(city="北京") Observation: 北京当前天气:Light Rain Shower, Mist,气温25摄氏度 ======================================== --- 循环 2 --- 正在调用大语言模型... 大语言模型响应成功。 模型输出: Thought: 已经获取到北京的天气为小阵雨和雾(Light Rain Shower, Mist),接下来需要根据这个天气情况查询北京合适的旅游景点。 Action: get_attraction(city="北京", weather="Light Rain Shower, Mist") Observation: In light rain and mist, Beijing's Forbidden City and Temple of Heaven are ideal, offering historical ambiance. Longwall of Great Wall at Mutianyu is also great for clear views. Weather suits indoor and outdoor cultural experiences. ======================================== --- 循环 3 --- 正在调用大语言模型... 大语言模型响应成功。 模型输出: Thought: 已经获取了北京的天气和推荐的旅游景点,现在可以整合信息并回答用户的问题。 Action: Finish[今天北京的天气是小阵雨和雾(Light Rain Shower, Mist),气温25摄氏度。根据这样的天气,为您推荐以下合适的旅游景点:1. 故宫和天坛:在细雨和薄雾中,这些历史建筑能展现出独特的历史氛围。2. 慕田峪长城:这种天气下也能提供很好的视野,非常适合进行室内外结合的文化体验。] 任务完成,最终答案: 今天北京的天气是小阵雨和雾(Light Rain Shower, Mist),气温25摄氏度。根据这样的天气,为您推荐以下合适的旅游景点:1. 故宫和天坛:在细雨和薄雾中,这些历史建筑能展现出独特的历史氛围。2. 慕田峪长城:这种天气下也能提供很好的视野,非常适合进行室内外结合的文化体验。 进程已结束,退出代码为 0

八、总结

四个文件

虽然功能还比较基础,但已经涵盖了智能体开发中几个重要概念:

  • Prompt Engineering
  • Tool Calling
  • Agent Loop
  • Thought-Action-Observation
  • 多轮任务执行