Skip to content
This page has been auto-translated and may contain errors.View in English

代理

docs.scrimba.com

一个**代理**是在循环中运行的模型,逐次选择要调用的工具,直到任务完成。本章的其余内容解释了这意味着什么、它的优势以及隐含的代价。

代理循环

工具使用中,模型调用了一个函数,你将结果发回。但有些任务需要多个步骤,而你事先不知道这些步骤是什么。"找到最便宜的航班并预订它"可能意味着搜索、比较、检查日历,然后预订,模型必须在进行过程中找出这个顺序。

所以与其处理单个工具调用,你把整个流程放在一个循环中。这个重复的周期就是**代理循环**,一个重复的回合,直到任务完成:

  1. 将对话和可用工具发送给模型。
  2. 模型要么调用工具,要么给出最终答案。
  3. 如果它调用了工具,运行它,将结果添加到对话中,然后回到步骤 1。
  4. 如果它给出了最终答案,停止。

每回合,模型都能看到到目前为止发生的一切,包括之前的工具结果,并决定下一步行动。这是与工具使用的唯一区别:你持续循环,直到模型确信它已经完成。

真正的转变在于谁在掌舵。在本章之前的所有章节中,你的代码决定了控制流:你选择何时调用模型、何时运行工具、接下来做什么。代理将这个决定权交给了模型。循环不断询问"接下来应该发生什么?",而模型的预测就是每次的答案。你用灵活性换取了控制权,而这种权衡既是优势也是风险的来源。

在底层,没有什么新发生。它是同样的预测,只是背景在增长,工具使用的来回过程不断重复。模型不会制定计划并执行它。每一回合,它都从现在能看到的东西中预测最好的下一步,然后循环再次运行。

当这个有效时,它看起来像是朝着目标推理。当它无效时,代理会自我循环或走入死胡同,因为系统的任何部分都不在掌握整体计划。

Juno代理循环 代理是工具使用循环的重复:发送对话和工具,模型调用工具或给出最终答案,你运行任何工具并将结果反馈,然后再次进行,直到它给出答案。每回合模型都看到所有先前的结果并选择下一步。相比普通工具使用,唯一新的想法是循环而不是处理单个调用。

工具使用中,你处理一个调用并返回结果。代理是这个来回过程被包装在一个循环中,运行到模型决定完成时。那个循环就是**代理循环**。关键是:停止硬编码步骤序列,让模型在每一回合选择下一个步骤。

周期是四个步骤:

  1. 将对话加上工具列表发送给模型。
  2. 模型用工具调用或最终文本答案做出响应。
  3. 如果它要求了工具,运行它们,将每个结果附加到对话中,然后循环。
  4. 如果它给出了答案,停止并返回。

使这个工作的机制是,模型在每一回合都看到完整的不断增长的对话:你的原始请求、它进行的每次工具调用,以及返回的每个结果。这个累积的背景是代理的唯一记忆。每个预测都以到目前为止的整个历史为条件,这就是为什么它可以链接依赖于早期结果的步骤。

值得尽早命名的一个细节:第 2 步可以一次返回多个工具调用,不止一个。当调用不相互依赖时,比如检查三个城市的天气,模型可以在一个回合中请求全部三个,你可以一起运行它们。当一个调用需要另一个的结果时,模型必须等待那个结果才能请求下一个,所以这些跨越分开的回合进行。循环处理两者:运行这一回合返回的任何调用,将所有结果反馈,然后再次预测。

这里面没有全局计划。模型从它现在能看到的东西中预测最好的下一步,而不是从整个任务的地图中。这就是代理和你之前编写的固定管道之间的区别,也是为什么同样能链接研究步骤的循环也能追逐自己的尾巴。

