赞
踩
本文搭建了一个完整的LangChain的Agent,调用本地启动的ChatGLM3-6B的HTTP server。
为后续的RAG做好了准备。
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,会导致接口调用失败。
参考:
LLM大语言模型(一):ChatGLM3-6B本地部署_llm3 部署-CSDN博客
自定义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
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
使用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
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
设置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)
![](https://csdnimg.cn/release/blogv2/dist/pc/img/newCodeMoreWhite.png)
调用结果:
> 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度,天气状况适中。'}
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。