章末自测

题目 1:为什么第一版 Agent API 使用 POST,而不是 GET?

请结合请求体、任务语义和后续扩展说明,并解释为什么 message 更适合放 JSON Body。

Agent Chat 表示向服务端提交一个需要执行的任务,会触发模型调用、Tool Calling 和 Run 创建,因此更符合 POST 的语义。任务内容可能很长,后续还可能增加 session_id、workspace、metadata 等字段,使用 JSON Body 更容易扩展。GET 更适合读取资源或查询状态,而不是提交复杂执行请求。

题目 2:answer、run_id、status 三个响应字段分别解决什么问题?

请说明为什么即使当前 API 是同步执行,也值得返回 run_id。

answer 是用户真正需要的 Agent 最终结果;run_id 是这一次执行的唯一标识,可用于关联日志、Trace、性能指标和未来状态查询;status 表示任务当前生命周期状态。即使当前同步执行后只返回 completed,run_id 仍然能帮助排查某一次具体请求,并为下一节正式 RunManager 保持协议兼容。

题目 3:编程实现——实现 POST /api/v1/chat

要求接收 JSON 的 message,缺失返回 400,成功返回 answer、run_id、status。

题目 4:为什么参数校验应该放 Flask / Service,而不是让 LLM 判断?

请结合“message 为空”这个例子说明确定性程序逻辑和 Agent 决策的边界。

message 是否存在、是否为空是完全确定的程序条件,用普通 if 判断即可,不需要模型参与。若把这类校验交给 LLM,不仅增加 Token、延迟和成本,还可能产生不稳定行为。Agent 更适合处理需要语义判断和动态决策的问题;格式、类型、必填字段、权限等确定性规则应由程序和中间件控制。

题目 5:为什么 Route 不应该越来越长?

请说明当 Session、Run、Memory、Trace 加入以后,直接把所有逻辑写进 Route 会带来什么问题。

如果 Route 同时负责请求解析、Session、Run、Context、模型、Tool、Memory、Trace 和错误处理,它会成为一个高度耦合的大函数。后续 CLI、飞书、测试无法复用业务逻辑,修改 HTTP 协议也可能影响 Agent Runtime。通过 Route → AgentService → AgentEngine 分层,可以让网络层、业务协调层和运行核心分别演进。

题目 6:如何验证 Agent 已经真正升级成 Backend Service?

请给出至少两种独立于服务器进程的调用方式。

可以在另一个终端使用 curl 发送 POST /api/v1/chat,也可以写一个独立 Python 客户端用 requests 调用。前端 JavaScript 的 fetch、Postman 或其他后端 HTTP Client 也可以。关键标准是调用方不需要 import AgentEngine,也不需要处于同一个 Python 进程,只需要遵循 HTTP API Contract 即可获得结果。

题目 7:这一节的 run_id 和下一节完整 Run 有什么区别?

请解释为什么现在只是标识符,下一节才是生命周期对象。

本节的 run_id 只是每次请求生成的唯一字符串,用来标识“这是哪一次执行”。它还没有对应的状态对象、开始时间、结束时间、Step、错误和查询接口。下一节会正式设计 Run,将 run_id 关联到 READY、RUNNING、COMPLETED、FAILED 等状态,并提供完整任务生命周期管理。因此本节是在 API 层提前预留标识,下一节才补齐运行模型。

题目 8:为什么 {"message": 123} 不应该被接口接受?

请说明强制 str() 转换会掩盖什么问题,并给出正确校验顺序。

API Contract 约定 message 是字符串。直接调用 str(123) 会把调用方的类型错误悄悄变成合法任务,客户端以为自己的请求格式正确,后续协议也更难收紧。Route 应先确认 Body 是字典,再确认 message 是 str,最后执行 strip() 并检查非空;任何一步不满足都返回结构化 400。

题目 9:应用工厂为什么比模块级全局 App 更容易测试?

create_chat_app(engine=...) 每次可以创建隔离的 Flask App,并注入假的 AgentEngine。测试无需启动网络端口或调用真实模型,就能构造成功、参数错误和内部异常;不同测试的配置和状态也不必共用。生产入口仍然可以调用同一个工厂创建正式应用。

进一步阅读

  • Flask Routing:理解 @app.post()、request、jsonify 的基本用法。
  • HTTP Status Code:重点掌握 200、400、404、500 的常见语义。
  • REST API Contract:理解为什么请求和响应结构应该提前稳定设计。
  • Service Layer Pattern:继续理解 Route → Service → Engine 分层。
  • UUID / Unique ID:理解 run_id、session_id 为什么需要唯一标识。
  • API Error Design:研究结构化 error code 与 message 的区别。
  • curl / HTTP Client:熟练掌握独立调用 API 的方法。
  • WSGI:后面部署 Flask Agent Server 时会继续涉及。

