Agent execution loop SDK: streaming events, tool dispatch, compaction, and session state for Anthropic-compatible model endpoints.
Project description
tada-core
tada-core 是 tada-agent 的多协议 agent 执行内核,负责驱动模型 ↔ 工具 ↔ hook 的运行循环,并向上层暴露统一的事件流与会话状态。
核心入口:AgentLoop。模型接入推荐通过 ModelSpec + build_model_client() 选择内置 adapter。
作为 SDK 引入
阶段 A(tada-agent 本地联调)推荐使用 sibling editable path:
[project]
dependencies = ["tada-core"]
[tool.uv.sources]
tada-core = { path = "../tada-core", editable = true }
团队内 CI 或跨仓验证可锁定 Git tag:
[project]
dependencies = ["tada-core"]
[tool.uv.sources]
tada-core = { git = "ssh://git@.../tada-core.git", tag = "v0.1.0" }
阶段 B 发布到 PyPI 后再切换为:
dependencies = ["tada-core>=0.1,<0.2"]
不要把本地 path 依赖写进生产环境配置。更完整的安装说明见 docs/integration/install.md。
负责什么
AgentLoop.run_turn()/run_turn_message()/continue_turn():单轮 agent 执行的 while 循环Event:运行过程的统一事件流(text/thinking delta、tool_use、permission、hook、compaction、usage、error 等)SessionState:跨轮可序列化的会话状态(messages、usage、metadata)ConversationMessage/ContentBlock:归一化 transcript(text、thinking、tool、image、file)ModelSpec+ adapter registry:轻量模型能力描述与多协议 adapter 选择- tools / permissions / hooks / compaction / budget:runtime 装配点
- 内置 adapter:
anthropic-messages、openai-responses、openai-completions、google-generative-ai、bedrock-converse-stream
不负责的事情(由上层实现):
- HTTP / WebSocket / CLI / UI
- 业务会话存储与鉴权
- human-in-the-loop 交互
- hint / 系统提示拼接 / 业务级上下文选择策略
- tool middleware(参数修复、错误捕获、流式 chunk 处理)
- 多 provider 路由、fallback、账号额度、成本估算/价格表(使用 token-router 或宿主)
- OAuth 登录/刷新
- 长期记忆 / 用户档案 / 跨会话记忆检索
- 具体文件上传、下载、权限和 channel 渲染
- multi-agent 编排
- 把
Event翻译成产品协议
安装
uv sync
最小使用
import asyncio
from tada_core import AgentLoop, SessionState
from tada_core.loop.model_client import OpenAICompatibleModelClient, make_echo_transport
loop = AgentLoop(
OpenAICompatibleModelClient(make_echo_transport(), streaming=True),
system_prompt=["You are a helpful assistant."],
)
state = SessionState()
async def main() -> None:
async for event in loop.run_turn(state, "hello"):
if event.type == "text_delta":
print(event.text, end="", flush=True)
print()
asyncio.run(main())
接真实模型(Anthropic via ModelSpec):
from tada_core import AgentLoop, ModelSpec, SessionState, build_model_client
spec = ModelSpec(
id="claude-sonnet-4-6",
provider="anthropic",
api="anthropic-messages",
base_url="http://localhost:8787",
)
loop = AgentLoop(
build_model_client(spec, api_key="token-router-key", streaming=True),
system_prompt=["You are a helpful assistant."],
)
或直接构造(v0.1 兼容):
from tada_core import AgentLoop, AnthropicModelClient, SessionState
loop = AgentLoop(
AnthropicModelClient(api_key="sk-ant-...", model="claude-sonnet-4-6"),
system_prompt=["You are a helpful assistant."],
)
核心能力
Event 事件流
AgentLoop.run_turn() 是一个异步生成器,按固定顺序 yield Event。所有事件类型通过 type 字段区分:
| type | 说明 |
|---|---|
turn_start |
一轮 turn 开始 |
text_start / text_delta / text_end |
助手文本块生命周期 |
thinking_start / thinking_delta / thinking_end |
reasoning/thinking 块生命周期 |
tool_input_delta |
工具参数 JSON 的增量片段 |
tool_use |
模型请求调用某工具(含 tool_use_id、tool_name、input_json) |
tool_result |
工具执行完成(含 output、is_error 标志) |
tool_progress |
长耗时工具通过 on_update 报告的中间进度 |
usage |
token 用量(input_tokens、output_tokens、cache_read_tokens、cache_creation_tokens) |
message_stop |
一次助手消息的逻辑结束(含 stop_reason) |
permission |
工具执行前的权限决策(allowed: bool、可选 reason) |
hook |
hook 参与循环时的观测事件(hook_name、phase、messages) |
compaction |
会话压缩摘要(strategy、removed_message_count、summary) |
error |
运行时错误(message、code) |
unsupported |
某能力当前不支持(feature、detail);当前 AgentLoop 主路径暂不会主动发射 |
loop_exit |
POST_TOOL_USE hook 请求当前 turn 优雅退出 |
steering |
POST_TOOL_USE hook 注入 steering messages 并跳过剩余工具 |
turn_end |
一轮 run_turn() 结束 |
事件顺序保证:tool_use → permission → tool_result;tool_progress 出现在对应最终 tool_result 之前。
SessionState
跨轮保存全部上下文,可序列化(Pydantic)。
state = SessionState()
# 字段:
state.messages # list[ConversationMessage] — 完整 transcript
state.usage # TokenUsage(input_tokens, output_tokens, cache_read_tokens, cache_creation_tokens)
state.metadata # dict[str, Any] — 供上层写入任意 KV
多轮对话只需将同一个 state 对象传给每次 run_turn()。
Multimodal turns 与 continue
from tada_core import ContentBlock, ConversationMessage, MessageRole
msg = ConversationMessage(
role=MessageRole.USER,
content=[
ContentBlock(type="text", text="describe this image"),
ContentBlock(type="image", source="base64", mime_type="image/png", data="..."),
],
)
async for event in loop.run_turn_message(state, msg):
...
# 从已有 tool_result 继续,不追加新的 user message
async for event in loop.continue_turn(state):
...
# LLM 调用前裁剪/注入 context
loop.set_context_transform(lambda messages: messages[-40:])
Tools
工具通过 ToolExecutor 接口挂载,与模型协议解耦:
from tada_core.contracts.tools import InMemoryToolExecutor, ToolDefinition
ex = InMemoryToolExecutor()
ex.register(
ToolDefinition(
name="echo",
description="Return input unchanged.",
parameters_schema={"type": "object"},
),
lambda inp: inp, # 接收 JSON 字符串,返回 JSON 字符串
)
loop.set_tool_executor(ex)
ToolDefinition:工具名、描述、JSON Schema 参数定义ToolExecutor.execute(tool_name, input_json) -> str:输入/输出均为 JSON 字符串- 未知工具或执行失败应抛
ToolError,AgentLoop 会将其转为ToolResultEvent(is_error=True)
Permissions
工具执行前调用 PermissionPolicy.authorize(),结果影响是否继续执行并产生 PermissionEvent:
from tada_core.contracts.permissions import DenyListPolicy
loop.set_permission_policy(DenyListPolicy(denied={"shell", "rm"}))
内置策略:
| 类 | 行为 |
|---|---|
AllowAllPolicy |
默认,放行所有工具 |
DenyListPolicy |
拒绝指定工具名集合(大小写不敏感) |
可实现 PermissionPolicy ABC 自定义策略。PermissionContext 可将 hook 结果注入权限判断。
Runtime Budget
BudgetLimits 提供 provider-agnostic 的运行时预算控制:token、迭代次数、工具调用次数。它不包含价格表、provider 计费规则或账号额度。
from tada_core import BudgetLimits
loop.set_budget_policy(BudgetLimits(max_total_tokens=100_000, max_tool_calls=20))
如果宿主需要成本上限,应在 token-router 或宿主层把 UsageEvent 转成成本,再决定是否继续调用。
Hooks
Hook 在工具执行前后介入,可观测、拒绝、替换工具输入:
from tada_core.contracts.hooks import FunctionHookRunner, HookResult
def pre(tool_name: str, input_json: str) -> HookResult:
if tool_name == "dangerous":
return HookResult(denied=True, messages=["blocked"])
return HookResult.allow()
loop.set_hook_runner(FunctionHookRunner(pre=pre))
当前 AgentLoop 会调用这些 hook 阶段:
| 阶段 | 触发时机 |
|---|---|
ON_TURN_START |
run_turn() 入口 |
PRE_LLM_CALL |
每次模型调用前,可替换 message 列表 |
POST_LLM_CALL |
每次模型响应后,可观测文本与工具名 |
PRE_TOOL_USE |
工具执行前 |
POST_TOOL_USE |
工具成功执行后 |
POST_TOOL_USE_FAILURE |
工具执行失败后 |
ON_TURN_END |
run_turn() 退出前 |
HookRunner 也定义了 ON_SESSION_START,但当前 AgentLoop 还没有调用这个阶段。
HookResult 关键字段:
denied / failed / cancelled:停止当前执行updated_input:替换传给工具的 input(优先于原始输入)updated_messages:在PRE_LLM_CALL阶段替换传给模型的消息列表exit_loop:在工具完成后请求结束当前 turnsteering_messages:注入新的消息并让模型重新规划messages:出现在HookEvent.messages中,供上层展示
Compaction
会话超长时默认启用结构化摘要压缩。AgentLoop 会把 provider 返回的单次完整输入归一化为
TokenUsage.context_input_tokens。post_llm 优先使用该真值判断是否需要压缩;
pre_turn、post_turn 和没有有效 usage 的场景估算当前请求,避免复用已经过期的 usage。
from tada_core import CompactionConfig, StructuredSummaryCompaction, TruncateCompaction
# 默认:StructuredSummaryCompaction(model)
loop = AgentLoop(model)
# 显式关闭 core 默认压缩
loop = AgentLoop(model, compaction="disabled")
# hook 接管:core 不主动压缩,但仍允许 hook 改写 messages
loop = AgentLoop(model, compaction=CompactionConfig(mode="hook_only"))
# 自定义策略
loop.set_compaction_strategy(TruncateCompaction(max_messages=20))
StructuredSummaryCompaction 的阈值默认从 ModelSpec.context_window * 0.7 派生;
宿主显式传入 threshold_tokens 时优先使用显式值。摘要请求会复用主对话
system_prompt 和 tools,让 Anthropic 显式 cache、OpenAI 隐式 prefix cache 等
provider cache 机制自然命中。
内置策略:
| 类 | 行为 |
|---|---|
NeverCompact |
不压缩 |
TruncateCompaction |
丢弃最旧消息,保留最近 N 条 |
SummarizeCompaction |
简单截断 + 占位摘要(适合测试) |
ModelSummaryCompaction |
低级 LLM 摘要策略,保留兼容 |
StructuredSummaryCompaction |
默认高质量结构化摘要,保留 head/tail、维护 tool pair、写入 manifest |
Hook 接管压缩时可返回:
HookResult(
updated_messages=compacted_messages,
compaction_handled=True,
skip_builtin_compaction=True,
)
这样可以避免 tada-core 默认策略与宿主(例如 tada-agent 现有
MemoryCompactionHook)发生双重压缩。更完整的语义、成本和 provider cache 行为见
docs/reference/compaction.md。
ModelClient
两种内置客户端:
AnthropicModelClient(推荐,tada-agent 默认):
from tada_core import AnthropicModelClient
client = AnthropicModelClient(
api_key="sk-ant-...",
model="claude-sonnet-4-6",
base_url="", # 默认 https://api.anthropic.com;可设为 OpenRouter、token-router 等
streaming=True,
max_tokens=8192,
extra_headers={}, # 例如 OpenRouter 的 HTTP-Referer
cache_retention="short", # "none" | "short" | "long"
)
- 不依赖
anthropicSDK,直接用httpx - 兼容任何实现 Anthropic Messages API 格式 (
/v1/messages) 的端点 cache_retention="long"仅在base_url命中api.anthropic.com时发送 1h TTL;token-router / LiteLLM 等代理默认使用 short retention,且只打 content block 级cache_control
OpenAICompatibleModelClient(echo 测试 / 非 Anthropic 端点):
from tada_core.loop.model_client import OpenAICompatibleModelClient, make_echo_transport
# echo 模式(无需 API key,用于测试 loop 本身)
client = OpenAICompatibleModelClient(make_echo_transport(), streaming=True)
自定义 transport 可对接任何 OpenAI chat/completions 格式端点。
验证层级
tada-core 的功能验证分三层:
Layer 1 单元/契约测试(tests/conformance/)
stub model,纯逻辑,< 0.1s,CI 强制通过
uv run pytest tests/
Layer 2 真实 LLM 手动烟雾测试(scripts/smoke.py)
AgentLoop + 真实 endpoint,手动跑,需要 API key
验证 text_delta / tool_use / permission / usage 等完整事件链路
Layer 3 交互式 UI 验证(agent-apps/agent-studio/)
FastAPI backend + Next.js 前端,在浏览器里看多 Agent 事件流、产物和 SessionState
Layer 1:单元测试
uv run pytest tests/
uv run ruff check tada_core tests scripts
测试覆盖:
tests/conformance/test_agent_loop.py:AgentLoop 完整能力(工具、权限、hook、compaction、abort)tests/conformance/test_anthropic_client.py:Anthropic 消息序列化与流式解析(offline)tests/conformance/test_contract_semantics.py:SessionState、事件顺序、permission contexttests/conformance/test_event_sequences.py:事件序列顺序保证tests/conformance/test_clawcode_model_client.py:OpenAI-compatible 客户端解析
Layer 2:烟雾测试
scripts/smoke.py 是一个独立脚本,直接驱动 AgentLoop,把每个 Event 打印出来。
# echo 模式(无需 API key,验证 loop 本身)
uv run python scripts/smoke.py
# 接真实模型(Anthropic direct 或任何兼容代理)
API_KEY=<key> \
BASE_URL=https://your-proxy-host \
MODEL_ID=claude-sonnet-4-6 \
uv run python scripts/smoke.py
# 多轮对话(每个 --text 是一轮,共享同一个 SessionState)
API_KEY=<key> MODEL_ID=claude-haiku-4-5-20251001 \
uv run python scripts/smoke.py --text "what is 2+2" --text "multiply that by 3"
环境变量说明:
| 变量 | 说明 |
|---|---|
API_KEY |
API key,不填则 echo 模式 |
MODEL_ID |
模型 ID,默认 claude-sonnet-4-6 |
BASE_URL |
接入端点,默认 https://api.anthropic.com |
PROTOCOL |
anthropic(默认)或 openai |
Layer 3:交互式 UI 验证
bash agent-apps/agent-studio/dev.sh
打开 http://127.0.0.1:3010,在 Agent Studio 里选择 agent、发送消息,并在 Inspector 中观察 trace、artifact 和运行状态。
不配置模型则使用 echo 模式,在"设置"页填入 API key 后走真实 LLM。
目录结构
tada_core/
contracts/ 纯数据结构与接口(无 IO、无网络)
events.py Event union(15 种事件类型)
session.py SessionState、TokenUsage
messages.py ConversationMessage、ContentBlock、MessageRole
model.py ModelClient ABC、ApiRequest、AssistantEvent
tools.py ToolDefinition、ToolExecutor ABC、InMemoryToolExecutor
permissions.py PermissionPolicy ABC、AllowAllPolicy、DenyListPolicy
hooks.py HookRunner ABC、FunctionHookRunner、HookResult
compaction.py CompactionStrategy ABC、4 种内置策略
loop/ AgentLoop 执行循环 + model clients
runner.py AgentLoop(唯一的 while 循环实现)
model_client.py AnthropicModelClient、OpenAICompatibleModelClient
extensions/ 可选扩展(MCP)
cli/ echo-only 本地调试入口(tada-core-smoke)
scripts/
smoke.py Layer 2 烟雾测试脚本
tests/
conformance/ Layer 1 契约测试
agent-apps/
agent-studio/ 多 Agent 统一宿主(推荐)
backend/ FastAPI + 插件 registry
frontend/ 三栏 UI:Chat / Trace / Artifact
shared/
sandbox/ coding-agent 复用的 sandbox 包、fixtures、runtime 与回归测试
agent-studio/dev.sh 推荐启动入口
docs/ 架构与接入文档
AGENTS.md AI / coding agent 机器可读入口
文档
docs/README.md:文档地图AGENTS.md:AI agent 接入入口
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file tada_core-0.8.5.tar.gz.
File metadata
- Download URL: tada_core-0.8.5.tar.gz
- Upload date:
- Size: 65.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ea0988861493aaebb9042aac598705bbf369bdf1b5372d026b5b25893c48e08e
|
|
| MD5 |
8f6f6421479142c0105edb2babe0a660
|
|
| BLAKE2b-256 |
baa725ee38b61a6d970ab7ec7d633750c30c8c95e686fec88e30c9b961eb1901
|
Provenance
The following attestation bundles were made for tada_core-0.8.5.tar.gz:
Publisher:
release.yml on MazeAI-pro/tada-core
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
tada_core-0.8.5.tar.gz -
Subject digest:
ea0988861493aaebb9042aac598705bbf369bdf1b5372d026b5b25893c48e08e - Sigstore transparency entry: 2167950770
- Sigstore integration time:
-
Permalink:
MazeAI-pro/tada-core@e94fc13bbda22f77677059ceb3f5526f669b04d2 -
Branch / Tag:
refs/tags/v0.8.5 - Owner: https://github.com/MazeAI-pro
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@e94fc13bbda22f77677059ceb3f5526f669b04d2 -
Trigger Event:
push
-
Statement type:
File details
Details for the file tada_core-0.8.5-py3-none-any.whl.
File metadata
- Download URL: tada_core-0.8.5-py3-none-any.whl
- Upload date:
- Size: 79.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
efbe1c103acf2063f36f8e6098da1b54f029880122932759716d3e217f70f378
|
|
| MD5 |
4ca07dc48890cf162ed203dbe25fa737
|
|
| BLAKE2b-256 |
a56d472565fcc26cbf036125710f18be8d9143f054411c23c3ef2706ada60d9f
|
Provenance
The following attestation bundles were made for tada_core-0.8.5-py3-none-any.whl:
Publisher:
release.yml on MazeAI-pro/tada-core
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
tada_core-0.8.5-py3-none-any.whl -
Subject digest:
efbe1c103acf2063f36f8e6098da1b54f029880122932759716d3e217f70f378 - Sigstore transparency entry: 2167950868
- Sigstore integration time:
-
Permalink:
MazeAI-pro/tada-core@e94fc13bbda22f77677059ceb3f5526f669b04d2 -
Branch / Tag:
refs/tags/v0.8.5 - Owner: https://github.com/MazeAI-pro
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@e94fc13bbda22f77677059ceb3f5526f669b04d2 -
Trigger Event:
push
-
Statement type: