使用智普清言的Tools功能实现ToolAgent

大家好,我是雨飞。最近在尝试智普的API实现Agent功能,由于langchain本身的接口只支持OpenAI的function call和tools,因此做了一些修改。

langchain原生的实现,可以看官方的文档,更新太频繁了,就感觉昨天还能用的东西,今天就不行了。

OpenAI tools | ️ Langchain

下面这个是核心的代码,实现了简单的Agent功能,通过调用不同的工具去回答问题。agent_output_parser 函数,自己做了重构和实现。

代码中,实现了两个简单的工具,获取天气、计算两个数的和

python 复制代码
import requests
import datetime
from langchain.agents import tool
from zhipu_llm import ChatZhipuAI
from langchain_core.utils.function_calling import convert_to_openai_tool
from langchain.schema.agent import  AgentFinish
from langchain.prompts import MessagesPlaceholder
from langchain.prompts import ChatPromptTemplate
from agent_output_parser import OpenAIToolsAgentOutputParser,format_to_openai_tool_messages

zhipuai_api_key=""
glm3= "glm-3-turbo"
glm4="glm-4"

chat_zhipu = ChatZhipuAI(
    temperature=0.8,
    api_key=zhipuai_api_key,
    model=glm3
)

@tool
def get_current_temperature(latitude: float, longitude: float):
    """Fetch current temperature for given coordinates."""

    BASE_URL = "https://api.open-meteo.com/v1/forecast"

    # Parameters for the request
    params = {
        'latitude': latitude,
        'longitude': longitude,
        'hourly': 'temperature_2m',
        'forecast_days': 1,
    }

    # Make the request
    response = requests.get(BASE_URL, params=params)

    if response.status_code == 200:
        results = response.json()
    else:
        raise Exception(f"API Request failed with status code: {response.status_code}")

    current_utc_time = datetime.datetime.utcnow()
    time_list = [datetime.datetime.fromisoformat(time_str.replace('Z', '+00:00')) for time_str in
                 results['hourly']['time']]
    temperature_list = results['hourly']['temperature_2m']

    closest_time_index = min(range(len(time_list)), key=lambda i: abs(time_list[i] - current_utc_time))
    current_temperature = temperature_list[closest_time_index]

    return f'The current temperature is {current_temperature}°C'

@tool
def sum_nums(s1: float, s2: float):
    """Sum the result for given inputs."""
    return "The result is {a}".format(a = s1+s2)


prompt = ChatPromptTemplate.from_messages([
    ("system", "You are helpful but sassy assistant"),
    ("user", "{input}"),
    MessagesPlaceholder(variable_name="agent_scratchpad")
])
tools =[get_current_temperature,sum_nums]
zhipu_tools = [convert_to_openai_tool(f) for f in tools]
model = chat_zhipu.bind(tools=zhipu_tools)
chain = prompt | model | OpenAIToolsAgentOutputParser()

def run_agent(user_input):
    intermediate_steps = []
    tool_mappings = {
        "get_current_temperature": get_current_temperature,
        "sum_nums":sum_nums
    }
    while True:
        mes = format_to_openai_tool_messages(intermediate_steps)
        result = chain.invoke({
            "input": user_input,
            "agent_scratchpad": mes
        })
        if isinstance(result, AgentFinish):
            return result
        tool = tool_mappings[result.tool]
        observation = tool.run(result.tool_input)
        intermediate_steps.append((result, observation))
        print((result,observation))

result = run_agent("what is the weather is NewYork?")
print(result)
print(result.messages[0].content)

result2 = run_agent("what is the result of 19+23?")
print(result2)
print(result2.messages[0].content)

agent_output_parser.py 这个类的代码,这个类是参考了 OpenAI 的 functions 的 Agent 类的实现,修改了最后返回的对象。因为是工具调用,最终要返回的是 ToolMessage,而 functions 最终返回的是FunctionMessage。

python 复制代码
# !/usr/bin env python3
import json
from json import JSONDecodeError
from typing import List, Union,Sequence, Tuple

from langchain_core.agents import AgentAction, AgentActionMessageLog, AgentFinish
from langchain_core.exceptions import OutputParserException
from langchain_core.messages import (
    AIMessage,
    BaseMessage,
    ToolMessage
)
from langchain_core.outputs import ChatGeneration, Generation

from langchain.agents.agent import AgentOutputParser


