(18-5-04)Agents(智能代理):返回结构化输出

6.5.4  返回结构化输出

在实际应用中,大多数代理默认返回单个字符串。然而,在某些情况下,返回具有更多结构化信息的输出可能会非常有用。在LangChain中,返回结构化输出是一种让代理(agent)提供更有组织和易于处理信息的方法。不同于简单的文本字符串,结构化输出可以包含多个字段,每个字段都承载着特定的数据。这种方式对于需要将代理的输出进一步用于数据分析、存储到数据库或集成到其他应用程序中的场景非常有用。

在LangChain中返回结构化输出的基本步骤如下所示:

(1)定义响应模式(Response Schema):首先,需要定义一个响应模式,这通常是一个类,使用Pydantic库来确保数据的类型和结构。例如,对于一个问答代理,可能需要一个包含答案和来源列表的响应模式。

from pydantic import BaseModel, Field
from typing import List

class Response(BaseModel):
    answer: str = Field(description="The final answer to respond to the user")
    sources: List[int] = Field(description="List of page chunks that contain the answer")

(2)创建代理(Creating the Agent):在创建代理时需要将工具(tools)和响应模式作为函数传递给语言模型(LLM),这样代理就可以在执行过程中调用这些工具,并按照定义好的模式返回结构化输出。

(3)自定义解析逻辑(Custom Parsing Logic):为了处理语言模型的输出并将其转换为结构化格式,可能需要自定义解析逻辑,这通常涉及到检查输出中的特定标记或函数调用,并据此构造相应的响应模式实例。

(4)运行代理(Running the Agent):在创建代理后可以通过提供输入来运行它,代理将执行其任务,并使用自定义解析逻辑来生成结构化输出。

(5)处理输出(Handling the Output):代理返回的结构化输出可以被应用程序的其他部分直接使用,无需额外的解析或转换步骤。

例如下面的实例演示了使用LangChain代理返回结构化输出的过程,这个例子的核心是构建一个能够理解和响应特定查询的智能系统,通过加载文档、分割文本、创建检索器和定义响应模型来实现。例子的最后部分展示了使用这个代理来执行一个具体查询的用法。

实例6-1使用LangChain代理返回结构化输出(源码路径:codes\6\Returning.py

实例文件Returning.py的具体实现代码如下所示。

from langchain_chroma import Chroma
from langchain_community.document_loaders import TextLoader
from langchain_openai import OpenAIEmbeddings
from langchain_text_splitters import RecursiveCharacterTextSplitter
import json
from langchain_core.agents import AgentActionMessageLog, AgentFinish
from langchain.agents import AgentExecutor
from langchain.agents.format_scratchpad import format_to_openai_function_messages
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_openai import ChatOpenAI
from typing import List
from langchain_core.pydantic_v1 import BaseModel, Field

loader = TextLoader("state_of_the_union.txt")
documents = loader.load()

# 将文档分割成块
text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=0)
texts = text_splitter.split_documents(documents)

# 在这里我们添加虚假的源信息
for i, doc in enumerate(texts):
    doc.metadata["page_chunk"] = i

# 创建我们的检索器
embeddings = OpenAIEmbeddings()
vectorstore = Chroma.from_documents(texts, embeddings, collection_name="state-of-union")
retriever = vectorstore.as_retriever()

from langchain.tools.retriever import create_retriever_tool

retriever_tool = create_retriever_tool(
    retriever,
    "state-of-union-retriever",
    "查询检索器以获取国情咨文的信息",
)

class Response(BaseModel):
    answer: str = Field(description="最终回答用户的答案")
    sources: List[int] = Field(
        description="包含问题答案的页面块列表。只有在页面块包含相关信息时才包括它"
    )

def parse(output):
    # 如果没有调用函数,返回给用户
    if "function_call" not in output.additional_kwargs:
        return AgentFinish(return_values={"output": output.content}, log=output.content)

    # 解析函数调用
    function_call = output.additional_kwargs["function_call"]
    name = function_call["name"]
    inputs = json.loads(function_call["arguments"])
    # 如果调用了Response函数,使用函数输入返回给用户
    if name == "Response":
        return AgentFinish(return_values=inputs, log=str(function_call))
    # 否则,返回代理动作
    else:
        return AgentActionMessageLog(
            tool=name, tool_input=inputs, log="", message_log=[output]
        )

prompt = ChatPromptTemplate.from_messages(
    [
        ("system", "你是一个有用的助手"),
        ("user", "{input}"),
        MessagesPlaceholder(variable_name="agent_scratchpad"),
    ]
)

llm = ChatOpenAI(temperature=0)
llm_with_tools = llm.bind_functions([retriever_tool, Response])

agent = (
    {
        "input": lambda x: x["input"],
        # 从中间步骤格式化代理草稿板
        "agent_scratchpad": lambda x: format_to_openai_function_messages(
            x["intermediate_steps"]
        ),
    }
    | prompt
    | llm_with_tools
    | parse
)
agent_executor = AgentExecutor(tools=[retriever_tool], agent=agent, verbose=True)

agent_executor.invoke(
    {"input": "总统对Ketanji Brown Jackson说了什么"},
    return_only_outputs=True,
)

在上述代码中,

上述代码的实现流程如下所示:

(1)文档加载与处理:使用TextLoader加载一个文本文件(state_of_the_union.txt),这通常包含国情咨文的文本。通过RecursiveCharacterTextSplitter将文本分割成较小的块,以便更好地管理和处理。

(2)添加元数据:为每个文本块添加元数据,这里特别添加了page_chunk,用于标识每个块的编号。

(3)创建向量存储和检索器

  1. 使用OpenAIEmbeddings生成文本的嵌入表示
  2. 利用嵌入表示和文档创建Chroma向量存储,这允许高效的相似性搜索。
  3. 从向量存储创建一个检索器(retriever),用于后续查询。

(4)创建检索器工具:使用create_retriever_tool函数创建一个工具,该工具允许用户通过查询检索器来获取信息。

(5)定义响应模型:定义类Response,这是一个继承自BaseModel的Pydantic模型,用于结构化输出。它包含一个答案字符串和一个源页面块列表。

(6)解析输出:实现方法parse,用于解析代理的输出。如果输出中包含了方法调用,它会根据方法的名称和参数进行相应的处理。

(7)构建提示模板:使用ChatPromptTemplate构建一个用于与代理交互的提示模板,包括系统描述、用户输入和代理草稿板。

(8)绑定大型语言模型(LLM):使用ChatOpenAI创建一个大型语言模型实例,并将其与检索器工具和Response函数绑定。

(9)构建代理:构建一个代理,它接收输入,能够格式化草稿板,使用LLM处理输入,并最终通过parse函数解析输出结果。

(10)执行代理:使用AgentExecutor执行代理,并提供必要的工具和设置。

(11)调用代理:最后,通过invoke方法调用代理,传入用户提出的问题,并请求仅返回输出。

执行后会输出:

AgentFinish(return_values={'output': 'No information found about Ketanji Brown Jackson in the provided text.'}, log='No information found about Ketanji Brown Jackson in the provided text.')

  • 10
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

码农三叔

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值