• LLM大语言模型(十二):关于ChatGLM3-6B不兼容Langchain 的Function Call


     

    背景

    基于本地的ChatGLM3-6B直接开发LangChain Function Call应用,发现其输出的action和action_input非常不稳定。

    表现为生成的JSON格式回答非常容易出现不规范的情况,导致LangChain的Agent执行报错,或者进入死循环。

    ChatGLM3-6B不兼容Langchain 的Function Call

    Langchain 作为最主流的大模型中间件开源框架,备受广大开发者的认可。

    Langchain中具有一套完整的 Agent 思维,包括灵活,简单的Function Call开发框架。

    ChatGLM3-6B 模型在同量级模型中有出色的Function Call能力。

    但遗憾的是,其训练过程并没有与Langchain进行原生对齐。

    这导致如果直接使用Langchian框架,将会遇到以下问题:

    • 无法载入ChatGLM3-6B模型,Langchain中的 LLM模型 目前仅支持在线的几个主流模型,例如ChatGPT,Bard,Claude等

    • 无法正常使用 Agent 的 Function Call 功能,ChatGLM3-6B的截断点与 Langchain 支持的并不同。

    • 提示词不同,使用 Langchain 封装的 Agent 提示词完全无法胜任ChatGLM3-6B 的 Function Call 任务。

    将GLM模型接入Langchain

    首先,要解决第一个痛点:ChatGLM3-6B 模型能够被 Langchain 读入并执行。

    那么,我们就需要基于Langchain的LLM类完成ChatGLM3-6B的模型实现。

    封装自定义LLM

    1. class ChatGLM3(LLM):
    2. max_token: int = 8192
    3. do_sample: bool = False
    4. temperature: float = 0.8
    5. top_p = 0.8
    6. tokenizer: object = None
    7. model: object = None
    8. history: List = []
    9. tool_names: List = []
    10. has_search: bool = False
    11. def __init__(self):
    12. super().__init__()
    13. @property
    14. def _llm_type(self) -> str:
    15. return "ChatGLM3"

    接着,我们要写入读入模型的方法,这与 Langchain 支持的在线模型不同,这里使用 Huggingface 的方式进行读入。

    1. def load_model(self, model_name_or_path=None):
    2. model_config = AutoConfig.from_pretrained(
    3. model_name_or_path,
    4. trust_remote_code=True
    5. )
    6. self.tokenizer = AutoTokenizer.from_pretrained(
    7. model_name_or_path,
    8. trust_remote_code=True
    9. )
    10. self.model = AutoModel.from_pretrained(
    11. model_name_or_path, config=model_config, trust_remote_code=True
    12. ).half().cuda()

    按LangChain的格式构建Tool

    其中包括工具的name,description,params等信息,可以被LangChain自动识别出来,加入到prompt中

    1. import abc
    2. from typing import Type
    3. from langchain.tools import BaseTool
    4. from pydantic import BaseModel, Field
    5. class CalculatorInput(BaseModel):
    6. calculation: str = Field(description="calculation to perform")
    7. class Calculator(BaseTool, abc.ABC):
    8. name = "Calculator"
    9. description = "Useful for when you need to calculate math problems"
    10. args_schema: Type[BaseModel] = CalculatorInput
    11. def __init__(self):
    12. super().__init__()
    13. def _run(self, calculation: str) -> str:
    14. calculation = calculation.replace("^", "**")
    15. if "sqrt" in calculation:
    16. calculation = calculation.replace("sqrt", "math.sqrt")
    17. elif "log" in calculation:
    18. calculation = calculation.replace("log", "math.log")
    19. return eval(calculation)

    从prompt中抽取tool信息并转换为ChatGLM能识别的结构 

    然后,就是非常重要的一环。由于我们的工具调用和观察抓取与 Langchain 并不相同,我们需要对 Langchain 的提示词进行修改,并配上我们的提示词。

    • 我们先从AgentType.STRUCTURED_CHAT_ZERO_SHOT_REACT_DESCRIPTION模板中截取到You have access to the following tools:\n\n")的关键词,并在合理插入已经注册的工具类型。

    1. tool_prompts = prompt.split(
    2. "You have access to the following tools:\n\n")[1].split("\n\nUse a json blob")[0].split("\n")
    3. tool_names = [tool.split(":")[0] for tool in tool_prompts]
    4. self.tool_names = tool_name

    增加Observation结构

    由于ChatGLM3-6B拥有Observation角色,这与Langchain中原本设定的Observation截断并不相同,因此,在这里,我们需要做提取,在这段代码中,我们需要对原本Langchain中的Observation进行截断,并补

    充上我们的工具观察的结果。

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

    将ChatGLM生成的结果转换为LangChain能识别的结构

    在这,我们还需要对执行工具进行截断和填充,使得其能满足ChatGLM3-6B的思维模式

    1. def _extract_tool(self):
    2. if len(self.history[-1]["metadata"]) > 0:
    3. metadata = self.history[-1]["metadata"]
    4. content = self.history[-1]["content"]
    5. if "tool_call" in content:
    6. for tool in self.tool_names:
    7. if tool in metadata:
    8. input_para = content.split("='")[-1].split("'")[0]
    9. action_json = {
    10. "action": tool,
    11. "action_input": input_para
    12. }
    13. self.has_search = True
    14. return f"""
    15. Action:
    16. ```
    17. {json.dumps(action_json, ensure_ascii=False)}
    18. ```"""
    19. final_answer_json = {
    20. "action": "Final Answer",
    21. "action_input": self.history[-1]["content"]
    22. }
    23. self.has_search = False
    24. return f"""
    25. Action:
    26. ```
    27. {json.dumps(final_answer_json, ensure_ascii=False)}
    28. ```"""

    由于ChatgGLM3-6B的思维方式并没有Action: 字段,而这是langchain的截断点,因此,我们需要对其进行补充,使得Langchain能知道,此时模型进入调用工具阶段。

    最后,我们要基于Langchain的构造,重写_call函数,包括历史记录,提示词等拼接内容。

    1. def _call(self, prompt: str, history: List = [], stop: Optional[List[str]] = ["<|user|>"]):
    2. if not self.has_search:
    3. self.history, query = self._tool_history(prompt)
    4. else:
    5. self._extract_observation(prompt)
    6. query = ""
    7. _, self.history = self.model.chat(
    8. self.tokenizer,
    9. query,
    10. history=self.history,
    11. do_sample=self.do_sample,
    12. max_length=self.max_token,
    13. temperature=self.temperature,
    14. )
    15. response = self._extract_tool()
    16. history.append((prompt, response))
    17. return response

    使用接入了Langchain的ChatGLM3-6B模型

    在完成了上述工作之后,我们就已经拥有了支持Langchain的ChatGLM3-6B模型,我们在main.py中对其进行了简单调用

    1. if __name__ == "__main__":
    2. llm = ChatGLM3()
    3. llm.load_model(MODEL_PATH)
    4. prompt = hub.pull("hwchase17/structured-chat-agent")
    5. # for single parameter without history
    6. tools = [Calculator()]
    7. agent = create_structured_chat_agent(llm=llm, tools=tools, prompt=prompt)
    8. agent_executor = AgentExecutor(agent=agent, tools=tools)
    9. ans = agent_executor.invoke({"input": "34 * 34"})
    10. print(ans)

    注意事项

    到此为止,你已经简单实现了使用LangChain调用ChatGLM3-6B模型来实现工具调用和其他基本用法。但是,在更多探索之前,请一定要看这部分的内容。这将能为你之后的开发减少不必要的麻烦。

    使用LLMChain的工具

    在官方的实现方案中,暂时不能解决在工具中仍然需要调用正常的LLMChain的操作,这意味着你在工具的设计中不能再次调用大模型进行更多操作,例如参数解析等,典型的错误例子为

    LLMMathChain

    如果使用官方Demo调用这个工具,则必然遇到以下错误:

    line 120, in _process_llm_result

    raise ValueError(f"unknown format from LLM: {llm_output}")

    ValueError: unknown format from LLM: Action:

    {"action": "Calculator", "action_input": "23*23"}

    这是因为在模型构建的过程中,模型会解析到tools,而在tools中的常规调用模型并没有修改模型的系统提示词,模型还会尝试调用工具,这在常规的Chain中是错误的。

    无效的参数和固定的参数

    • ChatGLM3-6B必须使用结构化的Agent,在Langchain中,我们只适配了AgentType.STRUCTURED_CHAT_ZERO_SHOT_REACT_DESCRIPTION

    • 如果使用LLMSingleActionAgent来构建,stop参数无效。

    • 使用Tool.from_function时,args_schema无效。

    • 每次创建一个新的Tools,都必须有同名的yaml,或者自己实现传入格式化的工具说明。

    兼容OpenAI API

    官方的OpenAI API格式的demo,目前无法适配Langchain的工具。

     参考

    1. GitHub - THUDM/ChatGLM-6B: ChatGLM-6B: An Open Bilingual Dialogue Language Model | 开源双语对话语言模型
    2.  LLM大语言模型(十一):基于自定义的ChatGLM3-6B构建LangChain的chain-CSDN博客
    3. LLM大语言模型(十):LangChain自定义Agent使用自定义的LLM-CSDN博客
    4. LLM大语言模型(九):LangChain封装自定义的LLM-CSDN博客
    5. LLM大语言模型(八):ChatGLM3-6B使用的tokenizer模型BAAI/bge-large-zh-v1.5-CSDN博客
    6. LLM大语言模型(七):部署ChatGLM3-6B并提供HTTP server能力
    7. LLM大语言模型(四):在ChatGLM3-6B中使用langchain_chatglm3-6b langchain-CSDN博客
    8. LLM大语言模型(一):ChatGLM3-6B本地部署-CSDN博客
  • 相关阅读:
    【React】React获取URL参数,根据URL参数隐藏页面元素
    硬技能之上的软技巧(三)
    小程序之如何学习一个新的知识,首先就是通过官网进行讲解!!(2)
    Debian的系统启动过程
    Ubuntu下Qt使用QProcess执行shell脚本并实时输出的标准方法
    12.(Python数模)(相关性分析一)相关系数矩阵
    【神经网络】梯度消失与梯度爆炸问题
    VirtualBox设置共享文件夹步骤及遇到的问题
    PHP项目学习笔记-萤火商城https://www.yiovo.com/doc
    LeetCode | 168.Excel表列名称
  • 原文地址:https://blog.csdn.net/hugo_lei/article/details/138095412