Hello-Agents学习笔记--旅行助手智能体案例

1.3 动手体验:5 分钟实现第一个智能体

在前面的小节,我们学习了智能体的任务环境、核心运行机制以及 Thought-Action-Observation 交互范式。理论知识固然重要,但最好的学习方式是亲手实践。在本节中,我们将引导您使用几行简单的 Python 代码,从零开始构建一个可以工作的智能旅行助手。这个过程将遵循我们刚刚学到的理论循环,让您直观地感受到一个智能体是如何"思考"并与外部"工具"互动的。让我们开始吧!

在本案例中,我们的目标是构建一个能处理分步任务的智能旅行助手。需要解决的用户任务定义为:"你好,请帮我查询一下今天北京的天气,然后根据天气推荐一个合适的旅游景点。"要完成这个任务,智能体必须展现出清晰的逻辑规划能力。它需要先调用天气查询工具,并将获得的观察结果作为下一步的依据。在下一轮循环中,它再调用景点推荐工具,从而得出最终建议。

1.3.1 准备工作

为了能从 Python 程序中访问网络 API,我们需要一个 HTTP 库。requests是 Python 社区中最流行、最易用的选择。tavily-python是一个强大的 AI 搜索 API 客户端,用于获取实时的网络搜索结果,可以在官网注册后获取 API。openai是 OpenAI 官方提供的 Python SDK,用于调用 GPT 等大语言模型服务。请先通过以下命令安装它们::

bash 复制代码
pip install requests tavily-python openaiCopy to clipboardErrorCopied

(1)指令模板

驱动真实 LLM 的关键在于提示工程(Prompt Engineering) 。我们需要设计一个"指令模板",告诉 LLM 它应该扮演什么角色、拥有哪些工具、以及如何格式化它的思考和行动。这是我们智能体的"说明书",它将作为system_prompt传递给 LLM。

复制代码
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[最终答案] 格式结束

