当前位置: 首页 > article >正文

Agent构建总结(LangChain)

跑通Agent的过程其实主要是在让LLM跑通以下链条:

有什么工具 → 当前需要用什么工具 → 结构化返回工具和参数 → 本地调用对应工具 → 得到最终结果

上述链条所有的实现均离不开prompt,因此我们可以将链条转换为以下两个问题:

  • 问题一:如何在prompt中告知LLM可用工具及工具用途?
  • 问题二:如何在prompt中约束LLM结构化返回工具和参数?

完成上述两个问题后Agent其实就可以完成单一工具的问题了,此时将会有一个新的问题:

  • 问题三:如何在prompt中约束LLM有次序的选择工具完成复杂任务?

本篇文章将针对上述问题进行简单总结

注意:出于简洁便于理解的意图,本篇文章中大面积结合 LangChain 实现。

一. 如何在prompt中告知LLM可用工具及工具用途?

利用LangChain我们可以先将工具及其信息生成一个工具对象,这及便于管理也便于后续对工具的调用,如下:

from langchain.tools import StructuredTool

tool = StructuredTool.from_function(
    func=func,
    name="ToolName",
    description="xxx",
)

参数解析:

  • func 传入我们已经实现的工具函数
  • name= 对该工具的命名
  • description 对该工具用途的描述

之后我们在写prompt的同时便可将工具及其信息一同写入,该步骤同样可用LangChain的接口实现。

from langchain_core.tools import render_text_description

with open(path, "r", encoding="utf-8") as f:
	main_prompt = ChatPromptTemplate.from_messages(
	    [
	        HumanMessagePromptTemplate.from_template(f.read()),
	    ]
	).partial(
	    tools=render_text_description([tool_a, tool_b]),
	)

render_text_description 的作用是解析我们提供的工具对象的列表,将其整理为文本的形式。

二. 如何在prompt中约束LLM结构化返回工具和参数?

与问题一类似,我们需要先利用pydantic创建一个类,该类用于描述对选择工具时输出的结构:

from pydantic import BaseModel, Field
from typing import List, Optional, Dict, Any

class Action(BaseModel):
    name: str = Field(description="Tool name")
    args: Optional[Dict[str, Any]] = Field(description="Tool input arguments, containing arguments names and values")

 
self.output_parser = PydanticOutputParser(pydantic_object=Action)
self.robust_parser = OutputFixingParser.from_llm(parser=self.output_parser, llm=self.llm)

action = self.robust_parser.parse(action if action else response)

tool.run(action.args)

参数解析:

  • name 为期望调用的工具名
  • args 为调用该工具时期望传入的参数

同样的我们在写prompt时便利用LangChain轻松将该结构化信息写入

from langchain.output_parsers import PydanticOutputParser, OutputFixingParser

output_parser = PydanticOutputParser(pydantic_object=Action)

with open(path, "r", encoding="utf-8") as f:
	self.main_prompt = ChatPromptTemplate.from_messages(
	    [
	        HumanMessagePromptTemplate.from_template(f.read()),
	    ]
	).partial(
	    format_instructions=self.output_parser.get_format_instructions(),
	)

后续当LLM有返回结果时我们要进行以下几步操作:

  1. 修复返回结果(由于LLM的推理机制,因此输出不一定靠谱)
  2. 找到执行工具
from langchain.output_parsers import OutputFixingParser

robust_parser = OutputFixingParser.from_llm(parser=self.output_parser, llm=self.llm)

action = robust_parser.parse(action)

for tool in self.tools:
	if tool.name == action.name:
		observation = tool.run(action.args)
		print(observation)

其中 OutputFixingParser 是LangChain提供的利用LLM本身修复结构化输出的类, robust_parser.parse() 之后将返回一个可调用的对象。

由于我们工具在问题一中已经生成工具对象,因此这里可以直接 tool.run(action.args) 执行工具。

三. 如何在prompt中约束LLM有次序的选择工具完成复杂任务?

想要完成多轮的工具选择我们就需要要求LLM在每次输出中包含除结构化信息外的内容:

  • 对于问题的拆解和计划等
  • 工具选择理由等

并将所有输出以短时记忆的形式写入prompt,再次给到LLM。

重复循环直到LLM认为当前任务已经彻底结束或超过我们预设的最大循环次数。

当然由于输出中包含了其他信息,因此我们需要对输出内容中的结构化信息进行解析等操作,就不在此赘述。

这里提供一个极其简易的prompt,仅供格式参考并无实际价值。

你是一个强大的智能体,你可以使用工具与指令独立自动化的解决问题

你的任务是:
{task}

可参考的资料文件均在以下目录:
{work_dir}
请始终保持该目录路径的完整。

你可以使用的工具如下:
{tools}

当前的任务执行记录:
<history>
{short_memory}
</history>

输出形式:1)首先,根据以下格式说明,输出你的思考过程:
**关键概念**:任务中涉及的组合型概念或实体。已经明确获得取值的关键概念,将其取值完整备注在概念后。
**概念拆解**:将任务中的关键概念拆解为一系列待查询的子要素。每个关键概念一行,后接这个概念的子要素,每个子要素一行,行前以 '-' 开始。已经明确获得取值的子概念,将其取值完整备注在子概念后。
**反思**:自我反思,观察以前的执行记录,一步步思考以下问题:
    1. 当前已经获得了那些概念?
    2. 还需要获取那些概念?
**计划**:详细列出当前动作的执行计划。只计划一步的动作。PLAN ONE STEP ONLY!
(2)最后,以JSON形式输出你选择执行的动作/工具
{format_instructions}

请确保每次选择工具前你都先以文字输出了你的思考分析过程。
请在每次**计划**后输出JSON格式的工具选择。
请确保你的工具选择(JSON)出现在输出的最后一部分。
请确保你输出的JSON代码块以```json\n\n```包裹。

http://www.kler.cn/a/418061.html

相关文章:

  • Fortran mpi在Linux的安装
  • 【Electron学习笔记(三)】Electron的主进程和渲染进程
  • JAVA篇08 —— String类
  • React+TS+css in js 练习
  • 【Linux】进程控制,手搓简洁版shell
  • 使用 Spring Boot 和 GraalVM 的原生镜像
  • C/C++基础知识复习(32)
  • Clickhouse 数据类型
  • 【遥感综合实习】专题一 多时相多波段遥感影像的机器学习地物分类研究
  • 第十一课 Unity编辑器创建的资源优化_预制体和材质篇(Prefabs和Materials)详解
  • java-kafka面试相关基础题目整理01
  • 基于单片机的微型电子琴建模
  • ASP.NET Core 负载/压力测试
  • Python语法基础(四)
  • 多线程安全单例模式的传统解决方案与现代方法
  • 关于线扫相机的使用和注意事项
  • shell脚本练习(2)
  • Java安全—原生反序列化重写方法链条分析触发类
  • C++趣味编程玩转物联网:基于树莓派Pico控制无源蜂鸣器-实现音符与旋律的结合
  • 递归算法讲解(c基础)
  • Docker扩容操作(docker总是空间不足)
  • C#基础之预处理器,异常处理
  • 三维扫描仪-3d扫描建模设备自动检测尺寸
  • Android笔记【10】
  • 【前端开发】JS+Vuew3请求列表数据并分页
  • Spring Boot日志总结