Juno代理循环 代理是工具使用来回过程在循环中:发送对话加工具,模型调用工具或给出答案,你运行工具并将结果反馈,重复直到它给出答案。不断增长的对话是代理的唯一记忆,所以每一步都以它之前的每个结果为条件。一个回合可以一次返回多个独立的工具调用,或当每个依赖于上一个时一次一个。没有全局计划存在于任何地方:每一步都是一个新的下一步猜测。

代理是工具使用的来回过程被放在一个循环中并交由掌舵,代理循环:模型每回合选择下一个工具,你执行它,你将结果反馈,然后再继续,直到它发出最终答案而不是调用。循环是四个步骤,那不是有趣的部分。有趣的部分是架构给你什么和不给你什么。

循环:

  1. 发送运行的对话加上工具模式。
  2. 模型返回工具调用或最终答案。
  3. 运行任何调用,附加结果,循环。
  4. 在最终答案时,返回。

单个回合可以返回多个工具调用。当模型判断它们独立时,它发出并行调用(三个没有数据依赖的查找),当一个需要另一个的输出时,它发出跨回合的串行调用。你不能从外部强制这个:工作是否并行化是模型基于它推断的任务的决定,所以你认为是并行的任务可能回来时是串行的和更慢的,而你认为是串行的任务可能作为一个你必须准备一起运行的批处理回来。

这是负载承担的事实,每个故障模式都追溯到它。没有全局计划。模型不持有任务的地图并对其执行。每一回合,它从当前对话预测下一步,而那个对话是系统整个状态,你的提示加上到目前为止的每次调用和结果,累积的。

每一步都信任前面的步骤,没有任何东西检查整体。这就是为什么看起来在朝着目标推理的代理实际上是采取一系列局部看起来合理的猜测,当它有效时,恰好加起来。

现在要内化的后果,因为防护措施部分会实现它:因为状态只是不断增长的抄本,而每一步都以它为条件,早期的错误工具结果不会被纠正,它会被建立在上面。错误向前传播并复合,而循环的任何部分都没有位置注意到。代理的可靠性工作主要是对抗那一个属性。

Juno代理循环 代理是工具使用来回过程在循环中,模型掌舵:它选择下一个工具,你运行它,你将结果反馈,重复直到它给出答案。整个状态是不断增长的抄本,而且任何地方都没有全局计划,只是一系列下一步猜测,每个都以上一个为条件。一个回合可以返回模型判断为独立的并行调用或它们相互依赖时的串行调用,而且你不控制哪种。坚持一件事:每一步都信任前面的步骤,所以早期的错误结果向前复合,没有任何东西检查整体。

最小代理

这是代码中的循环。它重用工具使用中的工具定义和实现,并将它们包装在循环中,直到模型停止要求工具。

python
import json

def run_agent(user_text, tools, tool_impls, max_steps=6):
    messages = [{"role": "user", "content": user_text}]
    step = 0

    while step < max_steps:
        response = client.chat.completions.create(model=MODEL, messages=messages, tools=tools)
        message = response.choices[0].message
        messages.append(message)

        calls = message.tool_calls
        if not calls:
            return message.content  # 没有工具请求:这是最终答案

        # 运行每个请求的工具并将结果反馈回来
        for call in calls:
            args = json.loads(call.function.arguments)
            result = tool_impls[call.function.name](args)
            messages.append({"role": "tool", "tool_call_id": call.id, "content": json.dumps(result)})

        step += 1

    return "已停止:代理达到了其步骤限制。"  # 安全网

从头到尾读:调用模型,将其消息推送到对话中,如果它要求了工具,运行所有工具并附加结果,然后再循环。当模型返回文本而不是工具调用时,那就是最终答案,你返回它。

max_steps值设置一个**步骤限制**,它是防止循环失控的安全网,这是下一部分的主题。这里的形状与一个提供者的 SDK 匹配;字段名在提供者之间不同,但循环在任何地方都是相同的。

Juno最小代理 最小代理是围绕模型调用的循环:运行模型,运行它要求的任何工具,将结果反馈,重复。当模型返回文本而不是工具调用时,那个文本就是最终答案。步骤限制是一个必需的安全网,不是你之后添加的额外内容。

