在语义、数学、推理、代码、知识等不同角度的数据集上测评显示,ChatGLM3-6B-Base 具有在10B以下的基础模型中最强的性能。ChatGLM3-6B采用了全新设计的Prompt格式,除正常的多轮对话外。同时原生支持工具调用(Function Call)、代码执行(Code Interpreter)和Agent任务等复杂场景。本文主要通过天气查询例子介绍了在tool_registry.py中注册新的工具来增强模型能力。

  可以直接调用LangChain自带的工具(比如,ArXiv),也可以调用自定义的工具。LangChain自带的部分工具[2],如下所示:

一.自定义天气查询工具

1.Weather类

  可以参考Tool/Weather.py以及Tool/Weather.yaml文件,继承BaseTool类,重载_run()方法,如下所示:

class Weather(BaseTool):  # 天气查询工具
    name = "weather"
    description = "Use for searching weather at a specific location"

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

    def get_weather(self, location):
        api_key = os.environ["SENIVERSE_KEY"]
        url = f"https://api.seniverse.com/v3/weather/now.json?key={api_key}&location={location}&language=zh-Hans&unit=c"
        response = requests.get(url)
        if response.status_code == 200:
            data = response.json()
            weather = {
                "temperature": data["results"][0]["now"]["temperature"],
                "description": data["results"][0]["now"]["text"],
            }
            return weather
        else:
            raise Exception(
                f"Failed to retrieve weather: {response.status_code}")

    def _run(self, para: str) -> str:
        return self.get_weather(para)

2.weather.yaml文件

  weather.yaml文件内容,如下所示:

name: weather
description: Search the current weather of a city
parameters:
  type: object
  properties:
    city:
      type: string
      description: City name
  required:
    - city

二.自定义天气查询工具调用

  自定义天气查询工具调用,在main.py中导入Weather工具。如下所示:

run_tool([Weather()], llm, [
    "今天北京天气怎么样?",
    "What's the weather like in Shanghai today",
])

  其中,run_tool()函数实现如下所示:

def run_tool(tools, llm, prompt_chain: List[str]):
    loaded_tolls = []  # 用于存储加载的工具
    for tool in tools:  # 逐个加载工具
        if isinstance(tool, str):
            loaded_tolls.append(load_tools([tool], llm=llm)[0])  # load_tools返回的是一个列表
        else:
            loaded_tolls.append(tool)  # 如果是自定义的工具,直接添加到列表中
    agent = initialize_agent(  # 初始化agent
        loaded_tolls, llm,
        agent=AgentType.STRUCTURED_CHAT_ZERO_SHOT_REACT_DESCRIPTION,  # agent类型:使用结构化聊天的agent
        verbose=True,
        handle_parsing_errors=True
    )
    for prompt in prompt_chain:  # 逐个输入prompt
        agent.run(prompt)

1.load_tools()函数

  根据工具名字加载相应的工具,如下所示:

def load_tools(
    tool_names: List[str],
    llm: Optional[BaseLanguageModel] = None,
    callbacks: Callbacks = None,
    **kwargs: Any,
) -> List[BaseTool]:

2.initialize_agent()函数

  根据工具列表和LLM加载一个agent executor,如下所示:

def initialize_agent(
    tools: Sequence[BaseTool],
    llm: BaseLanguageModel,
    agent: Optional[AgentType] = None,
    callback_manager: Optional[BaseCallbackManager] = None,
    agent_path: Optional[str] = None,
    agent_kwargs: Optional[dict] = None,
    *,
    tags: Optional[Sequence[str]] = None,
    **kwargs: Any,
) -> AgentExecutor:

  其中,agent默认为AgentType.ZERO_SHOT_REACT_DESCRIPTION。本文中使用为AgentType.STRUCTURED_CHAT_ZERO_SHOT_REACT_DESCRIPTION,一种为聊天模型优化的zero-shot react agent,该agent能够调用具有多个输入的工具。

3.run()函数

  执行链的便捷方法,这个方法与Chain.__call__之间的主要区别在于,这个方法期望将输入直接作为位置参数或关键字参数传递,而Chain.__call__期望一个包含所有输入的单一输入字典。如下所示:

def run(
    self,
    *args: Any,
    callbacks: Callbacks = None,
    tags: Optional[List[str]] = None,
    metadata: Optional[Dict[str, Any]] = None,
    **kwargs: Any,
) -> Any:


4.结果分析

  结果输出,如下所示:

> Entering new AgentExecutor chain...
======
======

Action: 
``
{"action": "weather", "action_input": "北京"}
``
Observation: {'temperature': '20', 'description': '晴'}
Thought:======
======

Action: 
``
{"action": "Final Answer", "action_input": "根据查询结果,北京今天的天气是晴,气温为20℃。"}
``

> Finished chain.

> Entering new AgentExecutor chain...
======
======

Action: 
``
{"action": "weather", "action_input": "Shanghai"}
``
Observation: {'temperature': '20', 'description': '晴'}
Thought:======
======

Action: 
``
{"action": "Final Answer", "action_input": "根据最新的天气数据,今天上海的天气情况是晴朗的,气温为20℃。"}
``