请开始吧!
"""Copy to clipboardErrorCopied

(2)工具 1:查询真实天气

我们将使用免费的天气查询服务 wttr.in,它能以 JSON 格式返回指定城市的天气数据。下面是实现该工具的代码:

python 复制代码
import requests

def get_weather(city: str) -> str:
    url = f"https://wttr.in/{city}?format=j1"
    try:
        response = requests.get(url, timeout=10, headers={
            "User-Agent": "Mozilla/5.0"
        })
        response.raise_for_status()

        data = response.json()
        # print("天气接口原始返回:", data)  # 先看真实结构

        if "current_condition" not in data or not data["current_condition"]:
            return f"错误:天气接口返回结构异常,未找到 current_condition。原始返回: {data}"

        current_condition = data["current_condition"][0]
        weather_desc = current_condition.get("weatherDesc", [{}])[0].get("value", "未知")
        temp_c = current_condition.get("temp_C", "未知")

        return f"{city}当前天气:{weather_desc},气温{temp_c}摄氏度"

    except requests.exceptions.RequestException as e:
        return f"错误:查询天气时遇到网络问题 - {e}"
    except ValueError as e:
        return f"错误:天气接口返回的不是合法JSON - {e}"
    except Exception as e:
        return f"错误:查询天气时出现未知问题 - {e}"

(3)工具 2:搜索并推荐旅游景点

我们将定义一个新工具 search_attraction,它会根据城市和天气状况,互联网上搜索合适的景点:

python 复制代码
import os
from tavily import TavilyClient

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}"Copy to clipboardErrorCopied

最后,我们将所有工具函数放入一个字典,供主循环调用:

python 复制代码
# 将所有工具函数放入一个字典,方便后续调用
available_tools = {
    "get_weather": get_weather,
    "get_attraction": get_attraction,
}Copy to clipboardErrorCopied

1.3.2 接入大语言模型

当前,许多 LLM 服务提供商(包括 OpenAI、Azure、以及众多开源模型服务框架如 Ollama、vLLM 等)都遵循了与 OpenAI API 相似的接口规范。这种标准化为开发者带来了极大的便利。智能体的自主决策能力来源于 LLM。我们将实现一个通用的客户端 OpenAICompatibleClient,它可以连接到任何兼容 OpenAI 接口规范的 LLM 服务。

python 复制代码
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 "错误:调用语言模型服务时出错。"Copy to clipboardErrorCopied

要实例化此类,您需要提供三个信息:API_KEYBASE_URLMODEL_ID,具体值取决于您使用的服务商(如 OpenAI 官方、Azure、或 Ollama 等本地模型),如果暂时没有渠道获取,可以参考 环境配置

1.3.3 执行行动循环

下面的主循环将整合所有组件,并通过格式化后的 Prompt 驱动 LLM 进行决策。

python 复制代码
import re

# --- 1. 配置LLM客户端 ---
# 请根据您使用的服务,将这里替换成对应的凭证和地址
API_KEY = "YOUR_API_KEY"
BASE_URL = "YOUR_BASE_URL"
MODEL_ID = "YOUR_MODEL_ID"
TAVILY_API_KEY="YOUR_Tavily_KEY"
os.environ['TAVILY_API_KEY'] = "YOUR_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"):
        finish_match = re.match(r"Finish\[(.*)\]$", action_str, re.DOTALL)
        if not finish_match:
            observation = "错误: Finish 格式解析失败。请确保使用 Finish[最终答案]。"
            observation_str = f"Observation: {observation}"
            print(f"{observation_str}\n" + "="*40)
            prompt_history.append(observation_str)
            continue

        final_answer = finish_match.group(1).strip()
        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)Copy to clipboardErrorCopied

通过以上步骤,我们构建了一个完整的、由真实 LLM 驱动的智能体。其核心在于"工具"和"提示工程"的结合,这正是当前主流智能体框架(如 LangChain、LlamaIndex 等)的设计精髓。

1.3.4 运行案例分析

以下输出完整地展示了一个成功的智能体执行流程。通过对这个三轮循环的分析,我们可以清晰地看到智能体解决问题的核心能力。

bash 复制代码
用户输入: 你好,请帮我查询一下明天哈尔滨的天气,然后根据天气推荐一个合适的旅游景点。
========================================
--- 循环 1 ---

正在调用大语言模型...
大语言模型响应成功。
模型输出:
Thought: 用户需要查询哈尔滨明天的天气,然后根据天气推荐旅游景点。我需要先调用get_weather工具查询哈尔滨的天气情况。
Action: function_name=get_weather(city="哈尔滨")

Observation: 明天哈尔滨的天气主要是晴天(Sunny),气温在-10°C到4°C之间
   
========================================
--- 循环 2 ---

正在调用大语言模型...
大语言模型响应成功。
模型输出:
Thought: 从天气数据中可以看到,明天(2026-03-20)哈尔滨的天气主要是晴天(Sunny),气温在-10°C到4°C之间,降水概率为0%。现在我需要根据这个天气情况调用get_attraction工具来推荐合适的旅游景点。
Action: function_name=get_attraction(city="哈尔滨", weather="Sunny")

Observation: 在哈尔滨阳光明媚的天气下,冰雪大世界和太阳岛风景区是最值得去的旅游景点。
========================================
--- 循环 3 ---

正在调用大语言模型...
大语言模型响应成功。
模型输出:
Thought: 我已经获取了哈尔滨明天的天气信息(晴天,气温-10°C到4°C)和推荐的旅游景点(冰雪大世界和太阳岛风景区)。现在我有足够的信息来回答用户的问题,可以结束任务了。
Action: Finish[明天哈尔滨的天气是晴天,气温在-10°C到4°C之间,降水概率为0%,非常适合户外活动。根据这样的天气条件,我为您推荐以下旅游景点:

1. **冰雪大世界**:哈尔滨的标志性景点,在晴朗的天气下,冰雕和雪雕在阳光下闪闪发光,景色格外壮观。

2. **太阳岛风景区**:阳光明媚的天气非常适合在太阳岛散步、拍照,欣赏松花江畔的美景。

温馨提示:虽然天气晴朗,但气温较低,请注意保暖,穿戴好羽绒服、帽子、手套等防寒装备。祝您旅途愉快!]

任务完成,最终答案: 明天哈尔滨的天气是晴天,气温在-10°C到4°C之间,降水概率为0%,非常适合户外活动。根据这样的天气条件,我为您推荐以下旅游景点:

1. **冰雪大世界**:哈尔滨的标志性景点,在晴朗的天气下,冰雕和雪雕在阳光下闪闪发光,景色格外壮观。

2. **太阳岛风景区**:阳光明媚的天气非常适合在太阳岛散步、拍照,欣赏松花江畔的美景。

温馨提示:虽然天气晴朗,但气温较低,请注意保暖,穿戴好羽绒服、帽子、手套等防寒装备。祝您旅途愉快!

这个简单的旅行助手案例,集中演示了基于Thought-Action-Observation范式的智能体所具备的四项基本能力:任务分解、工具调用、上下文理解和结果合成。正是通过这个循环的不断迭代,智能体才得以将一个模糊的用户意图,转化为一系列具体、可执行的步骤,并最终达成目标。

相关推荐
Ho1aAs2 小时前
『OpenClaw安全』CVE-2026-25253:ClawJacked One-Click RCE
安全·web安全·网络安全·ai·智能体·agent安全·openclaw
薛定e的猫咪2 小时前
AI 时代前端框架选型:React 核心原理与 SocialVibe 项目实战解析
人工智能·react.js·前端框架
王者引擎2 小时前
CozeLoop简化AI代理开发和运营
人工智能
星爷AG I2 小时前
15-3 前庭觉(AGI基础理论)
人工智能·agi
Ahtacca2 小时前
实测分享:AI 生成 Python 科技海报时,如何避免文字乱码问题
人工智能·科技
杨超越luckly2 小时前
AI Agent应用指南 :自动化构建品牌数据库:提示词 + API + 结构化输出
大数据·数据库·人工智能·自动化·ai agent
Takoony2 小时前
Nanobot 源码深度剖析:一个轻量级 AI Agent 框架的架构设计与实现原理
人工智能
物联网软硬件开发-轨物科技2 小时前
【轨物洞见】定义“视觉语音时代”:轨物科技重塑人机交互新范式
人工智能·科技·人机交互
DX_水位流量监测2 小时前
德希科技供水水质多参数 PLC 一体机
网络·人工智能·深度学习·水质监测·水质传感器·水质厂家·供水水质监测