Agent开发:自动查天气+景区推荐

参考文章:

前言

datawhalechina/hello-agents: 📚 《从零开始构建智能体》------从零开始的智能体原理与实践教程

其实这个教程已经讲述的非常详细了,这了这是添加一些自己的理解。

# 制作工具

这里制作了两个mcp工具,get_weather和get_attraction,功能分别是查询天气和根据天气获得景区的推荐。

python 复制代码
import requests
import pprint

# 定义第一个工具:查询天气工具
def get_weather(city:str)-> str:
    # 获取查询查询的链接:https://wttr.in/
    url = f"https://wttr.in/{city}?format=j1"

    try:
        response = requests.get(url)

        response.raise_for_status()

        # 解析数据
        data = response.json()

        current_condition = data['current_condition'][0]  # 返回字典类型,根据"键"获得需要的数据
        weather = current_condition["weatherDesc"][0]["value"]  # 获得天气
        temp_c = current_condition["temp_C"]
 

        #pprint.pprint(current_condition)

        return f"当前{city}地区的天气是:{weather},气温是:{temp_c}摄氏度!"

    except requests.exceptions.RequestException as e:
        return f"错误:查询天气是遇到网络问题-{e}"
    except (KeyError,IndexError) as e:
        return f"错误:解析天气数据失败!-{e}"
    


# city = "桂林"
# info = get_weather(city)
# print(info)


    
python 复制代码
# 定义第二个工具:搜索推荐旅游景点工具
# 这是一个基于大模型的工具,和后面负责调度,自行规划判断的主大模型要区分开来
import os
from openai import OpenAI

def get_attraction(city:str,weather:str)->str:
    api_key = os.getenv("ARK_API_KEY")
    client = OpenAI(
        base_url="https://ark.cn-beijing.volces.com/api/v3",
        api_key=api_key,
    )
    problem = f"你是一个旅游助手,请你为我推荐:{city}在{weather}天气下最值得去的至多3个旅游景点推荐及理由,回答要精简,结构化输出!"
    # 纯文本对话请求(最简版本)
    try:
        response = client.responses.create(
            model="doubao-seed-2-0-pro-260215",
            # 直接传入纯文本字符串即可
            input= problem
        )
        # print(type(response))
        # print(response)

        return response.output_text

    except Exception as e:
        return f"出现错误-{e}!"
    
    


# city = "桂林"
# weather = get_weather(city)
# result = get_attraction(city,weather)  # 获得大模型的返回结果
# print(result)

原本的教程中获得景区推荐的函数是基于tavily实现的,这个需要注册他们公司的账号,然后领取免费的额度,然后获得景区推荐。我这里是另外基于豆包大模型获得景区推荐的。本质上算是封装乘一个工具吧。

将制作好的函数工具封装成字典,后续通过字典进行函数映射,来调用对应的工具

python 复制代码
# 将设计好的工具放入字典,方便后续大模型自主判断使用
available_tools = {"get_weather":get_weather,"get_attraction":get_attraction}

# 封装一个统一的大模型调用接口

python 复制代码
# 定义一个通用的客户端类,避免每次调用大模型重复初始化工作

class OpenAIClient:
    def __init__(self,model,api_key,base_url):
        self.model = model
        self.client = OpenAI(api_key=api_key,base_url=base_url)
    
    def generate(self,prompt,system_prompt):
        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:
            return f"调用大模型API时发生报错!-{e}"

# 通过提供三个信息实例化对象
'''
model_id
api_key
base_url
'''

后续通过实例化,来和大模型进行交互。

# 创建一个系统提示词

这个是关键,指定了大模型的输出规范,交代了大模型可以使用的工具函数

python 复制代码
AGENT_SYSTEM_PROMPT = """
你是一个智能旅行助手。你的任务是分析用户的请求,并使用可用工具一步步地解决问题。

# 可用工具:
- `get_weather(city)`: 查询指定城市的实时天气。
- `get_attraction(city,weather)`: 根据城市和天气搜索推荐的旅游景点。

# 输出格式要求:
你的每次回复必须严格遵循以下格式,包含一对Thought和Action:

Thought: [你的思考过程和下一步计划]
Action: [你要执行的具体行动]

Action的格式必须是以下之一:
1. 调用工具:function_name(arg_name="arg_value")
2. 结束任务:Finish[最终答案]

# 重要提示:
- 每次只输出一对Thought-Action
- Action必须在同一行,不要换行
- 当收集到足够信息可以回答用户问题时,必须使用 Action: Finish[最终答案] 格式结束

请开始吧!
"""

