装配服务Assemble the service
先说问题The problem
你已经写过 agent,但你从没「运行过它所在的进程」。把 AgentScope 从库升级成服务要过的第一道门是 create_app 的四个必填积木——少一个,一个请求都进不来。
You have written agents; you have never run the process they live in. Promoting AgentScope from library to service starts with the blocks create_app demands. Miss one and not one request gets in.
先动手Do it first
先在页面上接线——页面只懂规则。然后跑下面那条判题,那才是真会失败的东西。Wire it on the page — the bench only knows the rules. Then run the check below; that is the part that can actually fail.
storage=
StorageBase
必填required
托管 agent、会话、凭据与消息。没有它,进程一重启,服务就没有任何记忆。Owns agents, sessions, credentials and messages. Without it the service has no memory across process restarts.
message_bus=
MessageBus
必填required
负责跨会话收件箱投递与空闲会话唤醒。它与存储刻意分离,所以传输后端和持久化后端可以是两套不同的系统。Carries cross-session inbox delivery and idle-session triggers. Deliberately separate from storage, so the transport backend can differ from the persistence backend.
workspace_manager=
WorkspaceManagerBase
必填required
决定工具到底在哪里执行、隔离到什么粒度。每次 chat 运行和每个 /workspace 接口都依赖它。Decides where a tool actually runs and how isolated it is. Every chat run and every /workspace route depends on it.
extra_credentials=
list[Type[CredentialBase]]
选填optional
注册服务可以解析的模型族。放入脚本化凭据后,整条链路可以离线、确定性地跑通——这正是评测能做真的前提。Registers the model families the service may resolve. A scripted credential makes the whole stack runnable offline and deterministic, which is what makes an evaluation honest.
create_app(…)
为什么这样设计Why it is built this way
01先选存储,再谈记忆Pick storage before memory
AsyncSQLAlchemyStorage 吃一条 SQLAlchemy 异步 URL。SQLite 就够本机跑,同一行代码换成 postgresql+asyncpg 就是生产。状态存储决定进程死后还剩什么。
Pick storage before talking about memory. AsyncSQLAlchemyStorage takes any async SQLAlchemy URL: SQLite runs on a laptop, and the same line swapped to postgresql+asyncpg is production. Storage decides what survives the process.
02总线不是存储The bus is not the store
InMemoryMessageBus 让你不用装 Redis 就能跑,代价是跨不了进程:两个副本互相看不见收件箱。框架把传输和持久化刻意分开,就是为了让你能一边存 SQL、一边换 Redis 传输。
The bus is not the store. InMemoryMessageBus runs with no broker installed, at the price of never crossing processes: two replicas cannot see each other's inbox. Transport is deliberately split from persistence so you can keep SQL rows and swap to a Redis transport.
03沙箱只是目录边界A directory is not a security boundary
LocalWorkspaceManager 的隔离是按 agent 分目录,不是安全边界——工具仍然碰得到宿主机。这一块的真正能力在 C7 换掉它时才出现。
LocalWorkspaceManager isolates by directory per agent, which is not a security boundary: a tool can still reach the host. This block's real power only shows up when C7 swaps it.
04凭据决定大脑The credential decides the brain
credential 是 pydantic 模型,它的 get_chat_model_class() 决定服务构造哪个模型。注册一个脚本化凭据,整条链路就能离线、确定性地跑——这是后面评测能做真的前提。
A credential is a pydantic model whose get_chat_model_class() decides which model the service builds. Register a scripted credential and the whole chain runs offline and deterministically — the precondition for an honest evaluation later.
用到的 APIAPIs in play
代码The code
from agentscope.app import create_app
from agentscope.app.storage import AsyncSQLAlchemyStorage
from agentscope.app.message_bus import InMemoryMessageBus
from agentscope.app.workspace_manager import LocalWorkspaceManager
app = create_app(
storage=AsyncSQLAlchemyStorage("sqlite+aiosqlite:///./capstone.db"),
message_bus=InMemoryMessageBus(),
workspace_manager=LocalWorkspaceManager("./workspaces"),
extra_credentials=[TapeCredential],
)
判题The check
python lab.py run c1
它断言了什么What it asserts
POST /credential/ -> 201, id af82d752POST /agent/ -> 201, id d132bbcaPOST /session/ -> 201, id 0e6b92d9 (the id rotates every run; what is asserted is that all three routes accepted the assembly)one chat turn, then status idle (not running, not awaiting anything)reply read back from persistenceall three required slots enforceda transport block in the storage slot is rejected
会踩的坑Where it hurts
TestClient(app) 不会跑 lifespan,storage 引擎没打开,第一个请求就 RuntimeError: AsyncSQLAlchemyStorage is not initialised。必须 with TestClient(app) as client。
TestClient(app) does not run the lifespan, so the storage engine never opens and the first request raises RuntimeError: AsyncSQLAlchemyStorage is not initialised. You need
with TestClient(app) as client.
费曼闸门Say it back
为什么 message_bus 不能直接用 storage 代替?说清两者各自的生命周期。
Why can the message bus not just be the storage? Spell out each one's lifecycle.
页面只存通行码的哈希。判题没真跑过,这里就解不开。The page stores a hash, not the code. Nothing unlocks until the command really ran.