实践第一个智能体

跟练手册

一个智能旅行助手:分析用户的请求,并使用可用工具一步步地解决问题。

设计思路:

  • 设计系统prompt,限定LLM输出结构(一对Thought: * Action:*组成的字符串),方便获取到LLM输出字符串时解析Action的内容。
  • 维护一个历史对话的list,每次拼接后喂给LLM。
  • 循环多轮对话,往list里推入observation,observation的内容是解析的LLM输出的Action后面的字符串,需要注意的是如果Action后面是调用工具的指示,需要解析出工具的名称,并将调用工具后获得的结果推入list。
  • 如果Action里包含Finish字符串,整轮对话结束

system prompt

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

请开始吧!
"""

2个可用工具:

  1. get_weather(city: str)查询指定城市天气。
  2. get_attraction(city: str, weather: str)根据城市和天气推荐景点。

get_weather(city: str)查询指定城市天气

使用wttr.in API,一个get请求结果如下方截图所示:

https://wttr.in/%E5%8C%97%E4%BA%AC?format=j1

可以看到可以获取到城市的温度、天气描述等。

import requests

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}"

get_attraction(city: str, weather: str)根据城市和天气推荐景点

使用Tavily Search API搜索并返回优化后的景点推荐

可以在这里尝试一个请求,并观察返回

返回的结果中answer是空的,需要把Include answer选上,include_answer=”basic”,重新请求发现有返回了。

完整的获取推荐的工具函数如下:

def get_attraction(city: str, weather: str) -> str:
    """
    根据城市和天气,使用Tavily Search API搜索并返回优化后的景点推荐。
    """

    # 从环境变量或主程序配置中获取API密钥,API密钥可以在travily官网获取
    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}"

智能体主要结构

# 模拟一个用户的初始输入
user_prompt = "你好,请帮我查询一下今天北京的天气,然后根据天气推荐一个合适的旅游景点。"
# 维护一个历史对话记录list
prompt_history = [f"用户请求: {user_prompt}"]

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)

代码分析:

系统prompt要明确Action规则。

prompt_history 用于存储每次循环得到的observation,observation来自分析LLM给出的Action的结果。

运行结果

Thought展示了LLM本轮主要思考内容,Action在运行顺利的情况下输出调用工具的函数。Observation组成喂给下一轮的主要提示词。

类似文章

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注