LangChain的函数,工具和代理(五):Tools Routing

关于langchain的函数、工具、代理系列的博客我之前已经写了四篇,还没有看过的朋友请先看一下,这样便于对后续博客内容的理解:

LangChain的函数,工具和代理(一):OpenAI的函数调用

LangChain的函数,工具和代理(二):LangChain的表达式语言(LCEL)

LangChain的函数,工具和代理(三):LangChain中轻松实现OpenAI函数调用 

LangChain的函数,工具和代理(四):使用 OpenAI 函数进行标记(Tagging) & 提取(Extraction)

 今天我们来学习Langchain中非常有用的工具“tools”,以及用来选择tools的方法“routing”,在之前的几篇博客中我们介绍了如何在langchain中实现openai的函数调用的功能,这里需要强调的是我们之前介绍的langchain的函数调用并非真正意义上的函数调用,而是让llm根据用户信息的上下文来返回被调用函数的参数,真正的函数调用还是需要手动编写函数调用的代码,那么今天我们来介绍一种让langchain实现真正意义上函数调用功能.

一、Tools

在langchain中,tools是代理(agent)用来与外界交互的接口。这里所谓的“与外界交换”是指让llm可以访问外部世界的功能,如执行互联网搜索,调用外部api等的能力。之前我们介绍了如何让llm来调用外部函数,也就是我们需要先定义好外部函数,然后生成一个openai能理解的该函数的描述对象,最后通过解析llm的返回值来获取调用参数,整个过程比较繁琐。但是有了langchain的tools以后,就可以大大简化整个函数调用的流程。

 接下来在正式“抠腚”😀之前,先让我们做一些初始化的工作,如设置opai的api_key,这里我们需要说明一下,在我们项目的文件夹里会存放一个 .env的配置文件,我们将api_key放置在该文件中,我们在程序中会使用dotenv包来读取api_key,这样可以避免将api_key直接暴露在程序中:

#pip install -U python-dotenv
 
import os
import openai
 
from dotenv import load_dotenv, find_dotenv
_ = load_dotenv(find_dotenv()) # read local .env file
openai.api_key = os.environ['OPENAI_API_KEY']

下面我们来定义一个函数用来模拟查询天气的函数search,它的逻辑简单只返回一个温度值:

#导入langchain的tool
from langchain.agents import tool

#添加tool装饰器
@tool
def search(query: str) -> str:
    """Search for weather online"""
    return "42f"

print(f"search.name:{search.name}")
print(f"search.description:{search.description}")
print(f"search.args:{search.args}")

这里我们在定义search函数时添加了一个@tool的装饰器,它表示search函数为一个tool,当search函数成为一个tool以后,它就具备了"run"的能力:

#执行函数调用
search.run("sf")

 下面我们需要对search函数的输入参数做一个类型限制,所以我们需要创建一个pydantic类:

from pydantic import BaseModel, Field
class SearchInput(BaseModel):
    query: str = Field(description="Thing to search for")
        
@tool(args_schema=SearchInput)
def search(query: str) -> str:
    """Search for the weather online."""
    return "42f"

print(f"search.args:{search.args}")

 这里我们给装饰器@tool附加了args_schema参数,它的值为SearchInput,这样就可以限制search函数的输入参数的类型了。

接下来我们需要定义一个真实的调用外部api获取天气温度的函数:

import requests
from pydantic import BaseModel, Field
import datetime

# Define the input schema
class OpenMeteoInput(BaseModel):
    latitude: float = Field(..., description="Latitude of the location to fetch weather data for")
    longitude: float = Field(..., description="Longitude of the location to fetch weather data for")