# 主函数

这里简单谈一下这一部分实现的思路,总体看下来还是比较妙的。他没有基于专业的如langchain这样的agent开发框架,而是从底层一步一步实现的。

**对话历史的管理:**每次对话的内容都会存储在一个列表中,这样大模型就能够知道自己当前工作的状态,分析到哪一步了。所以从最终输出结果来看,整个大模型的工作过程就是一步一步思考的。

**MCP工具的使用:**大模型不能够使用工具,但他知道应该使用哪一个工具。所以通过规范化输出,提取出使用的函数,具体函数的操作还是通过python后端调用函数实现的。此外,这里是通过正则表达式的方式,提取出关键的函数名称和形参的,通过这样的方式准确调用工具函数。

python 复制代码
# 实例化对象,调用大模型

import re # 正则表达式

MODEL_ID = "doubao-seed-2-0-pro-260215"
API_KEY_MAIN = os.getenv("ARK_API_KEY")
BASE_URL = "https://ark.cn-beijing.volces.com/api/v3"

llm = OpenAIClient(
    model = MODEL_ID,
    api_key= API_KEY_MAIN,
    base_url= BASE_URL
)

user_prompt = "我今天想去桂林玩,是否值得去,如果去的话可以去哪些地方玩?"

prompt_history = [f"用户请求:{user_prompt}"]  # 存储用户的提问

print(f"用户输入:{user_prompt}\n"+"="*40)

# 运行主程序

for  i  in range(5):
    print(f"-----循环{i+1}-----\n")

    # print("*"*40)
    # print("查看history_prompt中的情况:\n")
    

    full_prompt = "\n".join(prompt_history)  # 将历史内容拼接,因为目前没有记忆功能

    # print(full_prompt)
    # print("*"*40)

    llm_output = llm.generate(full_prompt,system_prompt=AGENT_SYSTEM_PROMPT) 
    print("llm_output的情况是:之后match在这个基础上进行匹配",llm_output)

    
    # 这里因为没有使用langchain这样的框架,所以其实只能够通过正则表达式的方式来调用mcp工具,并不能自动控制调用工具
    match = re.search(r'(Thought:.*?Action:.*?)(?=\n\s*(?:Thought:|Action:|Observation:)|\Z)', llm_output, re.DOTALL)

    # print("*"*40)
    # print("match匹配的结果为:\n")
    # print(match)
    # print("*"*40)

    # 有一点不太理解这里还有截断的必要吗  答:match匹配的结果不干净,这里相当与是进一步整理,使得输出结果更加结构化,规范
    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)

    # print("经过处理之后match的情况!如上")



    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)
        finish_match = re.match(r"Finish\[(.*?)\]", action_str, re.DOTALL)
        if finish_match:
            final_answer = finish_match.group(1)
        else:
            final_answer = action_str
        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)
    
相关推荐
Yupureki1 小时前
《Linux网络编程》9.数据链路层原理
linux·运维·服务器·网络
顶点多余1 小时前
Socket编程实现UDP通信
linux·网络协议·udp
切糕师学AI1 小时前
Remmina:Linux 平台的全能远程桌面客户端详解
linux·运维·远程控制·远程桌面·remmina
会编程的土豆1 小时前
MySQL 多表查询
开发语言·数据库·python·mysql
2403_883261091 小时前
PHP调用Codex处理PHP特定语法【操作】
jvm·数据库·python
dualven_in_csdn2 小时前
【assist】 需要用到的方法
linux·运维·服务器
minji...2 小时前
Linux 网络基础(二)HTTP协议,域名,URL,URI,认识HTTP的请求和响应
linux·服务器·网络·网络协议·http·tcp
四方云2 小时前
MySQL 迁移到 Apache Doris 生产实践:从评估到落地的完整指南
数据库·mysql·apache
跨境技工小黎2 小时前
亚马逊数据抓取怎么做?2026最新实战教程
java·大数据·数据库