从零跑通一个真正会“等待”的 AI Agent:openYuanrong Agent Session + Kimi 实战
这不是一个“把 Kimi API 包一层页面”的普通 demo。
它记录的是一次从虚拟机环境、openYuanrong 集群、函数注册、Agent Session,到 Kimi 接入端到端跑通的完整实操。
一、用户场景:Agent 执行到一半,要停下来等人
先把要解决的问题说清楚。
很多 AI Agent 教程里,前端一个聊天框,后端调一次大模型,返回一段文本——页面能动,但严格说那只是“模型 API 调用器”,不是 Agent 工程化。
真实业务里的 Agent,往往长这样:
- 一个文章策划 Agent:先问用户“想写什么选题、给谁看、强调什么”,停下来等用户填;用户填完,生成大纲;再停下来等用户确认;确认后才生成正文草稿。
- 一个报销审批 Agent:跑到一半发现缺发票,挂起等用户补传,补完接着往下走。
- 一个客服工单 Agent:需要用户二次确认订单信息后才执行退款。
这些场景共同的难点不是“模型能不能生成”,而是:
Agent 执行到一半需要停下来等用户补充信息,等信息回来后,还要沿着原来的上下文继续往下执行。
用传统 Web 服务当然能做,但你得自己补一整套状态系统:Redis 存中间状态、数据库存会话、消息队列做回调,再自己处理并发和超时。写到最后会发现,模型调用反而是最简单的部分,跨请求的状态编排才是真正的工程量。
这次 demo 用最克制的方式验证一件事:
openYuanrong 能不能作为 Agent Runtime,让一个函数在执行过程中真正“等待 → 被唤醒 → 基于同一个 SessionID 继续”,把状态编排这件事接管掉。
不做 RAG、不做复杂工具链、不堆智能体框架——先把这条最关键的链路跑通,其余都是自然扩展。
二、demo 目标与整体链路
最终跑通的是上面说的“文章策划 Agent”,完整链路:
- 用户启动 Agent。
- openYuanrong 函数进入
wait_for_notify(),等待用户补充文章方向。 - 用户提交选题信息,另一个带同一 SessionID 的请求通过
notify(payload)唤醒等待中的执行流程。 - Agent 调用 Kimi API 生成文章大纲。
- Agent 再次进入
wait_for_notify(),等待用户确认大纲。 - 用户确认后,再次
notify({"confirm": true})。 - Agent 继续调用 Kimi API 生成文章草稿。
- 原始
start请求返回最终结果。
这条链路里职责划得很清楚:
Kimi: 负责生成大纲和文章草稿(纯推理生成)。
openYuanrong: 负责函数部署、调用、Session 加载、等待、唤醒、
会话亲和、会话历史和状态恢复。
换句话说,本文的核心不是“怎么调 Kimi”,而是“怎么把一个需要多轮等待的 Agent 跑在 openYuanrong 上”。下面所有截图都来自这次实际运行环境。
三、运行环境
在 Mac 主机上用 Multipass 新建了一个 Ubuntu 虚拟机,openYuanrong 集群跑在 VM 里。
关键环境:
Host: macOS
VM: Ubuntu 24.04.4 LTS(yuanrong-agent-demo, 192.168.2.6, 4C/8G/40G)
Python: 3.11 virtualenv
openYuanrong: 已安装
本机项目目录 ~/openYuanrong/agent-demo,同步到 VM 后位于 /home/ubuntu/openYuanrong-agent-demo/agent-demo。
四、启动 openYuanrong 集群并确认健康
VM 里启动 openYuanrong:
cd ~/openYuanrong-agent-demo
source .venv/bin/activate
yr start --master \
-s 'mode.master.frontend=true' \
-s 'mode.master.function_scheduler=true' \
-s 'mode.master.meta_service=true'
这个 demo 至少需要 frontend(接收调用)、meta_service(函数元数据)、function_scheduler(调度)、function_agent(执行)、function_proxy(通信)、etcd(元数据存储)、ds_master / ds_worker(DataSystem)这些组件。
写业务代码前,先确认 openYuanrong 本身是健康的:
multipass exec yuanrong-agent-demo -- bash -lc '
source ~/openYuanrong-agent-demo/.venv/bin/activate
yr health
'