这是代码中的循环,重用工具使用中的工具定义和实现。它运行直到模型停止要求工具或击中步骤上限。

python
import json

def run_agent(user_text, tools, tool_impls, max_steps=6):
    messages = [{"role": "user", "content": user_text}]
    step = 0

    while step < max_steps:
        response = client.chat.completions.create(model=MODEL, messages=messages, tools=tools)
        message = response.choices[0].message
        messages.append(message)

        calls = message.tool_calls
        if not calls:
            return message.content  # 最终答案

        for call in calls:
            args = json.loads(call.function.arguments)
            try:
                result = tool_impls[call.function.name](args)
            except Exception as err:
                result = {"error": str(err)}  # 浮现故障,不要崩溃
            messages.append({"role": "tool", "tool_call_id": call.id, "content": json.dumps(result)})

        step += 1

    return "已停止:代理达到了其步骤限制。"

形状是一个提供者的 SDK,字段名有所不同,但每个循环都有这些部分:附加模型的消息,分支判断它是否要求了工具,运行请求的工具,将每个结果反馈为一个tool消息,与其调用 id 相关联。

相比裸版本值得做的一个改变是围绕工具的try/except。当工具失败时,不要让异常杀死循环。捕获它并将错误反馈给模型作为工具结果。模型然后可以在下一回合读到"那个查找失败了"并适应:用不同的参数重试,尝试另一个工具,或告诉用户它无法完成任务。崩溃给模型没有恢复的机会;这个**错误即结果**的模式让它在循环中,有可以作用的信息。

当这个运行时需要追踪两件事。首先,对话只会增长。每回合都附加模型的消息和每个工具结果,所以在第五步,模型在重新读取第一步到第四步的所有内容。那是代理的记忆,也是它的账单:每一步重新发送整个累积的背景,所以每步的代币成本随着任务的进行而上升。十步代理不是十个便宜的调用,它是十个调用在越来越大的抄本上。

其次,那个增长就是为什么max_steps很重要,超越停止无限循环。根据任务选择它。天气问题需要两三步;研究任务可能想要十步。将上限设置在健康运行所需的位置之上一点,所以合法的较长任务可以完成但卡住的任务在它耗尽你的预算之前仍然会被切断。

Juno最小代理 循环是:附加模型的消息,分支判断它是否要求了工具,运行它们,将每个结果作为tool消息反馈。用try/except包装工具调用并将错误反馈而不是崩溃,所以模型可以重试或恢复。观察对话只会增长:每一步重新发送整个抄本,所以每步的代币成本随着任务的进行而上升。根据任务设置max_steps,在健康运行之上一点。

循环,重用工具使用中的工具层。一个提供者的 SDK 形状,字段名有所不同,结构没有。

python
import json

def run_agent(user_text, tools, tool_impls, max_steps=6):
    messages = [{"role": "user", "content": user_text}]
    step = 0

    while step < max_steps:
        response = client.chat.completions.create(model=MODEL, messages=messages, tools=tools)
        message = response.choices[0].message
        messages.append(message)

        calls = message.tool_calls
        if not calls:
            return message.content

        for call in calls:
            args = json.loads(call.function.arguments)
            try:
                result = tool_impls[call.function.name](args)
            except Exception as err:
                result = {"error": str(err)}  # 模型读取这个并可以在下一回合适应
            messages.append({"role": "tool", "tool_call_id": call.id, "content": json.dumps(result)})

        step += 1

    return "已停止:代理达到了其步骤限制。"

裸循环是正确的且无法出货,而两者之间的差距就是这部分所在的地方。这段代码的三个属性在生产中会产生问题。

浮现错误,不要崩溃。try/except将失败的工具反馈给模型作为它可以读取的结果。那是对的,但理解你在做什么:你在向模型传递一个错误字符串并信任它能明智地恢复。它可能会重试同样的坏调用,或想出不同的方法,或道歉后停止。