> Finished chain.

  刚开始的时候没有找到识别实体city的地方,后面调试ChatGLM3/langchain_demo/ChatGLM3.py->_call()时发现了一个巨长的prompt,这不就是zero-prompt(AgentType.STRUCTURED_CHAT_ZERO_SHOT_REACT_DESCRIPTION)吗?顺便吐槽下LangChain的代码真的不好调试。

三.注册工具增强LLM能力

1.注册工具

  可以通过在tool_registry.py中注册新的工具来增强模型的能力。只需要使用@register_tool装饰函数即可完成注册。对于工具声明,函数名称即为工具的名称,函数docstring即为工具的说明;对于工具的参数,使用Annotated[typ: type, description: str, required: bool]标注参数的类型、描述和是否必须。将get_weather()函数进行注册,如下所示:

@register_tool
def get_weather(  # 工具函数
        city_name: Annotated[str, 'The name of the city to be queried', True],
) -> str:
    """
    Get the current weather for `city_name`
    """

    if not isinstance(city_name, str):  # 参数类型检查
        raise TypeError("City name must be a string")

    key_selection = {  # 选择的键
        "current_condition": ["temp_C", "FeelsLikeC", "humidity", "weatherDesc", "observation_time"],
    }
    import requests
    try:
        resp = requests.get(f"https://wttr.in/{city_name}?format=j1")
        resp.raise_for_status()
        resp = resp.json()
        ret = {k: {_v: resp[k][0][_v] for _v in v} for k, v in key_selection.items()}
    except:
        import traceback
        ret = "Error encountered while fetching weather data!\n" + traceback.format_exc()

    return str(ret)

  具体工具注册实现方式@register_tool装饰函数,如下所示:

def register_tool(func: callable):  # 注册工具
    tool_name = func.__name__  # 工具名
    tool_description = inspect.getdoc(func).strip()  # 工具描述
    python_params = inspect.signature(func).parameters  # 工具参数
    tool_params = []  # 工具参数描述
    for name, param in python_params.items():  # 遍历参数
        annotation = param.annotation  # 参数注解
        if annotation is inspect.Parameter.empty:
            raise TypeError(f"Parameter `{name}` missing type annotation")  # 参数缺少注解
        if get_origin(annotation) != Annotated:  # 参数注解不是Annotated
            raise TypeError(f"Annotation type for `{name}` must be typing.Annotated")  # 参数注解必须是Annotated

        typ, (description, required) = annotation.__origin__, annotation.__metadata__  # 参数类型, 参数描述, 是否必须
        typ: str = str(typ) if isinstance(typ, GenericAlias) else typ.__name__  # 参数类型名
        if not isinstance(description, str):  # 参数描述必须是字符串
            raise TypeError(f"Description for `{name}` must be a string")
        if not isinstance(required, bool):  # 是否必须必须是布尔值
            raise TypeError(f"Required for `{name}` must be a bool")

        tool_params.append({  # 添加参数描述
            "name": name,
            "description": description,
            "type": typ,
            "required": required
        })
    tool_def = {  # 工具定义
        "name": tool_name,
        "description": tool_description,
        "params": tool_params
    }

    print("[registered tool] " + pformat(tool_def))  # 打印工具定义
    _TOOL_HOOKS[tool_name] = func  # 注册工具
    _TOOL_DESCRIPTIONS[tool_name] = tool_def  # 添加工具定义

    return func

2.调用工具

  参考文件ChatGLM3/tool_using/openai_api_demo.py,如下所示:

def main():
    messages = [  # 对话信息
        system_info,
        {
            "role": "user",
            "content": "帮我查询北京的天气怎么样",
        }
    ]
    response = openai.ChatCompletion.create(  # 调用OpenAI API
        model="chatglm3",
        messages=messages,
        temperature=0,
        return_function_call=True
    )
    function_call = json.loads(response.choices[0].message.content)  # 获取函数调用信息
    logger.info(f"Function Call Response: {function_call}")  # 打印函数调用信息

    tool_response = dispatch_tool(function_call["name"], function_call["parameters"])  # 调用函数
    logger.info(f"Tool Call Response: {tool_response}")  # 打印函数调用结果

    messages = response.choices[0].history  # 获取历史对话信息
    messages.append(
        {
            "role": "observation",
            "content": tool_response,  # 调用函数返回结果
        }
    )

    response = openai.ChatCompletion.create(  # 调用OpenAI API
        model="chatglm3",
        messages=messages,
        temperature=0,
    )
    logger.info(response.choices[0].message.content)  # 打印对话结果

参考文献:

[1]https://github.com/THUDM/ChatGLM3/tree/main

[2]https://python.langchain.com/docs/integrations/tools

Langchain-Chatchat项目:5.1-ChatGLM3-6B工具调用的更多相关文章

  1. 项目乱码 GBK转UTF-8工具

    项目乱码 GBK转UTF-8工具 链接:http://pan.baidu.com/s/1pLw1mMB 密码:rj6c

  2. 项目经验分享——Java常用工具类集合 转

    http://blog.csdn.net/xyw591238/article/details/51678525 写在前面     本文涉及的工具类部分是自己编写,另一部分是在项目里收集的.工具类涉及数 ...

  3. react 前端项目技术选型、开发工具、周边生态

    react 前端项目技术选型.开发工具.周边生态 声明:这不是一篇介绍 React 基础知识的文章,需要熟悉 React 相关知识 主架构:react, react-router, redux, re ...

  4. spring项目中 通过自定义applicationContext工具类获取到applicationContext上下文对象

    spring项目在服务器启动的时候 spring容器中就已经被创建好了各种对象,在我们需要使用的时候可以进行调用. 工具类代码如下 import org.springframework.beans.B ...

  5. So easy Webservice 3.使用HttpClient工具调用Webservice接口

    首先,看看webservice服务调用演示: a) 登录http://www.webxml.com.cn b) 单击手机查询服务 c) 选择要调用的方法 例如: getMobileCodeInfo 输 ...

  6. 在maven项目中 配置代理对象远程调用crm

    1 在maven项目中配置代理对象远程调用crm 1.1 在项目的pom.xml中引入CXF的依赖 <dependency> <groupId>org.apache.cxf&l ...

  7. arcgis js 之 渔网工具(调用地图服务)

    arcgis js 之 渔网工具(调用地图服务) 原理: 简历不同级别的网渔网图层,设置显示比例尺.然后发布服务,使用MapImageLayer接收. 过程: 1.在arcmap中用创建渔网工具将不同 ...

  8. [C#项目开源] MongoDB 可视化管理工具 (2011年10月-至今)

    正文 该项目从2011年10月开始开发,知道现在已经有整整5年了.MongoDB也从一开始的大红大紫到现在趋于平淡. MongoCola这个工具在一开始定位的时候只是一个Windows版本的工具,期间 ...

  9. .NET项目工程生成一份项目帮助文档chm--Sandcastle工具

    Sandcastle的,由Microsoft创建的,是从创建MSDN风格的文档中使用的工具.NET程序集和关联的XML注释文件.目前的版本是 2010年6月发布.这是命令行并没有GUI前端,项目管理功 ...

  10. 在Visual Studio 2010中进行“项目重命名”的有效工具

    地址:http://www.cnblogs.com/dudu/archive/2011/12/11/visual_studio_2010_project_rename.html 提示:这个工具一次 r ...

随机推荐

  1. 文件系统:ext4 的 block 分布(1G分区为例)

    总的block数量:262144 $ dumpe2fs /dev/vg01/test | grep "Block count" dumpe2fs 1.42.9 (28-Dec-20 ...

  2. react中使用动画 react-transition-group

    在React中通过react-transition-group使用过渡.动画,首先要有CSS3中的过渡和动画的相关知识储备,可以参考 过渡和2D变换.动画和3d变换. 我们自己通过css设置过渡.动画 ...

  3. 基于python tornado实现的简易图床

    基于python tornado实现的简易图床 项目地址 因为买了阿里/腾讯的云服务器,但是使用云存储还需要收费,又加上家里正好有一台nas,又加上闲的没事,所以搞了一个小脚本 这个项目主要功能是为t ...

  4. python将print的打印内容保存到日志

    将python程序中的所有打印内容都输出到日志文件中,在程序执行完成后,方便查询程序运行过程是否出现异常. 1. 将打印内容输出到日志文件 1.1 代码实现: sys.stdout = open('s ...

  5. 《CTFshow-Web入门》07. Web 61~70

    @ 目录 web61~65 题解 web66 题解 原理 web67 题解 原理 web68 题解 原理 web69 题解 原理 web70 题解 原理 ctf - web入门 web61~65 题解 ...

  6. 详谈 springboot整合shiro

    背景: 上文学习了shrio 基本概念后,本章将进一步的落地实践学习,在springboot中如何去整合shrio,整个过程步骤有个清晰的了解. 利用Shiro进行登录认证主要步骤: 1. 添加依赖: ...

  7. 杰哥教你面试之一百问系列:java多线程

    java多线程是java面试中的高频问题,如何才能在面试中脱颖而出呢?熟读这里的一百个java多线程面试问题即可. 1. 什么是线程?什么是进程? 回答: 线程是操作系统能够进行调度的最小执行单位,它 ...

  8. [译]这几个CSS小技巧,你知道吗?

    前言 在网页设计和前端开发中,CSS属性是非常重要的一部分.掌握常用的CSS属性不仅可以使你的网页看起来更美观,还能提升用户体验,今天小编为大家介绍8个常见的CSS小技巧: 1.修改滚动条样式 下图是 ...

  9. 白盒AES和SM4实现的差分故障分析

    DFA攻击背景介绍 传统的密码安全性分析环境被称为黑盒攻击环境,攻击者只能访问密码系统的输入与输出,但随着密码系统部署环境的多样化,该分析模型已经不能够反映实际应用中攻击者的能力.2002年,Chow ...

  10. React仿大众点评外卖app

    主要使用技术: react react-router4 redux: action.reducer.store管理数据 fetch: 进行数据交互 prismjs : 页面嵌入代码,高亮显示插件 bu ...