数据截至 (上游 commit eaf1040642b2)
Marvin — 总览:这是什么·全景·主线·阅读地图
30 秒导读: Marvin 是一个 Python 框架,让你用一句自然语言指令 + 一个 Python 类型,就能从 LLM 拿到经过校验、类型安全的结构化结果,并把这些调用编排成 agentic 工作流。它建立在 Pydantic 生态之上(用 pydantic-ai 真正跟模型对话),最巧的一招是:把"我要一个
int/ 一个Article"这件事,翻译成让模型去调用一个叫MarkTaskSuccessful的工具——拿结果 = 一次工具调用。
本章只做导览与全景:讲清"这是什么、大盘怎么转、一次 marvin.run 走完全程发生了什么",并给出阅读地图。机制细节一律留给各章(下钻链接在每处点出)。
1. 这是什么(零基础也能懂)
一句话定义: Marvin 把"向 LLM 要一个特定 Python 类型的答案"变成一个可声明、可观测、结果被校验过的任务(Task),再由一个编排器驱动模型直到任务完成。
解决什么问题 / 给谁用:
假设你在写 Python,想让 LLM 帮你"从这段乱七八糟的文字里抽出所有金额",或者"把这句话分类成某个部门",或者"研究一个主题、写一篇有标题和要点的文章"。你不想拿回一坨字符串然后自己解析、自己校验——你想直接拿到 list[int]、拿到一个 Enum 成员、拿到一个 Article 的 Pydantic 模型实例。Marvin 就是给这类人用的:需要把 LLM 的自由文本可靠地落成结构化、类型安全数据的 Python 工程师。
它能做什么(功能):
- 结构化输出工具:
cast(转成某类型)、classify(归类)、extract(抽取)、generate(按描述造数据)、summarize、fn(把普通函数变成 AI 函数)。 - agentic 控制流:
Task(声明一个目标 + 结果类型)、Agent(可复用的 LLM 配置 + 工具 + 记忆)、run(跑一个任务)。 - 编排与放大:
Thread(共享上下文与历史,落到 SQLite)、plan/run_tasks(把复杂目标拆成有依赖的多任务)、Team/Swarm(多智能体协作,已标记弃用,见 04)、Memory(持久记忆)。
用起来什么样: 最小示例都在顶层包里(见 README.md)。
import marvin
# 1) 一句话跑一个任务,默认拿字符串
poem = marvin.run("Write a short poem about artificial intelligence")
# 2) 要一个具体类型——这是 Marvin 的核心卖点
answer = marvin.run("the answer to the universe", result_type=int)
print(answer) # 42(一个真正的 int,不是 "42" 字符串)
# 3) 高层门面:转类型 / 归类 / 抽取 / 造数据
marvin.cast("the place with the best bagels", Location) # -> {'lat':..., 'lon':...}
marvin.classify("shut up and take my money", SupportDepartment) # -> SupportDepartment.SALES
marvin.extract("i found $30 ... bought 5 bagels for $10", int) # -> [30, 10]
# 4) 显式 Task / 复用的 Agent
task = marvin.Task(instructions="Write a limerick about Python", result_type=str)
poem = task.run()
writer = marvin.Agent(name="Poet", instructions="Write creative, evocative poetry")
haiku = writer.run("Write a haiku about coding")
一句话直觉/类比: 把 Marvin 想成 LLM 版的"带类型标注的函数调用"。你写 result_type=int,就像给函数写返回类型注解;Marvin 负责让模型"填对返回值",并在拿回来时用 Pydantic 校验。区别只是:中间那层不是编译器,而是一个被反复驱动的 LLM。
2. 顶层全景(它大概怎么转)
怎么读这张图: 从上到下是"抽象层级"由高到低。你从最上面的一个便利函数进去,它层层归约成一个 Task,交给 Orchestrator 反复驱动一个 pydantic-ai 的 agentlet(一次性小 agent),中间的对话与结果落到 Thread/SQLite,途中的每个事件广播给 Handlers。
高层门面 run / cast / classify / extract / generate / fn / summarize (src/marvin/fns/*)
│ 全都归约成……
▼
Task(目标 + result_type + tools) (src/marvin/tasks/task.py)
│ run_tasks_async 交给……
▼
Orchestrator —— 回合循环(while 未完成: run_once) (src/marvin/engine/orchestrator.py)
│ 每回合向 Actor 要一个……
▼
agentlet = pydantic_ai.Agent(临时构造,含工具 + EndTurn 输出类型) (src/marvin/agents/agent.py:get_agentlet)
│ .iter() 驱动模型,事件流经……
├──► Handlers(打印 / 队列 / 自定义) (src/marvin/handlers/*)
▼
Thread —— 对话历史 + LLM 调用记录,持久化到 SQLite (src/marvin/thread.py, database.py)
部件一句话职责:
| 部件 | 干什么 | 在哪个文件(符号) |
|---|---|---|
| 高层门面 | 便利函数,每个都把"我要 X 类型"包成一个 Task 再跑 | src/marvin/fns/run.py(run/run_async)、fns/cast.py 等 |
Task | 声明式的一个目标:instructions + result_type + tools + 状态 | src/marvin/tasks/task.py(Task) |
Orchestrator | 回合循环:收集就绪任务、装配工具、渲染系统提示、跑 agentlet、处理结束 | src/marvin/engine/orchestrator.py(Orchestrator.run / run_once) |
Actor / Agent | 可复用的 LLM 配置;get_agentlet 现造一个 pydantic-ai agent | src/marvin/agents/agent.py(Agent.get_agentlet) |
| agentlet | 真正跟模型对话的 pydantic-ai Agent,一回合用完即弃 | 由 pydantic_ai.Agent(...) 构造,见 agent.py:211 |
EndTurn 工具 | 把"结束回合并交结果"做成类型化工具,如 MarkTaskSuccessful | src/marvin/engine/end_turn.py(create_mark_task_successful) |
Thread | 共享上下文/历史,with 作用域,持久化到 SQLite | src/marvin/thread.py(Thread)、src/marvin/database.py |
Handlers | 消费执行途中的事件(打印、入队、自定义) | src/marvin/handlers/*、src/marvin/engine/events.py |
defaults | 默认 agent / 模型 / 记忆提供方(可临时覆盖) | src/marvin/defaults.py(defaults, override_defaults) |
一句话记住两条暗线(详见 §4):Marvin 自己几乎不跟模型说话——那是 pydantic-ai 干的,Marvin 是它的薄壳 + 编排层;而"当前是哪个 Thread / 哪个 Actor / 哪个 Orchestrator"这些不靠参数层层传递,靠 contextvars 隐式作用域。