使用智普清言的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)
相关推荐
长安er14 小时前
LeetCode 20/155/394/739/84/42/单调栈核心原理与经典题型全解析
数据结构·算法·leetcode·动态规划·
MarkHD15 小时前
智能体在车联网中的应用:第28天 深度强化学习实战:从原理到实现——掌握近端策略优化(PPO)算法
算法
能源系统预测和优化研究15 小时前
【原创代码改进】考虑共享储能接入的工业园区多类型负荷需求响应经济运行研究
大数据·算法
yoke菜籽15 小时前
LeetCode——三指针
算法·leetcode·职场和发展
小高不明16 小时前
前缀和一维/二维-复习篇
开发语言·算法
bin915316 小时前
当AI优化搜索引擎算法:Go初级开发者的创意突围实战指南
人工智能·算法·搜索引擎·工具·ai工具
曹牧17 小时前
Java:Math.abs()‌
java·开发语言·算法
CoovallyAIHub18 小时前
纯视觉的终结?顶会趋势:不会联觉(多模态)的CV不是好AI
深度学习·算法·计算机视觉
CoovallyAIHub18 小时前
一文读懂大语言模型家族:LLM、MLLM、LMM、VLM核心概念全解析
深度学习·算法·计算机视觉
范纹杉想快点毕业18 小时前
嵌入式C语言实战开发详解
linux·运维·算法