术语表

  • Agent API:把 Agent Runtime 暴露给外部客户端的 HTTP 接口,使前端、App、飞书或其他服务可以通过网络提交任务并获取结果。
  • API Contract:调用双方约定的请求字段、响应字段、状态码和错误结构,是服务稳定演进的重要边界。
  • POST:HTTP Method 之一,适合提交任务、创建资源或触发执行。本节用它提交 Agent Chat 任务。
  • run_id:一次 Agent 执行的唯一标识。本节先作为响应字段返回,下一节会扩展为完整 Run 生命周期的主键。
  • status:描述任务当前状态的字段。本节成功统一为 completed,后续会扩展 ready、running、failed、waiting_approval 等状态。
  • JSON Request Body:客户端发送给服务端的结构化请求内容,本节主要包含 message。
  • 结构化错误(Structured Error):使用稳定 code 和 message 返回错误,而不是把内部 Python Exception 或 traceback 直接暴露给调用方。
  • Backend Service:可以被外部客户端通过网络调用的后端能力。Agent 从本地程序变成 Backend Service 后,不再要求调用方与 Agent 运行在同一个 Python 进程。
  • request_id:标识一次 HTTP 请求并关联客户端错误、服务端日志和 Trace 的 ID。客户端重试时通常会产生新的 request_id。
  • 应用工厂(Application Factory):通过函数创建和配置 Flask App 的模式,便于注入 Engine、隔离测试配置,并避免导入模块时意外启动服务。
  • Flask Test Client:Flask 提供的进程内 HTTP 测试工具,可以在不监听真实端口的情况下验证 Route、状态码和 JSON 响应。
  • 输入规范化(Input Normalization):在类型校验通过后,对输入做去除首尾空白等确定性处理。规范化不能代替类型校验。

本章总结

如果你想把这一章的主线再压缩成一句话,其实可以这么理解:先把 Agent 能力抽成稳定的执行内核,再通过 Route 和 Service 把它变成可被外部调用的服务。 前面几张图分别从 Contract、分层、任务执行、重试和 Web 迁移几个角度,把这条路径拆开讲清楚了。

这一章学完,最该建立起来的不是某个 Flask 语法细节,而是一种服务化思维:什么该放在 Route,什么该放在 Service,什么应该继续留在 Engine;哪些字段是现在就该保留的,哪些问题是上线后一定会遇到的。等你后面继续做 Session、Run、异步执行、权限和部署时,这套框架会越来越有用。

本章总结

这一节,我们第一次真正把 Agent 从本地程序升级成了 Backend Service。

之前的调用方式:

agent.run(
    "检查这个 Python 项目"
)

只适合当前 Python 进程。

现在我们正式实现:

POST /api/v1/chat

请求:

{
  "message": "检查这个 Python 项目"
}

响应:

{
  "answer": "...",
  "run_id": "...",
  "status": "completed"
}

这意味着:

curl
前端
App
飞书
其他服务

都可以通过 HTTP 调用 Agent。

这一节最重要的第一点是,先设计 API Contract,再写代码。

请求字段、响应字段、错误格式、HTTP Status 都应该有稳定约定。

“稳定”也包括失败路径:非法 JSON、非对象 Body、错误字段类型和内部执行异常要能区分;对外只给稳定错误,对内用 request_id 留下完整诊断信息。

第二点是,Flask Route 必须尽量保持薄。

我们的结构是:

Flask Route
  ↓
AgentService
  ↓
AgentEngine

Route 负责 HTTP。

Service 负责业务协调。

Engine 继续负责:

Main Loop
Context
Tools
Memory
State

这样前面整个 Harness 不需要因为服务化而重写。

第三点是,run_id 从第一版接口就值得存在。

哪怕当前是同步执行,run_id 仍然可以帮助:

日志关联
Trace
错误排查
性能统计

而下一节,我们会把它升级成真正的:

Run

第四点是,能被 curl 调用,不代表已经完成生产化。

这一节只是打通:

Client
 ↓
HTTP
 ↓
Flask
 ↓
AgentService
 ↓
AgentEngine

课程项目现在还提供了一个与本节完全对应的 minimal_chat_api.py。它使用应用工厂和可替换 Engine,并通过自动测试固定输入校验、请求标识和异常隐藏;综合 app.py 则保留给后续 Run、Streaming 与 SQLite 章节继续展开。

下一步,我们还要解决真正长任务的生命周期。

因为一个 Agent Run 不只是:

开始
完成

它还会经历:

READY
RUNNING
COMPLETED
FAILED

并且需要:

start_time
end_time
steps
error

所以下一节,我们会正式实现,Run:每一次 Agent 任务都应该有生命周期。

到那时:

run_id

就不再只是一个字符串,而会成为整个 Agent Backend 里最重要的任务对象之一。

© 版权声明
THE END
喜欢就支持一下吧
点赞9 分享
评论 抢沙发
头像-大赛人网
欢迎您留下宝贵的见解!
提交
头像-大赛人网

昵称

取消
昵称

    请登录后查看评论内容