Agent demo 跑通了,然后呢?聊聊多用户生产化这道没人填的坑

一、先说我遇到的问题

去年到今年,我陆陆续续写过几个 Agent 小工具。每次的体验都惊人地一致:

demo 阶段如丝般顺滑,上线阶段如鲠在喉。

跟着任何一个主流框架的 quickstart,二十行代码,一个能调工具、能多轮对话的 Agent 就跑起来了,演示效果很唬人。可一旦我想把它变成一个多人能同时用的服务,问题就接踵而至:

  • 两个用户同时发消息,会话状态开始串台------A 的上下文漏进了 B 的回复。
  • 我到底该给每个用户一个 Agent 实例,还是共享一个?共享会不会有并发安全问题?独享内存会不会爆?
  • 实例什么时候回收?空闲多久算空闲?并发上限怎么设?
  • 会话要持久化、进程重启要能恢复,这套又得自己从头搭。

我翻遍了手上几个框架的文档,发现这部分几乎都被当成"留给读者的练习":要么让你去用它收费的托管平台,要么一句"生产部署请参考最佳实践"就把你打发了。

后来我查了一圈资料才确认,这不是我一个人的错觉------Salesforce 专门写过工程博客讲他们的多租户 Agent 平台怎么扛 7000+ 并发会话不串台;2026 年初 Medium / dev.to 上一连串「Multi-Tenant AI Agent Infrastructure」实战文,焦点全是同一件事:会话状态串扰、per-user 隔离、资源池化、向量库的租户边界

「多用户会话隔离」是真实的生产难题,而大多数框架把它甩给了你。

milu 就是我为了系统性地解决这个问题,顺手把另外几个老问题(国产模型接入、工具安全、全家桶)一起做掉的产物。这篇文章主要讲讲 milu 是怎么填「多用户生产化」这道坑的,以及它的整体设计取舍。

项目地址:github.com/stephonGAO/... (MIT,pip install -U milu 即用,欢迎 star)


二、核心:AgentPool 多用户并发资源池

先看一个事实------Agent 实例是带状态的 。它持有对话历史、会话对象、MCP 连接、工具注册表、甚至"是否已经开始干活"这种运行期标记。这意味着多个用户绝对不能共享同一个 Agent 实例

那唯一安全的方案就是 per-user Agent 。但"每人一个实例"立刻带来新问题:实例怎么管理、怎么限量、怎么回收。这正是 AgentPool 干的事:

python 复制代码
from milu import AgentPool, ModelRegistry

# LLM 实例本身协程安全,可以在所有用户间共享(底层是 AsyncOpenAI)
llm = ModelRegistry.create("qwen", model="qwen3.6-plus")
pool = AgentPool.from_llm(llm)
await pool.start()

# 按 (user_id, session_id) 拿到这个用户专属的 Agent,用完自动归还
async with pool.acquire("user-1", "session-A") as h:
    async for evt in h.agent.run("你好"):
        ...

await pool.stop()

AgentPool 守着四个硬不变量,这也是它和"自己写个 dict 缓存 Agent"的本质区别:

  1. 每个 (user_id, session_id) 最多一个实例(隔离的根基);
  2. 实例总数 max_agents(LRU 淘汰,防内存爆);
  3. 并发执行的 run max_concurrent_runs(全局信号量限流);
  4. 空闲超过 idle_ttl_seconds 的实例被后台清理。

会话、记忆、知识库都按 user_id 自动派生隔离,你不用操心。

真正的难点不在池子,在"无状态化"

这里有个容易被忽略的坑。就算你给每个用户独立 Agent,如果工具内部用了模块级全局变量来存状态,照样串台。比如一个 todo 工具用模块级单例存当前计划,两个用户的 Agent 跑在同一个进程里,计划就会互相覆盖。

milu 的做法是全链路 ContextVar 无状态化 :todo 的计划存储、子代理的事件收集、父级安全模式的继承,全部通过 ContextVarAgent.run() 入口按调用注入,实现 asyncio 任务级隔离。源码里这条约束被反复强调:

