LLM大语言模型(十五):LangChain的Agent中使用自定义的ChatGLM,且底层调用的是remote的ChatGLM3-6B的HTTP服务

背景

本文搭建了一个完整的LangChain的Agent,调用本地启动的ChatGLM3-6B的HTTP server。

为后续的RAG做好了准备。

增加服务端role:observation

ChatGLM3的官方demo:openai_api_demo目录

api_server.py文件

class ChatMessage(BaseModel):
    # role: Literal["user", "assistant", "system", "function"]
    role: Literal["user", "assistant", "system", "function","observation"]
    content: str = None
    name: Optional[str] = None
    function_call: Optional[FunctionCallResponse] = None

修改role列表,增加了“observation”。

这是因为LangChain的Agent执行过程,是ReAct模式,在执行完tool调用后,会生成一个observation角色的消息。

在将LangChain的prompt转换为ChatGLM3的prompt时,也保留了observation角色,但是在服务启动时,接口允许的role却没有observation,会导致接口调用失败。

ChatGLM3-6B 本地HTTP服务启动

参考:

LLM大语言模型(一):ChatGLM3-6B本地部署_llm3 部署-CSDN博客

自定义LLM

自定义LLM内部访问的是HTTP server。

将LangChain Agent的prompt转换为ChatGLM3能识别的prompt。

prompt转换参考:LLM大语言模型(十三):ChatGLM3-6B兼容Langchain的Function Call的一步一步的详细转换过程记录_langchain+chatglm3-CSDN博客

import ast
import requests
import json
from typing import Any, List, Optional
from langchain.llms.base import LLM
from langchain_core.callbacks import CallbackManagerForLLMRun
from output_parse import getFirstMsg,parse_tool


class MyChatGLM(LLM):
    max_token: int = 8192
    # do_sample: bool = False
    do_sample: bool = True
    temperature: float = 0.8
    top_p = 0.8
    tokenizer: object = None
    model: object = None
    history: List = []
    has_search: bool = False
    model_name: str = "chatglm3-6b"
    url: str = "http://localhost:8000/v1/chat/completions"
    tools: List = []

    # def __init__(self):
    #     super().__init__()

    @property
    def _llm_type(self) -> str:
        return "MyChatGLM"


    def _tool_history(self, prompt: str):
            ans = []

            tool_prompts = prompt.split(
                "You have access to the following tools:\n\n")[1].split("\n\nUse a json blob")[0].split("\n")
            tools_json = []

            for tool_desc in tool_prompts:
                name = tool_desc.split(":")[0]
                description = tool_desc.split(", args:")[0].split(":")[1].strip()
                parameters_str = tool_desc.split("args:")[1].strip()
                parameters_dict = ast.literal_eval(parameters_str)
                params_cleaned = {}
                for param, details in parameters_dict.items():
                    params_cleaned[param] = {'description': details['description'], 'type': details['type']}

                tools_json.append({
                    "name": name,
                    "description": description,
                    "parameters": params_cleaned
                })

            ans.append({
                "role": "system",
                "content": "Answer the following questions as best as you can. You have access to the following tools:",
                "tools": tools_json
            })

            dialog_parts = prompt.split("Human: ")
            for part in dialog_parts[1:]:
                if "\nAI: " in part:
                    user_input, ai_response = part.split("\nAI: ")
                    ai_response = ai_response.split("\n")[0]
                else:
                    user_input = part
                    ai_response = None

                ans.append({"role": "user", "content": user_input.strip()})
                if ai_response:
                    ans.append({"role": "assistant", "content": ai_response.strip()})

            query = dialog_parts[-1].split("\n")[0]
            return ans, query

    def _extract_observation(self, prompt: str):
        return_json = prompt.split("Observation: ")[-1].split("\nThought:")[0]
        self.history.append({
            "role": "observation",
            "content": return_json
        })
        return

    def _extract_tool(self):
        if len(self.history[-1]["metadata"]) > 0:
            metadata = self.history[-1]["metadata"]
            content = self.history[-1]["content"]

            lines = content.split('\n')
            for line in lines:
                if 'tool_call(' in line and ')' in line and self.has_search is False:
                    # 获取括号内的字符串
                    params_str = line.split('tool_call(')[-1].split(')')[0]

                    # 解析参数对
                    params_pairs = [param.split("=") for param in params_str.split(",") if "=" in param]
                    params = {pair[0].strip(): pair[1].strip().strip("'\"") for pair in params_pairs}
                    action_json = {
                        "action": metadata,
                        "action_input": params
                    }
                    self.has_search = True
                    print("*****Action*****")
                    print(action_json)
                    print("*****Answer*****")
                    return f"""
Action: 
```
{json.dumps(action_json, ensure_ascii=False)}
```"""
        final_answer_json = {
            "action": "Final Answer",
            "action_input": self.history[-1]["content"]
        }
        self.has_search = False
        return f"""
Action: 
```
{json.dumps(final_answer_json, ensure_ascii=False)}
```"""

    def _call(self, prompt: str, history: List = [], stop: Optional[List[str]] = ["<|user|>"]):
        if not self.has_search:
            self.history, query = self._tool_history(prompt)
            if self.history[0]:
                self.tools = self.history[0]["tools"]
        else:
            self._extract_observation(prompt)
            query = ""
        print(self.history)
        data = {}
        data["model"] = self.model_name
        data["messages"] = self.history
        data["temperature"] = self.temperature
        data["max_tokens"] = self.max_token
        data["tools"] = self.tools
        resp = self.doRequest(data)
        
        msg = {}
        respjson = json.loads(resp)
        if respjson["choices"]:
            if respjson["choices"][0]["finish_reason"] == 'function_call':
                msg["metadata"] = respjson["choices"][0]["message"]["function_call"]["name"]
            else:
                msg["metadata"] = ''
            msg["role"] = "assistant"
            msg["content"] = respjson["choices"][0]["message"]["content"]

        self.history.append(msg)
        print(self.history)
        response = self._extract_tool()
        history.append((prompt, response))
        return response


    def doRequest(self,payload:dict) -> str:
        # 请求头
        headers = {"content-type":"application/json"}
        # json形式,参数用json
        res = requests.post(self.url,json=payload,headers=headers)
        return res.text

