数据截至 (上游 commit 2e970421f2b0)
驱动与 provider 中立:PromptStack、Message 与统一工具协议
30 秒导读: Griptape 里,上层逻辑(任务、工具、记忆)从不直接碰 OpenAI/Anthropic 的 SDK。它们只跟一套 provider 无关的中间表示打交道——
PromptStack(一叠消息)、Message(一条消息)、MessageContent(消息里的一块内容)。真正懂某家 API 的只有一个BasePromptDriver子类。于是"换供应商"退化成"换一个 driver 对象",代码里就是配置里的一行。
本章讲清一件事:为什么在 Griptape 里换 LLM 供应商只需要改一行。 我们先讲这套抽象是什么、长什么样,再拆开 driver 基类的模板方法,最后看三个可切换开关和配置层怎么把它们装配起来。
不重复的部分:ReAct 文本协议怎么解析,见 02-prompt-task-agent-loop.md;各家 provider driver 的逐行实现不展开,本章只讲基类契约 + 抽象点。
1. 这是什么(零基础也能懂)
1.1 一句话定义
Griptape 的"驱动层"是一层翻译官。 上层代码用一种统一的中间语言描述"我要跟大模型说什么、它回了什么";每家供应商配一个专职翻译官(driver),负责把中间语言翻成该家 API 的方言、再把回复翻回来。
1.2 它解决什么问题
假设你写了一个 agent,用 OpenAI 跑通了。现在老板说:"改用 Anthropic,便宜。" 如果你的代码里到处是 openai.chat.completions.create(...)、到处按 OpenAI 的 JSON 结构拼 messages,那这是一次大手术。
Griptape 的答案:上层根本不认识 OpenAI。它只认识 PromptStack。切供应商时你换掉的是装配好的 driver,不是业务代码。
1.3 用起来什么样
下面这段是真实可跑的用法,注意换供应商只动了一行:
# 示意用法,展示 provider 中立
from griptape.structures import Agent
from griptape.configs import Defaults
from griptape.configs.drivers import AnthropicDriversConfig
# 默认走 OpenAI —— 什么都不配就是它
agent = Agent()
agent.run("讲个冷笑话")
# 想换 Anthropic?改这一行全局默认,业务代码一字不动
Defaults.drivers_config = AnthropicDriversConfig()
agent = Agent()
agent.run("讲个冷笑话") # 现在走 claude
Agent、run("讲个冷笑话")、工具、记忆——全程没出现任何一家供应商的名字。
1.4 一句话直觉
把 PromptStack 想成通用集装箱:货物(你的提示词、图片、工具调用)按标准尺寸打包。港口(driver)只管把集装箱装上不同船公司(OpenAI/Anthropic/…)的船——货物本身不用重新包装。
2. 顶层全景(它大概怎么转)
一次调用的数据流:上层把各种 Artifact 塞进 PromptStack → driver 的 run() 模板跑重试、发事件、调用子类的 try_run → 子类把中间表示翻成 provider 请求、拿回结果再翻回一个 Message。
上层(Task / Agent)
│ add_user_message(TextArtifact / ImageArtifact / ...)
▼
┌─────────────────────────────────────────────┐
│ PromptStack (provider 无关的中间表示) │
│ messages: [Message, Message, ...] │
│ tools: [BaseTool, ...] │
│ output_schema │
└───────────────┬───────────────────────────────┘
│ driver.run(prompt_stack)
▼
┌──────── ─────────────────────────────────────┐
│ BasePromptDriver (统一模板 · 抽象基类) │
│ · 重试 + before/after 发事件 │
│ · 抽象 try_run / try_stream ◄── 子类填空 │
└───────────────┬───────────────────────────────┘
│
┌──────────┴───────────┐ 各 provider 子类
▼ ▼ ▼
OpenAiChat Anthropic Cohere / Google / Bedrock ...
│ 翻成该家 API 请求 → 调用 → 翻回 Message
▼
┌─────────────────────────────────────────────┐
│ Message (provider 无关的回复) │
│ content: [TextMessageContent, │
│ ActionCallMessageContent, ...] │
└─────────────────────────────────────────────┘
各部件一句话职责:
| 部件 | 干什么 | 在哪个文件 |
|---|---|---|
PromptStack | 一叠 Message + 可用工具 + 输出 schema,上层唯一要拼的东西 | common/prompt_stack/prompt_stack.py |
Message | 一条消息:一个 role + 一列 MessageContent | common/prompt_stack/messages/message.py |
BaseMessageContent | 消息里的一"块"内容(文本/图片/音频/工具调用/工具结果) | common/prompt_stack/contents/base_message_content.py |
ToolAction | provider 无关地描述"模型要调哪个工具、传什么参" | common/actions/tool_action.py |
BasePromptDriver | 所有 driver 的模板 + 契约,定义抽象翻译点 | drivers/prompt/base_prompt_driver.py |
DriversConfig / Defaults | 把具体 driver 装配成全局默认,切供应商就切这里 | configs/drivers/*、configs/defaults_config.py |