新增任何"跨调用共享"的工具状态,一律用 ContextVar,切勿用模块级全局变量

这块我专门写了并发隔离的测试(test_concurrency_with_pool.pytest_todo_concurrent_isolation.pytest_subagent_concurrent_isolation.py 等),整个项目 1100+ 测试里,这部分是核心保证。

顺带一提,我之所以对"并发崩溃"这么敏感,是因为调研时看到国产框架里有公开的并发翻车案例------某框架的群聊模块多用户同时发消息时 IndexError、Code Interpreter 多用户调用有线程安全瓶颈。这些都是真实发生过的、能在 CSDN 搜到的事故。无状态化不是过度设计,是被现实教育出来的。


三、第二个动机:国产模型不该是二等公民

我日常用的是 DeepSeek 和通义,但很多框架对国产模型的支持是"靠社区零散包"或"自己配 base_url + LiteLLM 转一道"。每接一家,我都要:查它的 base_url、适配它的 thinking 模式开关、处理它内置联网搜索的参数、规避它不支持的字段......然后自己维护一张多厂商路由表。

milu 把 9 家提供商收敛到一个接口,6 家国产模型(通义、DeepSeek、Kimi、GLM、MiniMax、豆包)+ OpenAI / Gemini / Claude,切换就一行:

python 复制代码
from milu import ModelRegistry, Message, MessageRole

llm = ModelRegistry.create("deepseek")          # 换 "kimi" / "glm" / "qwen" ... 同一接口
async for chunk in llm.chat([Message(role=MessageRole.USER, content="你好")]):
    print(chunk.content or "", end="", flush=True)

API Key 从 {PROVIDER}_API_KEY 环境变量读,各家的参数差异(thinking、内置搜索、参数过滤)已经逐家适配好。

实现上一个关键决定:所有 provider 统一用 openai.AsyncOpenAI 作为 HTTP 客户端 ,靠不同 base_url + extra_body 适配各家。好处是 AsyncOpenAI 协程安全,所以一个 LLM 实例能在多用户间安全共享------这正好和上面 AgentPool 的设计咬合:共享 LLM、隔离 Agent。

还有个国内特别实在的点:内置 web_search 工具是可插拔后端 ,DDG 国内连不上,配 WEB_SEARCH_PROVIDER=bocha 就能国内直连。这种细节,没在国内踩过坑的框架不会替你想。


四、第三件事:工具执行得有真正的安全模型

Agent 一旦能调 shell_commandfile_writepython_repl,"它会不会把我电脑搞坏"就成了真问题。milu 给了四种操作模式:

python 复制代码
agent = Agent(llm, mode="manual")   # 不安全工具等人工审批
agent.set_mode("talk")              # 只读:不安全工具一律拦截
模式 行为
talk 只读,所有不安全工具调用被拦
manual 安全工具直接跑;不安全工具产出确认事件、等审批
auto(默认) 自主决策;不安全调用交 AI 安全判定器裁决(放行 / 转确认 / 拒绝)
superwork 全权限,跳过一切检查

auto 模式那个 AI 安全判定器 是对齐 Claude Code auto 模式分类器做的:安全工具走快路径零开销,其余不安全调用批量交给一个判定 LLM 三态裁决。而且------子代理会通过 ContextVar 继承父级的模式和确认回调,也就是说你没法靠"委派给子代理"来绕过审批。委派不构成安全旁路,这点我觉得挺重要。


五、剩下的就是"全家桶开箱即用"

到这里 milu 的三个核心动机讲完了:多用户生产化、国产模型一等公民、工具安全。剩下的能力是"既然都做了 Agent,该有的都给齐":

一个 Agent(llm) 默认就是完整体------内置系统提示词、20+ 工具、技能、三个子代理、会话持久化、上下文自动压缩,全都有,传显式参数才覆盖:

python 复制代码
from milu import Agent, ModelRegistry, TextDelta, AgentDone

agent = Agent(ModelRegistry.create("deepseek"))
async for evt in agent.run("读一下 ./report.pdf 然后总结"):
    if isinstance(evt, TextDelta):
        print(evt.text, end="", flush=True)
    elif isinstance(evt, AgentDone):
        print(f"\n[共 {evt.turn_count} 轮]")

清单(都是库内置,不用再装一堆集成包):

  • 20+ 内置工具 :文件读写、shell、Python REPL、HTTP、网页正文提取、可插拔搜索、Office/PDF 文档解析、图片视觉输入、日期、结构化输出、todo、长期记忆。
  • MCP 协议:stdio / streamable HTTP / SSE 三种传输,而且 MCP 工具用"休眠池"设计------schema 不污染上下文,Agent 按需发现激活。
  • RAG 知识库 :分块向量化、余弦检索、来源路由常驻 prompt、可选每轮自动检索,kb_search / kb_ingest / kb_manage 三工具。
  • 子代理:内置调研员 / 阅读员 / 编码员三件套,上下文隔离、权限收窄、可并行。
  • 技能系统:9 个内置技能,元数据常驻、正文按需加载。
  • 定时任务 :多用户 cron 调度,能嵌在 milu chat / milu serve 里跑。
  • 内置 Web 服务milu serve 一行起多用户 SSE 流式对话 + 全功能演示前端(纯 vanilla 无构建链)。

而这一切,一个 pip install -U milu 全都装好,CLI、Web 服务、RAG、MCP 都在核心依赖里。

bash 复制代码
pip install milu
milu            # 首次运行引导你选厂商、填 Key,然后直接聊
milu serve      # 一行起多用户 Web 服务,浏览器开 http://127.0.0.1:8000


六、五分钟上手

bash 复制代码
pip install milu
milu                # 跟着引导走,零配置开聊

或者嵌进你自己的代码,三行:

python 复制代码
from milu import Agent, ModelRegistry
agent = Agent(ModelRegistry.create("deepseek"))
async for event in agent.run("现在几点?用工具查一下"):
    ...

项目还很新(v0.1.0,star 也刚起步),但 1100+ 测试、中英双语文档、Docker 部署文档都在位。如果这篇文章里有哪个点戳到你了,欢迎去 GitHub 点个 star,或者直接提 issue 拍砖------冷启动阶段,你的每一条反馈我都会认真看。

🦌 GitHub:github.com/stephonGAO/... 📦 PyPI:pip install -U milu


相关推荐
得物技术33 分钟前
从狂野代码到按目标生产:得物推荐 AI Harness 的工程化实践|AICon 演讲整理
人工智能·算法·架构
HokKeung37 分钟前
飞书 lark-cli 如何存储 tenant_access_token 和 user_access_token
人工智能·go
Ralph_Salar39 分钟前
从0到1搭建AI智能支付风控助手Stage3-Function Calling — 让AI能动起来
人工智能
Ralph_Salar44 分钟前
从0到1搭建AI智能支付风控助手Stage4-Agent编排 — 让AI自己思考、决策、行动
人工智能
smallyoung1 小时前
Spring AI 2.0 VectorStore实战:从原理到RAG落地
人工智能·后端
火山引擎开发者社区2 小时前
被 Vibe Coding 用户频点名的火山 Supabase 到底是个啥?一图来看懂
人工智能
火山引擎开发者社区2 小时前
动手做 AI 实验赢好礼!产品 + 大模型免费额度限时供应!
人工智能
字节跳动视频云技术团队2 小时前
从 VCloud 到 Agentic VCloud:Agent 时代的范式重构
人工智能·音视频开发
AKAMAI2 小时前
每百万 Token 成本砍六成,出海 AI 团队开始重算推理这笔账
人工智能·云计算
用户938515635073 小时前
从 Prompt 到 Harness:AI 工程化的三年跃迁与实战解码
javascript·人工智能