反馈错误保持代理活跃;它不会使恢复可靠。对于任何承载的东西,对每个工具的重试设置上限,并将重复失败视为你处理的终端状态,而不是一个你让模型凭感觉解决的循环。

抄本只会增长,那是两个复合的成本。每步的代币成本上升,因为每次调用重新发送完整的累积历史,所以长期运行在每一回合支付上升的账单,而不是一个平的。而且抄本越长,模型为了决定下一步行动而读取的旧工具输出就越多,这正是背景窗口章节的**丢失在中间**效果开始产生影响的地方,一个模型对长背景中间埋藏的信息的注意较少:应该约束代理的早期说明在累积的结果下沉没并获得较少的注意。长代理不会大声失败,它们漂移。

max_steps是一个预算,不是一个虫子守卫。它存在是因为模型决定何时停止,可以决定错误:循环一个步骤,在两个工具之间乒乓,追逐死胡同。

这个数字是一个真实的成本上限。最坏的情况支出大约是 max_steps 乘以运行结束附近的全背景调用的成本,所以一个背景繁重任务的慷慨上限是一个慷慨的账单。从最长的合法运行加上边界设置它,在运行经常击中它时发出警报(那是代理卡住的信号,而不是上限太低),并抵制提高它来弥补一个无法完成的代理。

Juno最小代理 裸循环是正确的且无法出货。将工具错误反馈回来,以便模型可以适应,但对重试设置上限并将重复失败视为终端,因为浮现错误不会使恢复可靠。抄本只会增长,所以每步的代币成本上升,而且你的早期说明在旧结果下陷埋没直到代理漂移。max_steps是一个成本上限,不是一个虫子守卫:根据最长的真实运行加上边界调整它,当运行一直击中它时,代理卡住了,所以不要反射性地提高这个数字。

停止和防护措施

一个让模型决定何时停止的循环可能无法停止。它可能永远调用工具、重复自己,或追逐死胡同。因为每一步都是一个按代币计费的模型调用,失控的代理会快速烧钱和时间。

所以**防护措施**,你在循环周围放置的限制,是设计的一部分,而不是你之后添加的抛光步骤:

  • 一个步骤限制。 限制循环数量,max_steps的方式。当达到时,停止并返回。
  • 验证工具输入。 模型选择参数,所以在运行任何有真实后果的东西之前检查它们。
  • 限制工具能做什么。 给代理它需要的最少权力。一个读取的工具比一个删除的工具更安全。
  • 观察成本。 每个任务的多个模型调用快速相乘,所以追踪代币并设置预算。

将这些联系在一起的线索:模型在即兴进行路径,所以你放置了它不会的限制。假设代理至少会做一次错误的事情,并使其可生存。

Juno停止和防护措施 代理决定自己的停止点,所以它可能无限循环,而每个循环花费代币。始终设置步骤限制,验证模型选择的参数,并给工具最少的权力。追踪成本,因为每个任务的多个模型调用加起来很快。

一个模型决定何时停止的循环可能无法停止,而且因为每一步都是一个按代币计费的模型调用,卡住的代理会快速烧预算。防护措施是设计的一部分。四个需要接通的:

  • 一个步骤限制。max_steps限制循环并在击中时干净返回。这是你对失控循环的硬停止。
  • 验证工具输入。 模型选择参数,它可以选择错误或被转向错误。在任何有真实后果的工具运行之前检查它们:没有 id 的删除、没有金额的转移、允许目录之外的路径。
  • 最少权力。 给每个工具任务需要的最窄能力。只读胜过读写,范围查询胜过原始数据库访问,固定收件人列表胜过任意发送。
  • 成本上限。 追踪整个运行的代币,而不是每个调用,并在运行跨越预算时停止。

