使用智普清言的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)
相关推荐
大模型教程12 小时前
8GB显存笔记本能跑多大AI模型?这个计算公式90%的人都不知道!
程序员·llm·agent
大模型教程13 小时前
大模型应用开发到底有多赚钱?看完这5个真实案例,你会惊掉下巴
程序员·llm·agent
AI大模型13 小时前
别乱装!Ollama×DeepSeek×AnythingLLM一键本地AI知识库,快人10倍
程序员·llm·agent
NAGNIP14 小时前
大模型框架性能优化策略:延迟、吞吐量与成本权衡
算法
聚客AI15 小时前
🌟大模型为什么产生幻觉?预训练到推理的漏洞全揭秘
人工智能·llm·掘金·日新计划
美团技术团队15 小时前
LongCat-Flash:如何使用 SGLang 部署美团 Agentic 模型
人工智能·算法
Fanxt_Ja20 小时前
【LeetCode】算法详解#15 ---环形链表II
数据结构·算法·leetcode·链表
侃侃_天下20 小时前
最终的信号类
开发语言·c++·算法
茉莉玫瑰花茶20 小时前
算法 --- 字符串
算法
博笙困了20 小时前
AcWing学习——差分
c++·算法