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)
    
相关推荐
A小辣椒8 小时前
TShark:Wireshark CLI 功能
linux
倔强的石头_11 小时前
《Kingbase护城河》——数据库存储空间全景探测与精细化瘦身实战
数据库
A小辣椒12 小时前
TShark:基础知识
linux
BingoGo12 小时前
PHP 泛型之殇 泛型 RFC 提案被拒绝
后端·php
JaguarJack13 小时前
PHP 泛型之殇 泛型 RFC 提案被拒绝
后端·php
AlfredZhao14 小时前
OCI 明明分配了 200G 系统盘,为什么 df 只看到 30G?
linux·oci
冬奇Lab1 天前
每日一个开源项目(第134篇):Zvec - 阿里开源的嵌入式向量数据库,向量搜索界的 SQLite
数据库·人工智能·llm
AlfredZhao1 天前
vi 删除指定范围的行,不用再反复按 dd
linux·vi
用户3074596982071 天前
PHP 扩展——从入门到理解
php
ClouGence1 天前
Oracle CDC 架构优化:从主库直连到 DataGuard 备库同步
数据库·后端·oracle