这是第一个关键证据:这个 demo 跑在一个真实运行的 openYuanrong 集群上,而不是普通本地 Python 服务。
五、项目结构
agent-demo/
functions/
agent_demo.py # openYuanrong Agent Session 状态机(核心)
llm_client.py # Kimi API 客户端(核心)
hello_demo.py
register/
agent.json # 函数注册配置
hello.json
scripts/
register_agent.sh
run_agent_session.sh
start_web.sh
web/ # 可视化控制台(仅展示层)
server.py
static/
README.md
真正的 Agent 逻辑都在 openYuanrong 函数里,Web 页面只是为了让 demo 有一个可视化控制台。
六、注册函数并开启 Agent Session
注册配置 register/agent.json:
{
"name": "0@agentdemo@kimi-agent",
"runtime": "python3.11",
"handler": "agent_demo.handler",
"kind": "faas",
"cpu": 1000,
"memory": 1024,
"timeout": 600,
"concurrentNum": "10",
"enableAgentSession": true,
"environment": {
"LLM_PROVIDER": "kimi",
"LLM_BASE_URL": "https://api.moonshot.cn/v1",
"LLM_MODEL": "kimi-k2.6"
},
"storageType": "local",
"codePath": "/home/ubuntu/openYuanrong-agent-demo/agent-demo/functions"
}
注册前先在 VM 里把关键字段打出来,确认函数名、运行时、handler、Agent Session 开关和 Kimi 配置都在:

注册脚本做的事:从 register/agent.json 读配置 → 把 Kimi API Key 通过环境变量注入函数环境(不写进源码)→ 调用 meta_service 注册或更新函数 → 把响应里的敏感字段脱敏为 *** → 保存函数 URN。
cd ~/openYuanrong-agent-demo/agent-demo
MOONSHOT_API_KEY="你的 Kimi API Key" bash scripts/register_agent.sh
注册走的是 openYuanrong REST API(函数已存在则走 PUT 更新):
POST http://192.168.2.6:31182/serverless/v1/functions

最重要的是这个 URN:
sn:cn:yrk:default:function:0@agentdemo@kimi-agent:latest
后续所有调用都不是直接执行 Python 文件,而是请求 /serverless/v1/functions/{URN}/invocations——这是“请求进了 openYuanrong”的第二个证据。
七、确认 Agent Session 真的生效
注册成功还不够,普通函数也能注册成功。我们要证明它真的启用了 Agent Session,所以进一步查 etcd 里的函数元数据:
multipass exec yuanrong-agent-demo -- bash -lc '
ETCDCTL_API=3 ~/openYuanrong-agent-demo/.venv/lib/python3.11/site-packages/yr/third_party/etcd/etcdctl \
--endpoints=http://192.168.2.6:32379 \
get /sn/functions/business/yrk/tenant/default/function/0@agentdemo@kimi-agent/version/latest -w json \
| jq -r .kvs[0].value | base64 -d \
| jq "{handler: .funcMetaData.handler, enableAgentSession: .extendedMetaData.enableAgentSession, code_path: .codeMetaData.code_path}"
'