这些联系在一起的要点是,你的工具的参数来自模型,这意味着它们,间接地,来自模型读过的任何文本。如果一个工具取得了一个网页,而那个网页包含说明,那些说明现在在模型的背景中,可以转向下一个工具调用。这是**提示注入**:背景中的不信任文本影响代理做什么。防御不在提示中。它在于验证每个工具输入和范围每个工具的权力,以便即使完全劫持的参数也无法做真实伤害。

Juno停止和防护措施 四个防护措施,设计而不是抛光:max_steps硬停止、每个后果工具的输入验证、最少权力工具范围和整个运行的代币预算。输入验证很重要的原因:工具参数来自模型,这意味着来自它读过的任何文本,所以一个带有指令的取得页面是提示注入,可以转向下一个调用。验证输入并范围权力,以便劫持参数仍然无法做伤害。

一个模型决定何时停止的循环可能无法停止,而且每一步都是一个按代币计费的调用,所以故障模式默认情况下很昂贵。四个防护措施(步骤限制、输入验证、最少权力、成本上限)是桌面赌注。值得你注意的部分是为什么输入验证是安全控制,而不是健全性检查。

工具参数是模型输出,而模型输出以背景中的所有东西为条件,包括代理从外部取得的文本。代理读取一个网页、文档、电子邮件或你不控制的工具结果的时刻,不信任的文本在其背景中,可以携带说明。那是**提示注入**:输入中的对抗内容转向模型到你没有意图的行动。在单一镜头提示中它是一个烦恼。在代理中它是一个活跃的漏洞,因为模型的下一步行动是带有真实世界效果的工具调用,而注入的文本获得转向那个调用的机会。

为它设计成对抗,因为它是。三个规则坚持:

  • 根据允许列表而不是阻止列表验证每个工具参数。 定义有效参数看起来像什么(这个 id 格式、这个收件人集、这个目录),并拒绝所有其他。你不能枚举攻击者会创造的每个坏输入;你可以枚举好的。
  • 范围工具权力,以便完全劫持的调用是可生存的。 假设参数是攻击者控制的,并询问工具可以做什么。如果真实答案是"给任何人发邮件"或"删除任何行",该工具无论如何都太宽了,不管提示有多小心。最少权力是在模型被破坏时坚持的控制,而且提示根本不是一个控制。
  • 对于不可逆行动在循环中放置一个人。 发送金钱、删除数据、发邮件给客户:在确认上有这些门。代理提议,一个人或更严格的检查处置。

这一切都不存在于提示的更深层原因:系统提示中的说明和取得内容中的注入说明以相同的方式抵达,背景中的文本,而模型没有可靠的方式来对它们进行排名。所以你不能提示你的方式到安全。坚持的边界在模型外:验证的输入和范围的工具。这连接到安全和限制中的更广泛的处理。

Juno停止和防护措施 四个防护措施是桌面赌注;赢得思考的那个是输入验证,因为它是一个安全控制。工具参数是模型输出,以它读过的所有东西为条件,所以任何取得的页面或邮件可以携带注入的说明,转向下一个调用,而在代理中那个调用有真实世界的效果。根据允许列表验证,范围每个工具,以便劫持的参数仍然可生存,并在人类上有不可逆行动。你不能提示你的方式到安全:注入的和系统说明对模型来说是相同的文本,所以边界必须活在它外面。

何时工作流胜过代理

代理很令人兴奋,这使得当你不需要时很容易去取一个。纯规则:如果你已经知道步骤,不要使用代理。将它们写出来作为纯代码,一个**工作流**,每个步骤以固定顺序运行。

一个工作流更可靠、更便宜,而且远更容易调试,因为你控制路径而不是希望模型找到它。为开放结束的任务保存代理,其中步骤取决于沿途发现的东西。

许多看起来需要代理的功能最好是作为结构化输出调用或RAG查找,由手工连接。只在你不能事先写下步骤时才取代理。