@tool(args_schema=OpenMeteoInput)
def get_current_temperature(latitude: float, longitude: float) -> str:
    """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'

这里我们定义了一个调用外部api(https://api.open-meteo.com/v1/forecast)来获取指定经纬度坐标d地区的天气温度的函数get_current_temperature,该函数的输入参数时经纬度坐标,它们被pydantic类限制了数据类型。下面我们查看一下该函数的输入参数:

print(get_current_temperature.args)

 下面我们实际调用一下该函数:

get_current_temperature.run({"latitude": 13, "longitude": 14})

 从上面的返回结果中我们看到外部api返回了温度值为18.2度,这里需要说明的是,我们在定义该函数时输入参数为pydantic 类,因此在执行函数调用的时候,输入参数必须是字典的格式,否则会报错。下面我们再定义一个维基百科的查询函数:

#pip install wikipedia

import wikipedia
@tool
def search_wikipedia(query: str) -> str:
    """Run Wikipedia search and get page summaries."""
    page_titles = wikipedia.search(query)
    summaries = []
    for page_title in page_titles[: 3]:
        try:
            wiki_page =  wikipedia.page(title=page_title, auto_suggest=False)
            summaries.append(f"Page: {page_title}\nSummary: {wiki_page.summary}")
        except (
            self.wiki_client.exceptions.PageError,
            self.wiki_client.exceptions.DisambiguationError,
        ):
            pass
    if not summaries:
        return "No good Wikipedia Search Result was found"
    return "\n\n".join(summaries)

这里我们定义了一个维基百科的查询函数search_wikipedia,接下来我们可以直接调用该函数

search_wikipedia.run({"query": "langchain"})

这里我们看到 search_wikipedia函数通过维基百科查询到了关于langchain的相关信息。

二、Routing

前面我们已经有了两个实用的外部函数“get_current_temperature”和“search_wikipedia”,它们可以根据要求来返回天气温度和维基百科的相关信息,接下来我们要让llm在和用户对话的过程中自主判该调用哪一个函数,不过由于llm只能理解函数的描述信息,因此我们需要将这两个函数转换成openai能理解的函数描述变量,我们可以使用langchain的format_tool_to_openai_function方法将函数转换成描述变量比如像这样:

from langchain.tools.render import format_tool_to_openai_function

format_tool_to_openai_function(get_current_temperature)

format_tool_to_openai_function(search_wikipedia)

 接下来我们来定义一个可以查询天气温度和维基百科的chain:

from langchain.chat_models import ChatOpenAI
from langchain.prompts import ChatPromptTemplate

#创建函数描述变量
functions = [
    format_tool_to_openai_function(f) for f in [
        search_wikipedia, get_current_temperature
    ]
]

#定义llm
model = ChatOpenAI(temperature=0).bind(functions=functions)

#创建prompt
prompt = ChatPromptTemplate.from_messages([
    ("system", "You are helpful but sassy assistant"),
    ("user", "{input}"),
])

#定义chain
chain = prompt | model

#调用chain
chain.invoke({"input": "上海现在的天气怎么样?"})

 这里我们看到llm并没有实际去调用函数,而是返回了函数调用的参数,并且llm通过上下文准确识别出需要调用哪个函数,并给出了函数的参数即经纬度坐标。我们再测试一下维基百科查询:

chain.invoke({"input": "langchain 是什么?"})

 这里我们看到当llm需要调用函数时返回的消息AIMessage中content都是空的,这个content表示llm返回给用户的文本内容,这就意味着当llm觉得需要调用外部函数时llm不会回答用户任何文本信息,因此返回消息中content都是空的。下面我们给llm打个招呼,看看会返回什么信息:

chain.invoke({"input": "你好"})

这里我们看到当我们和llm打招呼时,llm返回的AIMessage消息中只包含了content内容,除此之外没有其他任何的内容,这说明此时llm意识到不需要调用任何外部函数。 接下来我们给chain加上一个解析器组件:

from langchain.agents.output_parsers import OpenAIFunctionsAgentOutputParser

chain = prompt | model | OpenAIFunctionsAgentOutputParser()

result = chain.invoke({"input": "上海现在的天气怎么样?"})
result

当我们给chain添加了一个解析器组件OpenAIFunctionsAgentOutputParser以后,llm返回了一个AgentActionMessageLog结构的消息体,通过该消息体我们可以轻松获取被调用的函数名和参数:

result.tool

result.tool_input

有了该消息体,我们还可以轻松调用外部函数:

get_current_temperature(result.tool_input)

下面我们测试一下和llm打招呼:

result = chain.invoke({"input": "你好"})
result

这里我们看到当我们和llm打招呼时候,llm返回的并不是之前的 AgentActionMessageLog结构的消息体,而是AgentFinish结构的消息体,这说明当llm需要或者不需要调用外部函数时它们返回的消息体类型是不一样的。

type(result)

当不需要调用函数时,我们可以使用return_values方法来获取llm返回的文本消息:

result.return_values

前面我们介绍的让llm调用外部函数的方法和之前博客中介绍的类似,都是让llm返回被调用函数的参数,并没有真正实现函数调用,要实现真正的函数调用还必须要手工去执行该函数,下面我们来介绍真正的让llm自动化调用函数的功能,不过我们先要创建一个route函数,我们要借助这个rute函数来让llm实现真正的函数调用:

from langchain.schema.agent import AgentFinish

def route(result):
    if isinstance(result, AgentFinish):
        return result.return_values['output']
    else:
        tools = {
            "search_wikipedia": search_wikipedia, 
            "get_current_temperature": get_current_temperature,
        }
        return tools[result.tool].run(result.tool_input)
    

这里需要说明的是route函数通过识别llm返回消息的类型,来判断是否需要调用函数,如何需要调用函数,则执行之前介绍的函数的run功能来实现真正的函数调用,接下来我们需要给chain添加一个route组件,让它可以实现真正的函数调用:

chain = prompt | model | OpenAIFunctionsAgentOutputParser() | route

result = chain.invoke({"input": "上海的天气现在怎么样?"})
result

 这里我们看到当我们向llm询问上海的天气时候,llm调用了天气查询函数,并通过该函数返回了一个真实的温度值。

result = chain.invoke({"input": "langchain是什么?"})
result

 

 从上面的结果中我们看到,当我们询问“langchain是什么”的时候,llm调用了维基百科搜索函数,并将搜索结果返回给了用户。下面我们测试一下不需要调用函数的场景:

result = chain.invoke({"input": "请问老鼠生病了可以吃老鼠药吗?"})
result

 这里我们看到,当我们询问llm“老鼠生病了可以吃老鼠药吗”这个问题时,llm给出的结果是它自己判断的结果,并没有调用我们定义好的外部函数。

三、OpenAPI Specification

OpenAPI 规范 (OAS) 定义了一个与语言无关的标准 HTTP API 接口,允许人类和计算机发现和理解服务的功能,而无需访问源代码、文档或通过网络流量检查。下面我们再介绍一下langchain中如何将符合OpenAPI规范文本转换成openai的函数描述变量。下面我们有一个符合openAPI规范的函数描述文本,我们要将它转换成openai的函数描述变量:

text = """
{
  "openapi": "3.0.0",
  "info": {
    "version": "1.0.0",
    "title": "Swagger Petstore",
    "license": {
      "name": "MIT"
    }
  },
  "servers": [
    {
      "url": "http://petstore.swagger.io/v1"
    }
  ],
  "paths": {
    "/pets": {
      "get": {
        "summary": "List all pets",
        "operationId": "listPets",
        "tags": [
          "pets"
        ],
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "description": "How many items to return at one time (max 100)",
            "required": false,
            "schema": {
              "type": "integer",
              "maximum": 100,
              "format": "int32"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A paged array of pets",
            "headers": {
              "x-next": {
                "description": "A link to the next page of responses",
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Pets"
                }
              }
            }
          },
          "default": {
            "description": "unexpected error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "summary": "Create a pet",
        "operationId": "createPets",
        "tags": [
          "pets"
        ],
        "responses": {
          "201": {
            "description": "Null response"
          },
          "default": {
            "description": "unexpected error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/pets/{petId}": {
      "get": {
        "summary": "Info for a specific pet",
        "operationId": "showPetById",
        "tags": [
          "pets"
        ],
        "parameters": [
          {
            "name": "petId",
            "in": "path",
            "required": true,
            "description": "The id of the pet to retrieve",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Expected response to a valid request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Pet"
                }
              }
            }
          },
          "default": {
            "description": "unexpected error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "Pet": {
        "type": "object",
        "required": [
          "id",
          "name"
        ],
        "properties": {
          "id": {
            "type": "integer",
            "format": "int64"
          },
          "name": {
            "type": "string"
          },
          "tag": {
            "type": "string"
          }
        }
      },
      "Pets": {
        "type": "array",
        "maxItems": 100,
        "items": {
          "$ref": "#/components/schemas/Pet"
        }
      },
      "Error": {
        "type": "object",
        "required": [
          "code",
          "message"
        ],
        "properties": {
          "code": {
            "type": "integer",
            "format": "int32"
          },
          "message": {
            "type": "string"
          }
        }
      }
    }
  }
}
"""

该变量text 包含了三个函数的描述内容,接下来我们要将它转换成openai的函数描述变量:

from langchain.chains.openai_functions.openapi import openapi_spec_to_openai_fn
from langchain.utilities.openapi import OpenAPISpec

spec = OpenAPISpec.from_text(text)

pet_openai_functions, pet_callables = openapi_spec_to_openai_fn(spec)
pet_openai_functions

这里我们使用了openapi_spec_to_openai_fn方法轻松的将openapi的变换成了openai的函数描述变量其中包含了3个函数 listPets,createPets,showPetById,下面我们还实际使用一下该函数描述变量:

from langchain.chat_models import ChatOpenAI

model = ChatOpenAI(temperature=0).bind(functions=pet_openai_functions)

model.invoke("what are three pets names")

 

model.invoke("tell me about pet with id 42")

从上面的结果中我们看到llm能根据问题返回调用函数的参数,这说明符合openapi规范的文本也可以被正确转换成openai能识别的函数描述变量。

四、总结

今天我们学习了langchain的tools,routing组件的原理,并借助tool和routing让llm实现了真正意思上的函数调用,最后我们还介绍了openapi格式的函数说明文本如何通过langchain来转换成openai的函数描述变量,希望今天的内容对大家有所帮助。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/225469.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【改进YOLOv8】融合Gold-YOLO的车辆未礼让行人检测系统

1.研究背景与意义 项目参考AAAI Association for the Advancement of Artificial Intelligence 研究背景与意义 随着交通工具的普及和道路交通量的增加,交通安全问题日益凸显。尤其是车辆未礼让行人的情况频繁发生,给行人的生命安全带来了严重威胁。因…

从零开始:同城O2O外卖APP的技术开发指南

随着互联网的迅速发展,O2O(OnlinetoOffline)模式在各个行业都取得了巨大成功,而同城外卖APP更是成为人们生活中不可或缺的一部分。本文将从零开始,为您提供一份同城O2O外卖APP的技术开发指南,让您能够深入了…

OpenCV交叉编译

1.下载代码解压 tar -zxvf opencv-4.8.1.tar.gz cd cd opencv-4.8.1 sudo mkdir chmod 777 build cd build 2.配置交叉编译工具 根据自己的板子进行修改 -D CMAKE_C_COMPILERaarch64-mix210-linux-gcc -D CMAKE_CXX_COMPILERaarch64-mix210-linux-g 3.cmake生成makefi…

1.4 场景设计精要

一、场景主题确定 设计游戏场景首先明确游戏发生的时间地点等时代背景。通过对玩家动线的设计,功能模型的合理布局构建出场景的基本骨架。利用光影效果和色彩变化烘托场景氛围。 市场上常见的主题场景:剑侠、科幻、废墟、魔幻等 二、场景风格确定 大类分…

C51汇编程序

目录 一.C51的数据类型和存储类型 1.数据类型: 2.C51的扩展数据类型: 3.数据存储类型 4.数据存储模式 二.特殊功能寄存器及其位变量定义 1.特殊功能寄存器的C51定义 2.位变量的C51定义 三.C51语言的绝对地址访问 1.绝对宏 2._at_关键字 一.C5…

Linux CentOS本地部署SQL Server数据库结合cpolar内网穿透实现公网访问

🌈个人主页:聆风吟 🔥系列专栏:数据结构、Cpolar杂谈 🔖少年有梦不应止于心动,更要付诸行动。 文章目录 📋前言一. 安装sql server二. 局域网测试连接三. 安装cpolar内网穿透四. 将sqlserver映射…

创业6个月裤衩都赔掉了;2023生成式AI年度大事表;AI工程师的自我修养;LLM开发者成长计划;OpenAI LLM入门课程 | ShowMeAI日报

👀日报&周刊合集 | 🎡生产力工具与行业应用大全 | 🧡 点赞关注评论拜托啦! 👀 黄家驹AI演唱「直到世界尽头」,是科技前进也是青春回望~ https://www.bilibili.com/video/BV1CG411i7MV 最近几天&#xf…

2022 RedisDays 内容揭秘

上个月,Redis举办了3场线上会议,分别介绍了即将正式发布的Redis 7中包括的重要更新的内容,还有Redis完全重写的RedisJSON 2.0模块,和新发布的Redis Stack模块。除此之外,在此次线上会议中还介绍了现代化的软件架构与Re…

pyside6详细笔记

文章目录 主要模组简介绍安装与环境配置安装配置QtDesignerPyUICPyRCC基础了解元对象系统对象模型重要的类Qt 对象:身份?值?对象树与所有状态概述QObjects 的构造/销毁顺序继承关系图Qt 命名空间模块简介QWidget窗口的创建在PyQt中使用qrc/rcc资源系统Qt 资源系统简介qrc 文件…

从Java8升级到Java17,特色优化点

从Java8升级到Java17,特色优化点 一、局部变量类型推断二、switch表达式三、文本块四、Records五、模式匹配instanceof六、密封类七、NullPointerException 从Java 8 到 Java 20,Java 已经走过了漫长的道路,自 Java 8 以来,Java 生…

预赛->省赛->国赛 我的全国软件测试大赛之旅

学习推荐 Web 功能测试:Javaselenium3 web自动化测试实战 性能测试:看慕测官方的视频,这里会用就行,不用学太多 自己根据视频写的:Web自动测试常用代码(Java版) Web没啥难的,主要拼手速,其…

出错:I/O文件读取JAVA

I/O文件读取 /** author:xiaowang* date:2023/12/6* demand:读取java1班的数据* * */ package homework;import java.io.FileNotFoundException; import java.io.FileReader; import java.io.IOException;public class FileReadTest {public static void main(String[] args) …

Windows系统上如何搭建Linux操作系统

一、准备工作 1,VMware安装包 2,Centos IOS镜像 3,finalshell安装包 阿里云盘下载地址: https://www.alipan.com/s/uSQsWn15E3W 二,VMware安装 1,新建虚拟机 2,选择下一步 3,…

小航助学题库白名单竞赛考级蓝桥杯等考scratch(14级)(含题库教师学生账号)

需要在线模拟训练的题库账号请点击 小航助学编程在线模拟试卷系统(含题库答题软件账号) 需要在线模拟训练的题库账号请点击 小航助学编程在线模拟试卷系统(含题库答题软件账号)

每日一题 1466. 重新规划路线(树,DFS)

根据 connections 建立无向树从 0 开始深搜,每次调用 dfs 时判断路径方向是否正确 class Solution:def minReorder(self, n: int, connections: List[List[int]]) -> int:to defaultdict(set)edge defaultdict(list)for con in connections:edge[con[0]].appe…

Numpy 实现ID3决策树

Numpy 实现ID3决策树 # 定义节点类 二叉树 class Node:def __init__(self, rootTrue, labelNone, feature_nameNone, featureNone):self.root rootself.label labelself.feature_name feature_nameself.feature featureself.tree {}self.result {label:: self.label,fea…

HarmonyOS学习--TypeScript语言学习(一)

注意:这只是我学习的笔记!!! 注意:这只是我学习的笔记!!! 注意:这只是我学习的笔记!!! 本章目录如下: 一、TypeScript语言…

汽车防爆膜行业研究:中国发展前景及市场投资分析

随着汽车保有量的不断增长,汽车的维修和保养等服务市场规模也会快速提升。业内人士表示,今年以来,越来越多的企业开始发力这一市场,汽车后市场的竞争区域也从大中城市向县域城市下沉。 防爆膜就是在车的玻璃上安装一层保护膜&…

各大期刊网址

1.NeurIPS,全称Annual Conference on Neural Information Processing Systems, 是机器学习领域的顶级会议,与ICML,ICLR并称为机器学习领域难度最大,水平最高,影响力最强的会议! NeurIPS是CCF 推…

Android 背景边框集合

效果图 代码 <?xml version"1.0" encoding"utf-8"?> <shape xmlns:android"http://schemas.android.com/apk/res/android" android:shape"rectangle"><solid android:color"#ffffff" /><stroke and…