最关键的一行:
"enableAgentSession": true
这证明这个函数不是普通 FaaS 函数,而是启用了 openYuanrong 的 AI Agent Session 能力。
八、Agent 函数:等待 → 唤醒 → 继续
核心入口在 functions/agent_demo.py。
函数一进来,先从 openYuanrong context 里拿 session:
session_id = context.get_session_id()
session = context.get_session_service().load_session(session_id)
如果调用请求没有携带可被 openYuanrong 识别的 SessionID,就拿不到可用 session。本文 REST 调用里,SessionID 放在 X-Instance-Session Header 中:
if session is None:
return {
"status": "missing_session",
"message": "请在调用 Header 中传入 X-Instance-Session。",
"session_id": session_id,
}
当请求是 notify 时,函数不进入主流程,只向当前 Session 发通知。会话绑定有效时,这个请求会因为会话亲和落到同一实例,唤醒之前挂起的执行线程:
if action == "notify":
payload = event.get("payload") or {}
session.notify(payload)
return {"status": "notified", "session_id": session_id, "payload": payload}
主流程第一步:记录状态,然后等待用户补充信息:
_record(session, "collect_info",
message="请提交文章目标读者、风格、痛点和你想强调的 demo 方向。")
user_info = session.wait_for_notify(...)
注意这里不是轮询数据库,也不是 sleep。wait_for_notify 是 openYuanrong Agent Session 的等待能力:当前执行线程/协程会挂起并释放会话锁,直到同一会话收到 notify 或等待超时。
被 notify(payload) 唤醒后,函数继续往下走,调用 Kimi 生成大纲,再等一次用户确认,确认后继续生成正文草稿,最后把结果写进 session histories:
outline = generate_outline(user_info) # Kimi 生成大纲
_record(session, "confirm_outline", outline=outline,
message="大纲已由 Kimi 生成,请确认是否继续生成文章草稿。")
confirmation = session.wait_for_notify(...) # 再等一次确认
draft = generate_article(user_info, outline, confirmation) # Kimi 生成草稿
_record(session, "done", outline=outline, draft=draft,
message="文章草稿已由 Kimi 生成,openYuanrong Agent Session 流程完成。")
这个流程的价值在于:业务代码写起来像一段同步流程,但实际是跨请求、跨用户输入、多阶段恢复执行的。 Kimi 调用封装在 functions/llm_client.py,只负责按 prompt 生成大纲(JSON Mode)和草稿,不感知会话状态。
九、三次调用与会话亲和
端到端脚本 scripts/run_agent_session.sh 的核心是三次 HTTP 调用。
第一次:启动 Agent。它会进入 wait_for_notify() 等待,所以放到后台:
curl -sS -X POST "${FRONTEND_ENDPOINT}/serverless/v1/functions/${URN}/invocations" \
-H "Content-Type: application/json" \
-H "X-Instance-Session: ${SESSION_HEADER}" \
--data-binary '{"action":"start","reset":true}' > "${LOG_DIR}/agent-start.response.json" &
第二次:提交选题信息唤醒 Agent。这个请求只负责把 payload 送进 Session,返回值只是“已通知”:
curl -sS -X POST ".../invocations" \
-H "X-Instance-Session: ${SESSION_HEADER}" \
--data-binary '{"action":"notify","payload":{"product":"AI Agent Session","audience":"想写补课文章的开发者","tone":"实操、少废话","pain":"普通函数请求结束就丢上下文"}}'
第三次:确认大纲,继续生成草稿。同样是通知请求,真正的草稿结果由还在等待的原始 start 请求继续执行后返回:
curl -sS -X POST ".../invocations" \
-H "X-Instance-Session: ${SESSION_HEADER}" \
--data-binary '{"action":"notify","payload":{"confirm":true}}'
三次请求都带同一个 Header:
X-Instance-Session: {"sessionID":"demo-agent-kimi-real-005","sessionTTL":0,"concurrency":2}
这就是 openYuanrong 会话调用的入口:sessionID 用来建立和命中会话绑定;只要绑定关系有效,后续相同 SessionID 的请求会被定向到同一实例。如果原实例不可用,新实例可以从分布式数据系统重新加载会话上下文(如 histories)。
请求打的不是 Web demo 自己的本地接口,而是 openYuanrong frontend:
http://192.168.2.6:8888/serverless/v1/functions/{URN}/invocations

十、端到端运行结果
CLI 流程跑通后,两次 notify 都成功,原始 start 请求返回 done:


关键字段:
{
"status": "done",
"session_id": "demo-agent-kimi-real-005",
"history_stages": [
"collect_info", "info_received",
"confirm_outline", "confirmation_received", "done"
]
}
这条 history_stages 证明同一个 SessionID 下的 histories 被连续读写,流程真实经历了 collect_info → info_received → confirm_outline → confirmation_received → done。这不是一次性模型调用,而是一个跨多次请求、依赖会话亲和与状态恢复的 Agent 执行流程。
对照官方多轮对话示例:
| 官方示例里的角色 | 本文 demo 里的角色 |
|---|---|
第一次 /invocations 请求 |
action=start |
sess.wait(...) / session.wait_for_notify(...) |
等待选题信息、等待大纲确认 |
| 第二次带相同 SessionID 的 notify 请求 | action=notify,提交选题信息 |
| 再一次 notify 请求 | action=notify,确认继续生成草稿 |
| 原始请求最终返回 | start 请求返回 status=done、outline、draft 和 histories |
十一、Web 控制台
为了让 demo 不只停留在命令行,加了一个零依赖 Web 控制台。
cd ~/openYuanrong-agent-demo/agent-demo
PORT=18080 bash scripts/start_web.sh
# 浏览器打开 http://192.168.2.6:18080
页面做三件事:启动 Agent、发送选题信息、确认大纲。