Juno何时工作流胜过代理 如果你已经知道步骤,将它们硬编码为工作流而不是使用代理:它更便宜、更可靠且更容易调试。为开放结束的任务保存代理,其中步骤取决于沿途发现的东西。许多"代理"功能真的是结构化输出或 RAG 调用由手工连接。

代理是令人兴奋的工具,这正是为什么当它们错误时它们被取用。决定规则是一个问题:你能事先写下步骤吗?如果是的,你想要一个**工作流**,步骤作为纯代码以固定顺序进行,而不是代理。

一个工作流在代理苦恼的所有方面赢得。它更便宜,因为你进行一两个模型调用而不是它们的循环。它更可靠,因为路径是固定的而不是每回合重新决定。而且它远更容易调试,因为失败发生在你可以检查的已知步骤,而不是在一系列模型选择的移动的某处。

这里是实际中的测试。"总结这个支持票、分类其紧迫性并将其路由到一个队列"有已知的步骤以已知的顺序,所以它是一个工作流:一个结构化输出调用来获得总结、类别和紧迫性,然后纯代码来根据结果路由。"调查为什么这个客户的最后三个订单失败"没有已知的步骤,路径取决于每个查找揭示什么,所以它是一个代理。

陷阱是许多功能看起来开放结束且不是。从文档拉字段、从你的文档回答、分类传入的邮件:这些读起来像代理工作但解决为结构化输出调用或RAG查找加上代码。默认为工作流并让任务因不可预测性而值得代理,不可预测性是你不能脚本化。

Juno何时工作流胜过代理 决定是一个问题:你能事先写下步骤吗?如果是的,构建一个工作流,步骤作为纯代码,因为它更便宜、更可靠,且在已知点可调试。"总结、分类、路由票"是一个工作流:一个结构化输出调用加路由代码。"调查这些订单失败"是一个代理,因为路径取决于每个步骤找到什么。默认为工作流并让任务因任务值得代理而获得代理。

代理是人们想使用的工具,这就是它在它不属于的地方被使用。决定规则很短:如果你能事先写下步骤,写下来。那是一个**工作流**,步骤作为代码以固定顺序,对于任何形状你知道的任务,它在成本、可靠性和可调试性上击败代理。

原因直接追踪回架构。代理没有全局计划并向前复合错误,所以其可靠性是每个步骤做对的乘积,没有任何东西检查整体。一个你编写的工作流根据定义有一个计划,你可以检查、测试,并分离地验证每个步骤。当什么在工作流中破坏时,它在一行破坏。当什么在代理中破坏时,它在模型选择的移动的抄本的某处破坏,而你从踪迹重建它在想什么。

现在是更难的情况,因为"我知道步骤"是直接的一半。一个代理可以是错误的工具,即使当任务是开放结束时,当错误步骤的成本太高以至于不能让下一步猜测者拿它。一个预订旅行、移动金钱或修改生产数据的代理是开放结束的,也是某个地方你不想要一个无计划的、可注入的、错误复合循环掌控的。那里正确的设计通常是混合的:模型计划或提议,但一个固定的工作流和人类的门执行,所以无计划的部分永远不直接接触不可逆的部分。

所以这个问题是两个问题。你知道步骤吗,一个错误步骤成本什么?

已知的步骤去一个工作流。已知步骤的未知步骤有便宜、可逆的行动是代理赚取它的保持的地方。有昂贵、不可逆行动的未知步骤想要一个受约束的设计,而不是自由运行的循环。

代理适应开放结束的工作有便宜、可逆的步骤,以及很少别的。一个令人惊讶的"代理"功能份额是结构化输出调用或RAG查找加上代码,而且它们最好保持这样。

Juno何时工作流胜过代理 两个问题,不是一个:你知道步骤吗,一个错误步骤成本什么?已知的步骤去工作流,它在成本、可靠性和调试上击败代理,因为它有一个计划而代理从不。有便宜可逆行动的未知步骤是代理赚取其位置的地方。有昂贵不可逆行动的未知步骤想要混合:模型提议,一个固定工作流和人类的门执行,所以错误复合循环永远不接触你不能撤销的部分。

实际应用

上面的run_agent函数就是全部。给它一个问题、一个工具集和它们的实现,它循环直到完成或直到它击中步骤限制。一个小任务展示了循环值得其保持:

python
answer = run_agent(
    "北京的天气怎样,我应该带伞吗?",
    tools,        # 包括来自工具使用章节的 get_weather
    tool_impls,   # {"get_weather": ...}
)
# 代理调用 get_weather,读"多云",然后在一个最终回合给出答案:
# "北京多云,温度 18 度,所以带伞是个明智的想法。"

一个工具、一个循环、一个答案。相同的结构规模到多个工具和多个步骤而不改变形状。一旦你可以构建这么有能力的东西,下一个问题是你如何知道它们实际有效,这是evals的内容。

Juno实际应用run_agent循环是全部模式:给它一个问题、工具和它们的实现,它循环直到模型停止要求工具或击中限制。一个工具和一个循环规模到许多而不改变形状。下一个问题是你如何知道它实际有效,那是 evals。

run_agent函数就是全部模式。给它一个问题、工具和它们的实现,它循环直到模型停止要求或击中上限。

python
answer = run_agent(
    "北京的天气怎样,我应该带伞吗?",
    tools,        # 包括来自工具使用章节的 get_weather
    tool_impls,   # {"get_weather": ...}
)
# 代理调用 get_weather,读"多云",然后在一个最终回合给出答案。

一个工具、一个循环、一个答案,而相同的形状规模到多个工具和多个步骤。什么没有免费规模是你对它有效的信心。一个工作流你可以逐步测试;代理在不同的输入上走不同的路径,所以一个昨天有效的运行告诉你关于今天失败的运行几乎没有。

这就是为什么下一个要建造的东西与代理一起是一个测量它的方式:在一个代表性任务的集合中运行它并检查结果,而不是信任一个好的演示。把"它在我试时有效"变成一个你可以追踪的数字正是evals涵盖的东西。

Juno实际应用run_agent循环就是全部模式,一个工具和一个循环规模到许多而不改变形状。问题是信心:代理每个输入走不同的路径,所以好的演示几乎没有证明下一次运行。构建一个方式在代表性任务中测量它而不是信任一个成功,那是 evals 的目的。

run_agent函数就是全部模式,而下面的例子就是裸最小值:一个工具、一个循环、一个答案。

python
answer = run_agent(
    "北京的天气怎样,我应该带伞吗?",
    tools,        # 包括来自工具使用章节的 get_weather
    tool_impls,   # {"get_weather": ...}
)
# get_weather 返回"多云",模型在一个最终回合给出答案。

形状规模到许多工具和许多步骤而不改变,这是陷阱,因为故障模式规模与它,而代码保持仍然。一个代理是路径依赖:不同的输入驱动模型选择的不同步骤序列,所以每输入测试告诉你几乎关于你没有看到的输入。来自采样的非决定论意味着即使相同的输入可以两次走不同的路径。

所以下一个建造与代理的东西是测量,它是一个工作流需要的不同类型的测量。你不是断言一个预期输出;你是采样许多代表性任务并读取结果的分布:它有多经常以预算完成,它有多经常到达正确的答案,它在哪里循环,它花费什么在尾部。那是代理形状的evals版本,而它是将上面的节的可靠性、成本和安全关注点变成你可以辩护的数字而不是一个你很幸运获得的演示的唯一方式。

Juno实际应用run_agent是全部模式,而它的形状在故障模式规模时规模,它们规模。一个代理是路径依赖,采样增加非决定论,所以每输入测试告诉你少,而好的演示证明较少。按代理方式测量它:采样许多代表性任务并读分布,它有多经常以预算完成,它在哪里循环,尾部花费什么。那是 evals 的目的,而它是上面的可靠性和成本关注点变成你可以辩护的数字的方式。