Agent設計12要素:構建可靠的AI Agent 原創
本文僅做記錄譯自:raw repo:https://github.com/humanlayer/12-factor-agents
因素 1. 自然語言到工具調用
在構建智能體時最常見的模式之一是將自然語言轉換為結構化工具調用。這是一種強大的模式,它允許你構建能夠推理任務并執行它們的智能體。
當原子化地應用此模式時,它可以將類似以下的短語
你能為Terri創建一個750美元的支付鏈接,用于贊助二月的AI Tinkerers聚會嗎?
轉換為描述Stripe API調用的結構化對象,例如
{
"function": {
"name": "create_payment_link",
"parameters": {
"amount": 750,
"customer": "cust_128934ddasf9",
"product": "prod_8675309",
"price": "prc_09874329fds",
"quantity": 1,
"memo": "嘿Jeff - 請參見下方用于二月AI Tinkerers聚會的支付鏈接"
}
}
}
注意:實際上,Stripe API稍微復雜一些,一個真正執行此操作的智能體(視頻)會列出客戶、產品、價格等,以使用正確的ID構建此有效載荷,或在提示/上下文窗口中包含這些ID(我們將在下面看到,它們其實是一回事!)
從那里,確定性代碼可以接收有效載荷并對其進行處理。(更多內容請參見因素3)
# LLM接收自然語言并返回一個結構化對象
nextStep = await llm.determineNextStep(
"""
為Jeff創建一個750美元的支付鏈接
用于贊助二月的AI Tinkerers聚會
"""
)
# 根據其功能處理結構化輸出
if nextStep.function == 'create_payment_link':
stripe.paymentlinks.create(nextStep.parameters)
return# 或者你想要的任何內容,請參見下文
elif nextStep.function == 'something_else':
# ... 更多情況
pass
else: # 模型未調用我們知道的工具
# 做其他事情
pass
注意:雖然一個完整的智能體會接收API調用結果并與其循環,最終返回類似
我已成功為Terri創建了一個750美元的支付鏈接,用于贊助二月的AI Tinkerers聚會。鏈接如下:https://buy.stripe.com/test_1234567890
相反,我們在這里實際上會跳過這一步,并將其留給另一個因素,你可以選擇是否也采用(由你決定!)
因素 2. 擁有你的提示
不要將你的提示工程外包給一個框架。
順便說一句,這遠非新穎的建議:
一些框架提供了類似這樣的“黑盒”方法:
agent = Agent(
role="...",
goal="...",
persnotallow="...",
tools=[tool1, tool2, tool3]
)
task = Task(
instructinotallow="...",
expected_output=OutputModel
)
result = agent.run(task)
這對于引入一些頂級的提示工程來幫助你入門是很好的,但通常很難進行微調和/或逆向工程,以獲得進入模型的精確標記。
相反,擁有你的提示,并將其視為一等公民的代碼:
function DetermineNextStep(thread: string) -> DoneForNow | ListGitTags | DeployBackend | DeployFrontend | RequestMoreInformation {
prompt #"
{{ _.role("system") }}
你是一個管理前端和后端系統部署的有用助手。
你勤奮地確保通過遵循最佳實踐和正確的部署程序來實現安全和成功的部署。
在部署任何系統之前,你應該檢查:
- 部署環境(暫存 vs 生產)
- 要部署的正確標簽/版本
- 當前系統狀態
你可以使用諸如 deploy_backend、deploy_frontend 和 check_deployment_status
等工具來管理部署。對于敏感部署,使用 request_approval 來獲得
人工驗證。
始終思考首先該做什么,比如:
- 檢查當前部署狀態
- 驗證部署標簽是否存在
- 如有需要,請求批準
- 在生產之前先部署到暫存環境
- 監控部署進度
{{ _.role("user") }}
{{ thread }}
下一步應該是什么?
"#
}
(上面的例子使用了 BAML 來生成提示,但你可以使用任何你想要的提示工程工具,甚至手動進行模板化)
如果簽名看起來有點奇怪,我們將在因素4 - 工具只是結構化輸出中討論這一點
function DetermineNextStep(thread: string) -> DoneForNow | ListGitTags | DeployBackend | DeployFrontend | RequestMoreInformation {
擁有你的提示的關鍵好處:
- 完全控制:編寫你的智能體需要的確切指令,沒有黑盒抽象
- 測試和評估:像對待任何其他代碼一樣,為你的提示構建測試和評估
- 迭代:根據實際表現快速修改提示
- 透明性:確切知道你的智能體在使用什么指令
- 角色黑客:利用支持用戶/助手角色非標準用法的API - 例如,現已棄用的OpenAI“completions” API的非聊天版本。這包括一些所謂的“模型誤導”技術
記住:你的提示是你應用程序邏輯和LLM之間的主要接口。
完全控制你的提示能為你提供生產級智能體所需的靈活性和提示控制。
我不知道什么是最好的提示,但我知道你想要嘗試一切的靈活性。
因素 3. 擁有你的上下文窗口
你不一定需要使用標準的基于消息的格式來向LLM傳遞上下文。
在任何給定的時刻,你向智能體中的LLM輸入的內容是:“到目前為止發生了什么,下一步是什么”
一切皆是上下文工程。LLM是無狀態函數,它們將輸入轉換為輸出。為了獲得最佳輸出,你需要給它們提供最佳輸入。
創建優秀的上下文意味著:
- 你給模型的提示和指令
- 你檢索的任何文檔或外部數據(例如RAG)
- 任何過去的狀態、工具調用、結果或其他歷史記錄
- 來自相關但獨立的歷史/對話的任何過去消息或事件(記憶)
- 關于應輸出何種結構化數據的指令
關于上下文工程
本指南的全部內容都是關于如何從今天的模型中獲取盡可能多的價值。值得注意的是,以下內容并未提及:
- 模型參數的更改,如 temperature、top_p、frequency_penalty、presence_penalty 等。
- 訓練你自己的補全或嵌入模型
- 微調現有模型
再次強調,我不知道將上下文交給LLM的最佳方式是什么,但我知道你需要有靈活性來嘗試一切。
標準與自定義上下文格式
大多數LLM客戶端使用類似這樣的標準基于消息的格式:
[
{
"role":"system",
"content":"You are a helpful assistant..."
},
{
"role":"user",
"content":"Can you deploy the backend?"
},
{
"role":"assistant",
"content":null,
"tool_calls":[
{
"id":"1",
"name":"list_git_tags",
"arguments":"{}"
}
]
},
{
"role":"tool",
"name":"list_git_tags",
"content":"{\"tags\": [{\"name\": \"v1.2.3\", \"commit\": \"abc123\", \"date\": \"2024-03-15T10:00:00Z\"}, {\"name\": \"v1.2.2\", \"commit\": \"def456\", \"date\": \"2024-03-14T15:30:00Z\"}, {\"name\": \"v1.2.1\", \"commit\": \"abe033d\", \"date\": \"2024-03-13T09:15:00Z\"}]}",
"tool_call_id":"1"
}
]
雖然這在大多數用例中效果很好,但如果你想真正從今天的LLM中榨取最大價值,你需要以最節省token和注意力的方式將你的上下文輸入到LLM中。
作為標準基于消息格式的替代方案,你可以構建自己的上下文格式,以優化你的特定用例。例如,你可以使用自定義對象,并根據需要將它們打包/展開到一個或多個用戶、系統、助手或工具消息中。
以下是一個將整個上下文窗口放入單個用戶消息中的示例:
[
{
"role":"system",
"content":"You are a helpful assistant..."
},
{
"role":"user",
"content":|
Here's everything that happened so far:
<slack_message>
From:@alex
Channel:#deployments
Text:Canyoudeploythebackend?
</slack_message>
<list_git_tags>
intent:"list_git_tags"
</list_git_tags>
<list_git_tags_result>
tags:
-name:"v1.2.3"
commit:"abc123"
date:"2024-03-15T10:00:00Z"
-name:"v1.2.2"
commit:"def456"
date:"2024-03-14T15:30:00Z"
-name:"v1.2.1"
commit:"ghi789"
date:"2024-03-13T09:15:00Z"
</list_git_tags_result>
what'sthenextstep?
}
]
模型可能會通過你提供的工具模式推斷出你在詢問它??下一步是什么?
?,但將其融入你的提示模板也無妨。
代碼示例
我們可以用類似這樣的方式來構建:
class Thread:
events: List[Event]
class Event:
# 可以只用字符串,也可以顯式定義 - 由你決定
type: Literal["list_git_tags", "deploy_backend", "deploy_frontend", "request_more_information", "done_for_now", "list_git_tags_result", "deploy_backend_result", "deploy_frontend_result", "request_more_information_result", "done_for_now_result", "error"]
data: ListGitTags | DeployBackend | DeployFrontend | RequestMoreInformation |
ListGitTagsResult | DeployBackendResult | DeployFrontendResult | RequestMoreInformationResult | string
def event_to_prompt(event: Event) -> str:
data = event.data if isinstance(event.data, str) \
else stringifyToYaml(event.data)
returnf"<{event.type}>\n{data}\n</{event.type}>"
def thread_to_prompt(thread: Thread) -> str:
return'\n\n'.join(event_to_prompt(event) for event in thread.events)
示例上下文窗口
以下是使用這種方法的上下文窗口可能的樣子:
初始Slack請求:
<slack_message>
From: @alex
Channel: #deployments
Text: Can you deploy the latest backend to production?
</slack_message>
列出Git標簽后:
<slack_message>
From: @alex
Channel: #deployments
Text: Can you deploy the latest backend to production?
Thread: []
</slack_message>
<list_git_tags>
intent: "list_git_tags"
</list_git_tags>
<list_git_tags_result>
tags:
- name: "v1.2.3"
commit: "abc123"
date: "2024-03-15T10:00:00Z"
- name: "v1.2.2"
commit: "def456"
date: "2024-03-14T15:30:00Z"
- name: "v1.2.1"
commit: "ghi789"
date: "2024-03-13T09:15:00Z"
</list_git_tags_result>
出錯并恢復后:
<slack_message>
From: @alex
Channel: #deployments
Text: Can you deploy the latest backend to production?
Thread: []
</slack_message>
<deploy_backend>
intent: "deploy_backend"
tag: "v1.2.3"
environment: "production"
</deploy_backend>
<error>
error running deploy_backend: Failed to connect to deployment service
</error>
<request_more_information>
intent: "request_more_information_from_human"
question: "I had trouble connecting to the deployment service, can you provide more details and/or check on the status of the service?"
</request_more_information>
<human_response>
data:
response: "I'm not sure what's going on, can you check on the status of the latest workflow?"
</human_response>
從這里開始,你的下一步可能是:
nextStep = await determine_next_step(thread_to_prompt(thread))
{
"intent": "get_workflow_status",
"workflow_name": "tag_push_prod.yaml",
}
XML風格的格式只是一個例子 - 關鍵是你可以構建自己的格式,使其符合你的應用程序需求。如果你有靈活性去嘗試不同的上下文結構以及存儲和傳遞給LLM的內容,你將獲得更好的質量。
擁有你的上下文窗口的關鍵好處:
- 信息密度:以最大化LLM理解的方式組織信息
- 錯誤處理:以幫助LLM恢復的格式包含錯誤信息。考慮在錯誤和失敗的調用解決后將其從上下文窗口中隱藏。
- 安全性:控制傳遞給LLM的信息,過濾掉敏感數據
- 靈活性:根據你學到的最佳實踐調整格式
- Token效率:優化上下文格式以提高token效率和LLM理解
上下文包括:提示、指令、RAG文檔、歷史、工具調用、記憶
記住:上下文窗口是你與LLM的主要接口。掌控如何結構化和呈現信息可以顯著提升你的智能體性能。
示例 - 信息密度 - 相同消息,更少的tokens:
Loom Screenshot 2025-04-22 at 09 00 56
不要聽我說的
在12因子智能體發布約2個月后,上下文工程開始成為一個相當流行的術語。
還有來自@lenadroid的相當不錯的上下文工程備忘單,發布于2025年7月。
這里反復出現的主題是:我不知道最好的方法是什么,但我知道你需要有靈活性來嘗試一切。
因素 4. 工具只是結構化輸出
工具不需要很復雜。在核心上,它們只是來自你LLM的結構化輸出,觸發了確定性的代碼。
例如,假設你有兩個工具 ??CreateIssue?
?? 和 ??SearchIssues?
?。要求一個LLM“使用多個工具中的一個”只是要求它輸出我們可以解析成代表這些工具的對象的JSON。
class Issue:
title: str
description: str
team_id: str
assignee_id: str
class CreateIssue:
intent: "create_issue"
issue: Issue
class SearchIssues:
intent: "search_issues"
query: str
what_youre_looking_for: str
這個模式很簡單:
- LLM輸出結構化的JSON
- 確定性代碼執行相應的操作(例如調用外部API)
- 捕獲結果并反饋到上下文中
這在LLM的決策和你的應用程序操作之間創建了一個清晰的分離。LLM決定做什么,但你的代碼控制如何做。僅僅因為LLM“調用了一個工具”,并不意味著你必須每次都以相同的方式執行一個特定的對應函數。
如果你還記得我們上面的switch語句
if nextStep.intent == 'create_payment_link':
stripe.paymentlinks.create(nextStep.parameters)
return # 或者你想要的任何內容,請參見下文
elif nextStep.intent == 'wait_for_a_while':
# 做一些單子操作,我不知道
else: #... 模型沒有調用我們知道的工具
# 做其他事情
注意:關于“純提示”與“工具調用”與“JSON模式”的好處以及每種方法的性能權衡,已經有很多討論。我們很快會鏈接一些關于這些內容的資源,但在這里不會深入探討。參見提示 vs JSON模式 vs 函數調用 vs 約束生成 vs SAP,我什么時候應該使用函數調用、結構化輸出或JSON模式? 和 OpenAI JSON vs 函數調用。
“下一步”可能不像“運行一個純函數并返回結果”那樣原子化。當你將“工具調用”視為模型輸出描述確定性代碼應該做什么的JSON時,你會解鎖很多靈活性。將這一點與因素8 擁有你的控制流結合起來。
因素 5. 統一執行狀態和業務狀態
即使在AI世界之外,許多基礎設施系統也試圖將“執行狀態”與“業務狀態”分開。對于AI應用,這可能涉及復雜的抽象來跟蹤當前步驟、下一步、等待狀態、重試次數等。這種分離會帶來復雜性,可能值得,但也可能對你的用例來說是過度設計。
一如既往,決定什么適合你的應用由你決定。但不要認為你必須將它們分開管理。
更明確地說:
- 執行狀態:當前步驟、下一步、等待狀態、重試次數等。
- 業務狀態:代理工作流到目前為止發生了什么(例如,OpenAI消息列表、工具調用和結果列表等)。
如果可能,盡量簡化——盡可能統一這些狀態。
實際上,你可以設計你的應用,以便從上下文窗口推斷出所有執行狀態。在許多情況下,執行狀態(當前步驟、等待狀態等)只是到目前為止發生的事情的元數據。
你可能有一些不能放入上下文窗口的東西,比如會話ID、密碼上下文等,但你的目標應該是盡量減少這些東西。通過擁抱因素3,你可以控制實際進入LLM的內容。
這種方法有幾個好處:
- 簡單性:所有狀態的單一事實來源
- 序列化:線程可以輕松地序列化/反序列化
- 調試:整個歷史記錄在一個地方可見
- 靈活性:只需添加新的事件類型即可輕松添加新狀態
- 恢復:只需加載線程即可從任何點恢復
- 分叉:通過將線程的某個子集復制到新的上下文/狀態ID中,可以在任何點分叉線程
- 人機接口和可觀測性:可以輕松將線程轉換為人類可讀的markdown或豐富的Web應用UI
因素 6. 使用簡單API啟動/暫停/恢復
智能體只是程序,我們對其啟動、查詢、恢復和停止的方式有相應的期望。
用戶、應用程序、管道和其他智能體應該能夠通過簡單的API輕松啟動智能體。
智能體及其協調的確定性代碼應該能夠在需要長時間運行操作時暫停智能體。
外部觸發器(如webhooks)應允許智能體從中斷處恢復,而無需與智能體協調器進行深度集成。
這與因素5 - 統一執行狀態和業務狀態和因素8 - 擁有你的控制流密切相關,但可以獨立實現。
注意 - 通常AI協調器允許暫停和恢復,但不能在工具選擇和工具執行的瞬間之間進行。另見因素7 - 使用工具調用聯系人類和因素11 - 從任何地方觸發,與用戶會面。
因素 7. 使用工具調用聯系人類
默認情況下,LLM API 依賴于一個基本的高風險 token 選擇:我們是返回純文本內容,還是返回結構化數據?
你把這個選擇的權重放在了第一個 token 上,對于 ??the weather in tokyo?
? 這種情況,它是
"the"
但在 ??fetch_weather?
? 的情況下,它是一些特殊 token 來表示 JSON 對象的開始。
|JSON>
你可能會通過讓 LLM 始終 輸出 json 獲得更好的結果,然后用一些自然語言 token 聲明其意圖,比如 ??request_human_input?
?? 或 ??done_for_now?
??(而不是像 ??check_weather_in_city?
? 這樣的“正式”工具)。
再次強調,你可能不會從這獲得任何性能提升,但你應該進行實驗,并確保你有自由嘗試奇怪的東西來獲得最佳結果。
class Options:
urgency: Literal["low", "medium", "high"]
format: Literal["free_text", "yes_no", "multiple_choice"]
choices: List[str]
# 用于人機交互的工具定義
class RequestHumanInput:
intent: "request_human_input"
question: str
context: str
options: Options
# 在代理循環中的示例用法
if nextStep.intent == 'request_human_input':
thread.events.append({
type: 'human_input_requested',
data: nextStep
})
thread_id = await save_state(thread)
await notify_human(nextStep, thread_id)
return# 中斷循環并等待帶有線程ID的響應
else:
# ... 其他情況
稍后,你可能會收到來自處理 Slack、電子郵件、短信或其他事件的系統的 webhook。
@app.post('/webhook')
def webhook(req: Request):
thread_id = req.body.threadId
thread = await load_state(thread_id)
thread.events.push({
type: 'response_from_human',
data: req.body
})
# ... 為簡潔起見,你可能不希望在這里阻塞 web worker
next_step = await determine_next_step(thread_to_prompt(thread))
thread.events.append(next_step)
result = await handle_next_step(thread, next_step)
# todo - 循環或中斷或任何你想要的
return {"status": "ok"}
上述內容包含了因素 5 - 統一執行狀態和業務狀態、因素 8 - 擁有你的控制流、因素 3 - 擁有你的上下文窗口和因素 4 - 工具只是結構化輸出以及其他幾個因素的模式。
如果我們使用因素 3 - 擁有你的上下文窗口中的 XML 風格格式,經過幾次交互后,我們的上下文窗口可能看起來像這樣:
(為簡潔起見省略)
<slack_message>
From: @alex
Channel: #deployments
Text: Can you deploy backend v1.2.3 to production?
Thread: []
</slack_message>
<request_human_input>
intent: "request_human_input"
question: "Would you like to proceed with deploying v1.2.3 to production?"
context: "This is a production deployment that will affect live users."
options: {
urgency: "high"
format: "yes_no"
}
</request_human_input>
<human_response>
response: "yes please proceed"
approved: true
timestamp: "2024-03-15T10:30:00Z"
user: "alex@company.com"
</human_response>
<deploy_backend>
intent: "deploy_backend"
tag: "v1.2.3"
environment: "production"
</deploy_backend>
<deploy_backend_result>
status: "success"
message: "Deployment v1.2.3 to production completed successfully."
timestamp: "2024-03-15T10:30:00Z"
</deploy_backend_result>
好處:
- 明確的指令:用于不同類型人機聯系的工具允許 LLM 提供更多具體信息
- 內循環與外循環:使代理工作流能夠超出傳統的 chatGPT 風格界面,其中控制流和上下文初始化可能是?
?Agent->Human?
? 而不是??Human->Agent?
?(想想由 cron 或事件觸發的代理) - 多個人訪問:可以通過結構化事件輕松跟蹤和協調來自不同人的輸入
- 多代理:簡單的抽象可以輕松擴展以支持?
?Agent->Agent?
? 的請求和響應 - 持久性:結合因素 6 - 使用簡單 API 啟動/暫停/恢復,這使得持久、可靠且可檢查的多人工作流成為可能
更多關于外循環代理的內容在這里
與因素 11 - 從任何地方觸發,與用戶會面配合使用效果很好
← 啟動/暫停/恢復 | 擁有你的控制流 →
因素 8. 擁有你的控制流
如果你擁有你的控制流,你就可以做很多有趣的事情。
構建適合你特定用例的自定義控制結構。具體來說,某些類型的工具調用可能是跳出循環并等待來自人類的響應或另一個長時間運行的任務(如訓練管道)的理由。你可能還想整合以下自定義實現:
- 工具調用結果的摘要或緩存
- 對結構化輸出的LLM評判
- 上下文窗口壓縮或其他內存管理
- 日志記錄、追蹤和指標
- 客戶端速率限制
- 持久性睡眠/暫停/"等待事件"
下面的示例展示了三種可能的控制流模式:
- request_clarification:模型請求更多信息,跳出循環并等待來自人類的響應
- fetch_git_tags:模型請求獲取git標簽列表,獲取標簽,附加到上下文窗口,并直接傳回給模型
- deploy_backend:模型請求部署后端,這是一件高風險的事情,因此跳出循環并等待人工批準
def handle_next_step(thread: Thread):
whileTrue:
next_step = await determine_next_step(thread_to_prompt(thread))
# 為了清晰起見內聯 - 實際上你可以將其放入一個方法中,使用異常進行控制流,或任何你想要的方式
if next_step.intent == 'request_clarification':
thread.events.append({
type: 'request_clarification',
data: nextStep,
})
await send_message_to_human(next_step)
await db.save_thread(thread)
# 異步步驟 - 跳出循環,我們稍后會收到一個webhook
break
elif next_step.intent == 'fetch_open_issues':
thread.events.append({
type: 'fetch_open_issues',
data: next_step,
})
issues = await linear_client.issues()
thread.events.append({
type: 'fetch_open_issues_result',
data: issues,
})
# 同步步驟 - 將新上下文傳遞給LLM以確定下一步
continue
elif next_step.intent == 'create_issue':
thread.events.append({
type: 'create_issue',
data: next_step,
})
await request_human_approval(next_step)
await db.save_thread(thread)
# 異步步驟 - 跳出循環,我們稍后會收到一個webhook
break
這種模式允許你根據需要中斷和恢復智能體的流程,從而創建更自然的對話和工作流。
示例 - 我對每個AI框架的最大功能請求是我們需要能夠中斷正在工作的智能體并稍后恢復,尤其是在工具選擇和工具調用時刻之間。
如果沒有這種級別的可恢復性/粒度,就無法在工具調用運行前進行審查/批準,這意味著你只能:
- 在等待長時間運行的任務完成時在內存中暫停任務(想想?
?while...sleep?
?),如果過程被中斷,則從頭開始重新啟動 - 將智能體限制為僅進行低風險、低風險的調用,如研究和摘要
- 允許智能體訪問進行更大、更有用的操作,并希望它不會搞砸
你可能會注意到這與因素5 - 統一執行狀態和業務狀態和因素6 - 使用簡單API啟動/暫停/恢復密切相關,但可以獨立實現。
← 返回 README
因素 9. 將錯誤壓縮到上下文窗口
這一點雖然有點簡短,但值得提及。智能體的好處之一是“自我修復”——對于短期任務,LLM可能會調用一個失敗的工具。好的LLM有很大幾率能夠讀取錯誤消息或堆棧跟蹤,并在后續的工具調用中找出需要更改的內容。
大多數框架都實現了這一點,但你可以在不進行其他11個因素的情況下僅實現這一點。以下是一個示例:
thread = {"events": [initial_message]}
whileTrue:
next_step = await determine_next_step(thread_to_prompt(thread))
thread["events"].append({
"type": next_step.intent,
"data": next_step,
})
try:
result = await handle_next_step(thread, next_step) # 我們的switch語句
except Exception as e:
# 如果出現錯誤,可以將其添加到上下文窗口并重試
thread["events"].append({
"type": 'error',
"data": format_error(e),
})
# 循環,或在此處執行其他操作以嘗試恢復
你可能希望為特定的工具調用實現一個錯誤計數器,以限制單個工具的嘗試次數約為3次,或根據你的用例實施其他邏輯。
consecutive_errors = 0
whileTrue:
# ... 現有代碼 ...
try:
result = await handle_next_step(thread, next_step)
thread["events"].append({
"type": next_step.intent + '_result',
data: result,
})
# 成功!重置錯誤計數器
consecutive_errors = 0
except Exception as e:
consecutive_errors += 1
if consecutive_errors < 3:
# 進行循環并重試
thread["events"].append({
"type": 'error',
"data": format_error(e),
})
else:
# 中斷循環,重置上下文窗口的部分內容,升級到人工,或執行其他操作
break
}
}
達到連續錯誤閾值可能是升級到人工的好時機,無論是通過模型決策還是通過控制流的確定性接管。
好處:
- 自我修復:LLM可以讀取錯誤消息并找出在后續工具調用中需要更改的內容
- 持久性:即使一個工具調用失敗,智能體也可以繼續運行
我確信你會發現,如果這樣做得太多,你的智能體會開始失控,并可能一遍又一遍地重復相同的錯誤。
這就是因素8 - 擁有你的控制流和因素3 - 擁有你的上下文構建發揮作用的地方——你不需要僅僅將原始錯誤放回,你可以完全重構其表示方式,從上下文窗口中移除先前的事件,或執行任何確定性操作來使智能體重回正軌。
但防止錯誤失控的首要方法是擁抱因素10 - 小而專注的智能體。
因素 10. 小而專注的智能體
與其構建試圖做所有事情的龐大智能體,不如構建小而專注、能做好一件事的智能體。智能體只是更大、主要是確定性系統中的一個構建模塊。
這里的關鍵見解是關于LLM的局限性:任務越大越復雜,需要的步驟就越多,這意味著上下文窗口會更長。隨著上下文的增長,LLM更有可能迷失或失去焦點。通過讓智能體專注于特定領域,最多3-10步,可能20步,我們就能保持上下文窗口的可管理性,從而保持LLM的高性能。
隨著上下文的增長,LLM更有可能迷失或失去焦點
小而專注的智能體的好處:
- 可管理的上下文:較小的上下文窗口意味著更好的LLM性能
- 明確的責任:每個智能體都有明確定義的范圍和目的
- 更好的可靠性:在復雜工作流中迷失的可能性更小
- 更易測試:更容易測試和驗證特定功能
- 改進的調試:在出現問題時更容易識別和修復
如果LLM變得更聰明了怎么辦?
如果LLM足夠聰明,能夠處理100步以上的工作流,我們還需要這樣做嗎?
簡而言之,是的。隨著智能體和LLM的進步,它們可能自然擴展到能夠處理更長的上下文窗口。這意味著能夠處理更大的DAG的更多部分。這種小而專注的方法確保你今天就能獲得結果,同時為隨著LLM上下文窗口變得更加可靠而慢慢擴展智能體范圍做好準備。(如果你之前重構過大型確定性代碼庫,你可能現在就在點頭了)。
,時長00:14
有意地控制智能體的大小/范圍,并且只以允許你保持質量的方式增長,是這里的關鍵。正如構建NotebookLM的團隊所說:
我覺得,對我來說,AI構建中最神奇的時刻總是當我真的、真的、真的接近模型能力的邊緣時
無論那個邊界在哪里,如果你能找到那個邊界并持續正確地做到,你就能構建出神奇的體驗。這里有很多護城河可以建立,但和往常一樣,它們需要一些工程嚴謹性。
因素 11. 從任何地方觸發,與用戶會面
如果你在等待 humanlayer 的推薦,你已經找到了。如果你正在執行因素6 - 使用簡單API啟動/暫停/恢復和因素7 - 使用工具調用聯系人類,你已經準備好融入這一因素。
允許用戶通過Slack、電子郵件、短信或他們想要的任何其他渠道觸發智能體。允許智能體通過相同的渠道進行響應。
好處:
- 與用戶會面:這有助于你構建感覺像真人或至少是數字同事的AI應用
- 外循環智能體:允許智能體由非人類觸發,例如事件、cron、故障或其他任何情況。它們可能工作5、20、90分鐘,但當它們到達關鍵點時,可以聯系人類尋求幫助、反饋或批準
- 高風險工具:如果你能夠快速引入各種人類,你可以讓智能體訪問更高風險的操作,如發送外部電子郵件、更新生產數據等。保持清晰的標準可以讓你對智能體進行審計并對其執行更大更好的事情充滿信心
因素 12. 讓你的智能體成為一個無狀態的歸約器
好吧,到目前為止我們已經有1000多行的markdown了。這一點主要是為了好玩。
本文轉載自???大模型自然語言處理?? 作者:Agent
