执行摘要
抽取 Adapter 基类,封装会话生命周期管理
消除 anthropic.py 与 openai.py 之间重复的模块级状态变量和函数,提供统一的面向接口的会话管理方式,降低新增适配器的门槛并提高可维护性。
该 PR 值得核心开发者精读,尤其是 BaseAdapter 的设计模式和类型安全 AppKey 的使用。外部用户若已使用 agent 适配器应尽快迁移。
消除 anthropic.py 与 openai.py 之间重复的模块级状态变量和函数,提供统一的面向接口的会话管理方式,降低新增适配器的门槛并提高可维护性。
该 PR 值得核心开发者精读,尤其是 BaseAdapter 的设计模式和类型安全 AppKey 的使用。外部用户若已使用 agent 适配器应尽快迁移。
slime/agent/adapters/common.py):新增 BaseAdapter 类,封装 store、inflight、closed 等状态和 open_session / shutdown_session / finish_session(抽象)方法;定义 ADAPTER_KEY、TOKENIZER_KEY 等类型安全的 web.AppKey,替换原有的字符串键。slime/agent/adapters/anthropic.py):将原有的 _Store、_inflight、_closed 模块变量移除,新类 AnthropicAdapter 继承 BaseAdapter,__init__ 调用基类构造后添加路由,finish_session 实现子代理和 final 段的合并。slime/agent/adapters/openai.py):类似地迁移为类,finish_session 仅处理最终段。slime/agent/adapters/__init__.py):导出 AnthropicAdapter、OpenAIAdapter、BaseAdapter。examples/coding_agent_rl/generate.py):使用 AnthropicAdapter 实例替代模块函数调用,调用 adapter.open_session / adapter.finish_session。tests/test_agent_adapters.py、tests/test_agent_sdk_adapters.py):适配新类语法,使用 adapter.app 创建 TestServer。docs/*/get_started/agent.md 等):更新为新的类使用方式。| 文件 | 模块 | 状态 | 重要度 |
|---|---|---|---|
slime/agent/adapters/common.py |
核心 | modified | 8.19 |
slime/agent/adapters/anthropic.py |
适配器 | modified | 8.65 |
slime/agent/adapters/openai.py |
适配器 | modified | 8.55 |
examples/coding_agent_rl/generate.py |
示例 | modified | 6.69 |
slime/agent/adapters/common.py
core-logic
新增 BaseAdapter 基类和应用键,是所有适配器共用的生命周期管理核心。
class BaseAdapter:
"""Base HTTP adapter with per-instance session lifecycle state."""
session_cls: type
def __init__(self, *, tokenizer, sglang_url, tool_parser=None, reasoning_parser=None) -> None:
# 每个实例拥有独立的会话存储、inflight 任务集和关闭标记,
# 替代原来模块级的 _Store、_inflight、_closed 变量。
self.store: dict[str, Any] = {}
self.inflight: dict[str, set[asyncio.Task]] = {}
self.closed: set[str] = set()
self.app = web.Application(client_max_size=64 * 1024 * 1024)
# 使用类型安全的 AppKey 代替字符串 key,IDE 可获得更好的补全和类型检查。
self.app[ADAPTER_KEY] = self
self.app[TOKENIZER_KEY] = tokenizer
self.app[SGLANG_URL_KEY] = sglang_url.rstrip("/") if isinstance(sglang_url, str) else sglang_url
self.app[TOOL_PARSER_KEY] = tool_parser
self.app[REASONING_PARSER_KEY] = reasoning_parser
def open_session(self, sid: str, *, sampling_defaults: dict | None = None, max_context_tokens: int = 0) -> None:
# 委托给公共的 register_session 函数,但通过实例的 store 和 session_cls 完成注册。
register_session(self.store, sid, self.session_cls, sampling_defaults=sampling_defaults, max_context_tokens=max_context_tokens)
async def shutdown_session(self, sid: str, *, wait_timeout: float = 5.0) -> None:
# 关闭会话时先取消所有 in-flight 任务,然后标记 closed。
await shutdown_session_tasks(sid, self.closed, self.inflight, wait_timeout=wait_timeout)
async def finish_session(self, sid: str, *, wait_timeout: float = 5.0) -> list[TokenSegment]:
# 抽象方法,子类需实现如何从 store 中提取并合并 TokenSegment。
raise NotImplementedError
slime/agent/adapters/anthropic.py
core-logic
AnthropicAdapter 继承 BaseAdapter,实现会话结束时子代理和最终段的合并逻辑。
class AnthropicAdapter(BaseAdapter):
"""Anthropic Messages-compatible HTTP adapter with session lifecycle helpers."""
session_cls = Session
def __init__(self, *, tokenizer, sglang_url, tool_parser=None, reasoning_parser=None) -> None:
super().__init__(
tokenizer=tokenizer,
sglang_url=sglang_url,
tool_parser=tool_parser,
reasoning_parser=reasoning_parser,
)
# 注册 Anthropic Messages API 特有的路由
self.app.router.add_post("/v1/messages", _handle_request)
self.app.router.add_post("/v1/messages/count_tokens", _count_tokens)
self.app.router.add_get("/healthz", _ok)
self.app.router.add_get("/v1/models", _ok)
async def finish_session(self, sid: str, *, wait_timeout: float = 5.0) -> list[TokenSegment]:
# 先关闭 inflight 任务并标记 closed
await self.shutdown_session(sid, wait_timeout=wait_timeout)
s = self.store.pop(sid, None)
if s is None:
return []
# 如果存在活跃子代理且其 turns 不为空,先作为 subagent 段追加
if s.active_sub is not None and s.active_sub.turns:
s.segments.append(make_turn_segment(s.active_sub.turns, kind="subagent"))
# 主链的 turn 作为 final 段
if s.main.turns:
s.segments.append(make_turn_segment(s.main.turns, kind="final"))
return merge_turn_segments(s.segments, max_context_tokens=s.max_context_tokens)
slime/agent/adapters/openai.py
core-logic
OpenAIAdapter 继承 BaseAdapter,实现会话最终段的合并。
class OpenAIAdapter(BaseAdapter):
"""OpenAI-compatible HTTP adapter with session lifecycle helpers."""
session_cls = Session
def __init__(self, *, tokenizer, sglang_url, tool_parser=None, reasoning_parser=None) -> None:
super().__init__(
tokenizer=tokenizer,
sglang_url=sglang_url,
tool_parser=tool_parser,
reasoning_parser=reasoning_parser,
)
# 注册 OpenAI 兼容的聊天和响应端点
self.app.router.add_post("/v1/chat/completions", _handle_chat_completions)
self.app.router.add_post("/v1/responses", _handle_responses)
self.app.router.add_get("/healthz", _ok)
self.app.router.add_get("/v1/models", _ok)
async def finish_session(self, sid: str, *, wait_timeout: float = 5.0) -> list[TokenSegment]:
# 先关闭 inflight 任务并标记 closed
await self.shutdown_session(sid, wait_timeout=wait_timeout)
s = self.store.pop(sid, None)
if s is None:
return []
# OpenAI 适配器不处理子代理,仅将主链 turn 作为 final 段
if s.main.turns:
s.segments.append(make_turn_segment(s.main.turns, kind="final"))
return merge_turn_segments(s.segments, max_context_tokens=s.max_context_tokens)
当前评论区没有形成足够清晰的争议点或结论,后续有更多讨论时会体现在这里。
pop_session_split、start 等模块级函数被移除,任何直接调用它们的代码(如未迁移的测试或示例)将失败。_inflight、_closed)变为实例属性,确保同一进程内多个适配器实例不会互相干扰,但若原有代码依赖全局单例则需注意。对使用 agent 适配器的开发者:需从调用函数改为实例化类,并调用相应方法。对系统:无运行时性能影响,但代码结构更清晰。对团队:降低了后续新增适配器(如 Gemini)的重复劳动。
当前没有检测到明确关联的 Issue 链接,后续同步到相关引用后会出现在这里。
参与讨论