页面本身不是核心,它只是展示层,背后仍然调用 openYuanrong frontend:8888:
response = requests.post(
f"{FRONTEND_ENDPOINT}/serverless/v1/functions/{function_urn}/invocations",
headers={"Content-Type": "application/json",
"X-Instance-Session": _session_header(session_id)},
json=payload, timeout=timeout)

换句话说,页面不是伪造状态,只是把请求转发给 openYuanrong。
十二、怎么证明这个 demo 真的用了 openYuanrong
很多 demo 讲得天花乱坠,最后其实只是本地 Python 调了一次模型。这个 demo 可以从 6 个角度证明它没有:
- 集群在运行:
yr health输出里frontend / meta_service / function_scheduler / function_agent全部RUNNING。 - 函数注册在 openYuanrong:URN
sn:cn:yrk:default:function:0@agentdemo@kimi-agent:latest。 - 元数据里启用了 Agent Session:etcd 中
"enableAgentSession": true。 - 调用走的是 frontend:
http://192.168.2.6:8888/serverless/v1/functions/{URN}/invocations。 - 请求带了 Session Header:
X-Instance-Session: {"sessionID":"...","sessionTTL":0,...}。 - 代码真实调用了 Agent Session API:
load_session()/wait_for_notify()/notify()/ histories 读写。
更关键的是:start 请求在 wait_for_notify() 处挂起后,后续两个带同一 SessionID 的 notify 请求把数据送回同一会话流程,最终由原始 start 请求返回 done。这些证据都成立,就说明它确实把 openYuanrong 放在了 Agent Runtime 的位置上。
十三、openYuanrong 和 Kimi 的职责边界
这个 demo 最值得讲的不是“调用了哪个模型”,而是职责边界清楚:
| 模块 | 负责什么 | 不负责什么 |
|---|---|---|
| openYuanrong | 函数注册、运行、会话加载、等待、唤醒、会话亲和、histories、状态恢复 | 生成文章内容 |
| Kimi | 根据 prompt 生成大纲和草稿 | 保存会话状态、等待用户、恢复执行 |
| Web 控制台 | 展示状态、触发请求 | 不保存核心 Agent 状态 |
| CLI 脚本 | 复现端到端流程 | 不替代 openYuanrong Runtime |
一句话:
大模型不是 Agent 的全部。Agent 还需要一个能承载状态和执行流的 Runtime。
十四、结论
这次 demo 最终跑通了三件事:
- openYuanrong 函数服务真实运行,函数注册在 openYuanrong 里,并通过 openYuanrong frontend 调用。
- openYuanrong Agent Session 真实生效,函数内部使用
load_session/wait_for_notify()/notify()和 histories 读写,完成两轮等待和唤醒。 - Kimi API 真实参与生成,先生成大纲,再在用户确认后生成草稿。
最终草稿也整理成了 Markdown 文件,方便后续修改和发布:

所以这个项目不是“页面套模型”,而是一次完整实操:
openYuanrong 负责运行时、会话、等待、唤醒;
Kimi 负责模型生成;
Web/CLI 负责触发和展示。
最值得强调的不是“我调用了 Kimi”,而是:
这个 demo 把一个需要多轮等待、外部唤醒和会话状态恢复的 Agent,真实部署到了 openYuanrong 函数运行时里,并用 Kimi 完成了生成任务。
参考文档
- openYuanrong AI Agent 多轮对话示例:https://docs.openYuanrong.org/zh-cn/latest/multi_language_function_programming_interface/examples/ai_agent_multiturn_dialogue.html
- openYuanrong AI Agent 会话与亲和性调度:https://docs.openYuanrong.org/zh-cn/latest/multi_language_function_programming_interface/advanced_tutorials/ai_agent_session.html
- openYuanrong 官方文档首页:https://docs.openYuanrong.org/zh-cn/latest/index.html
更多推荐


所有评论(0)