class OpenAIToolsAgentOutputParser(AgentOutputParser):
    """Parses a message into agent action/finish.

    Is meant to be used with OpenAI models, as it relies on the specific
    function_call parameter from OpenAI to convey what tools to use.

    If a function_call parameter is passed, then that is used to get
    the tool and tool input.

    If one is not passed, then the AIMessage is assumed to be the final output.
    """

    @property
    def _type(self) -> str:
        return "openai-functions-agent"

    @staticmethod
    def _parse_ai_message(message: BaseMessage) -> Union[AgentAction, AgentFinish]:
        """Parse an AI message."""
        if not isinstance(message, AIMessage):
            raise TypeError(f"Expected an AI message got {type(message)}")

        function_call = message.additional_kwargs.get("tool_calls", [{}])
        function_call = function_call[0].get("function",{})
        if function_call:
            function_name = function_call["name"]
            try:
                if len(function_call["arguments"].strip()) == 0:
                    # OpenAI returns an empty string for functions containing no args
                    _tool_input = {}
                else:
                    # otherwise it returns a json object
                    _tool_input = json.loads(function_call["arguments"], strict=False)
            except JSONDecodeError:
                raise OutputParserException(
                    f"Could not parse tool input: {function_call} because "
                    f"the `arguments` is not valid JSON."
                )

            # HACK HACK HACK:
            # The code that encodes tool input into Open AI uses a special variable
            # name called `__arg1` to handle old style tools that do not expose a
            # schema and expect a single string argument as an input.
            # We unpack the argument here if it exists.
            # Open AI does not support passing in a JSON array as an argument.
            if "__arg1" in _tool_input:
                tool_input = _tool_input["__arg1"]
            else:
                tool_input = _tool_input

            content_msg = f"responded: {message.content}\n" if message.content else "\n"
            log = f"\nInvoking: `{function_name}` with `{tool_input}`\n{content_msg}\n"
            return AgentActionMessageLog(
                tool=function_name,
                tool_input=tool_input,
                log=log,
                message_log=[message],
            )

        return AgentFinish(
            return_values={"output": message.content}, log=str(message.content)
        )

    def parse_result(
        self, result: List[Generation], *, partial: bool = False
    ) -> Union[AgentAction, AgentFinish]:
        if not isinstance(result[0], ChatGeneration):
            raise ValueError("This output parser only works on ChatGeneration output")
        message = result[0].message
        return self._parse_ai_message(message)

    def parse(self, text: str) -> Union[AgentAction, AgentFinish]:
        raise ValueError("Can only parse messages")

def format_to_openai_tool_messages(
    intermediate_steps: Sequence[Tuple[AgentAction, str]],
) -> List[BaseMessage]:
    """Convert (AgentAction, tool output) tuples into FunctionMessages.

    Args:
        intermediate_steps: Steps the LLM has taken to date, along with observations

    Returns:
        list of messages to send to the LLM for the next prediction

    """
    messages = []

    for agent_action, observation in intermediate_steps:
        messages.extend(_convert_agent_action_to_messages(agent_action, observation))

    return messages

def _convert_agent_action_to_messages(
    agent_action: AgentAction, observation: str
) -> List[BaseMessage]:
    """Convert an agent action to a message.

    This code is used to reconstruct the original AI message from the agent action.

    Args:
        agent_action: Agent action to convert.

    Returns:
        AIMessage that corresponds to the original tool invocation.
    """
    if isinstance(agent_action, AgentActionMessageLog):
        return list(agent_action.message_log) + [
            _create_function_message(agent_action, observation)
        ]
    else:
        return [AIMessage(content=agent_action.log)]


def _create_function_message(
    agent_action: AgentAction, observation: str
) -> ToolMessage:
    """Convert agent action and observation into a function message.
    Args:
        agent_action: the tool invocation request from the agent
        observation: the result of the tool invocation
    Returns:
        FunctionMessage that corresponds to the original tool invocation
    """
    if not isinstance(observation, str):
        try:
            content = json.dumps(observation, ensure_ascii=False)
        except Exception:
            content = str(observation)
    else:
        content = observation
    return ToolMessage(
        tool_call_id=agent_action.tool,
        content=content,
    )

智普大模型调用部分,也就是 zhipu_llm 这个模块的文件,

可以参考这篇文章里的代码,代码比较长,就不再贴了。

雨飞:利用智普大模型进行表格数据问答

另外,也可以使用 langchain 的 AgentExecutor 封装部分代码去实现 Agent 的功能,示例如下:

python 复制代码
from langchain.agents import AgentExecutor
def agent_builder():
    agent = (
        {"input":lambda x:x["input"],
         "agent_scratchpad":lambda  x:format_to_openai_tool_messages(x["intermediate_steps"])
         }
    )| prompt | model | OpenAIToolsAgentOutputParser()

    agent_executor = AgentExecutor(agent=agent,tools=tools,verbose=True)
    return agent_executor

query = "how many letters in the word educa?"
agent = agent_builder()
result = agent.invoke({"input":query})
print(result)
相关推荐
散峰而望30 分钟前
C++数组(三)(算法竞赛)
开发语言·c++·算法·github
q***952235 分钟前
SpringMVC 请求参数接收
前端·javascript·算法
初级炼丹师(爱说实话版)1 小时前
多进程与多线程的优缺点及适用场景总结
算法
hetao17338371 小时前
2025-11-25~26 hetao1733837的刷题记录
c++·算法
历程里程碑1 小时前
各种排序法大全
c语言·数据结构·笔记·算法·排序算法
少许极端1 小时前
算法奇妙屋(十四)-简单多状态dp问题
算法·动态规划·图解算法·简单多状态dp·打家劫舍问题·买卖股票问题全解
大模型教程2 小时前
3 张动图秒懂 A2A 协议:打造高效 Multi-Agent 协同机制
程序员·llm·agent
2301_823438022 小时前
解析论文《复杂海上救援环境中无人机群的双阶段协作路径规划与任务分配》
人工智能·算法·无人机
大模型教程2 小时前
RAG 系统架构设计模式介绍
程序员·llm·agent
embrace992 小时前
【C语言学习】结构体详解
android·c语言·开发语言·数据结构·学习·算法·青少年编程