定义tool

使用LangChain中Tool的方式:继承BaseTool

Tool实现方式对prompt的影响,参考:LLM大语言模型(十四):LangChain中Tool的不同定义方式,对prompt的影响-CSDN博客

class WeatherInput(BaseModel):
    location: str = Field(description="the location need to check the weather")


class Weather(BaseTool):
    name = "weather"
    description = "Use for searching weather at a specific location"
    args_schema: Type[BaseModel] = WeatherInput

    def __init__(self):
        super().__init__()

    def _run(self, location: str) -> dict[str, Any]:
        weather = {
            "temperature": "20度",
            "description": "温度适中",
        }
        return weather

LangChain Agent调用

设置Agent使用了2个tool:Calculator() Weather(),看是否能正确调用。

    # Get the prompt to use - you can modify this!
    prompt = hub.pull("hwchase17/structured-chat-agent")
    prompt.pretty_print()
    tools = [Calculator(),Weather()]
    # Choose the LLM that will drive the agent
    # Only certain models support this
    # Choose the LLM to use
    llm = MyChatGLM()

    # Construct the agent
    agent = create_structured_chat_agent(llm, tools, prompt)
    # Create an agent executor by passing in the agent and tools
    agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=True, handle_parsing_errors=True)

    ans = agent_executor.invoke({"input": "北京天气怎么样?"})
    print(ans)

调用结果:

> Entering new AgentExecutor chain...
[{'role': 'system', 'content': 'Answer the following questions as best as you can. You have access to the following tools:', 'tools': [{'name': 'Calculator', 'description': 'Useful for when you need to calculate math problems', 'parameters': {'calculation': {'description': 'calculation to perform', 'type': 'string'}}}, {'name': 'weather', 'description': 'Use for searching weather at a specific location', 'parameters': {'location': {'description': 'the location need to check the weather', 'type': 'string'}}}]}, {'role': 'user', 'content': '北京天气怎么样?\n\n\n (reminder to respond in a JSON blob no matter what)'}]
[{'role': 'system', 'content': 'Answer the following questions as best as you can. You have access to the following tools:', 'tools': [{'name': 'Calculator', 'description': 'Useful for when you need to calculate math problems', 'parameters': {'calculation': {'description': 'calculation to perform', 'type': 'string'}}}, {'name': 'weather', 'description': 'Use for searching weather at a specific location', 'parameters': {'location': {'description': 'the location need to check the weather', 'type': 'string'}}}]}, {'role': 'user', 'content': '北京天气怎么样?\n\n\n (reminder to respond in a JSON blob no matter what)'}, {'metadata': 'weather', 'role': 'assistant', 'content': "weather\n ```python\ntool_call(location='北京')\n```"}]
*****Action*****
{'action': 'weather', 'action_input': {'location': '北京'}}
*****Answer*****

Action:
```
{"action": "weather", "action_input": {"location": "北京"}}
```{'temperature': '20度', 'description': '温度适中'}

[{'role': 'system', 'content': 'Answer the following questions as best as you can. You have access to the following tools:', 'tools': [{'name': 'Calculator', 'description': 'Useful for when you need to calculate math problems', 'parameters': {'calculation': {'description': 'calculation to perform', 'type': 'string'}}}, {'name': 'weather', 'description': 'Use for searching weather at a specific location', 'parameters': {'location': {'description': 'the location need to check the weather', 'type': 'string'}}}]}, {'role': 'user', 'content': '北京天气怎么样?\n\n\n (reminder to respond in a JSON blob no matter what)'}, {'metadata': 'weather', 'role': 'assistant', 'content': "weather\n ```python\ntool_call(location='北京')\n```"}, {'role': 'observation', 'content': "{'temperature': '20度', 'description': '温度适中'}"}]
[{'role': 'system', 'content': 'Answer the following questions as best as you can. You have access to the following tools:', 'tools': [{'name': 'Calculator', 'description': 'Useful for when you need to calculate math problems', 'parameters': {'calculation': {'description': 'calculation to perform', 'type': 'string'}}}, {'name': 'weather', 'description': 'Use for searching weather at a specific location', 'parameters': {'location': {'description': 'the location need to check the weather', 'type': 'string'}}}]}, {'role': 'user', 'content': '北京天气怎么样?\n\n\n (reminder to respond in a JSON blob no matter what)'}, {'metadata': 'weather', 'role': 'assistant', 'content': "weather\n ```python\ntool_call(location='北京')\n```"}, {'role': 'observation', 'content': "{'temperature': '20度', 'description': '温度适中'}"}, {'metadata': '', 'role': 'assistant', 'content': '根据最新的气象数据 
,北京的天气情况如下:温度为20度,天气状况适中。'}]

Action:
```
{"action": "Final Answer", "action_input": "根据最新的气象数据,北京的天气情况如下:温度为20度,天气状况适中。"}
```

> Finished chain.
{'input': '北京天气怎么样?', 'output': '根据最新的气象数据,北京的天气情况如下:温度为20度,天气状况适中。'}

  • 19
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Hugo Lei

赏你了,我的一点心意

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值