deerflow-code/newpython/demo_call.py
2026-09-07 18:24:55 +08:00

133 lines
5.2 KiB
Python

"""
OUT.py 的调用链 demo —— 跑通 agent_init → leader 派活 → 各 special agent 干活 这一整套流程。
前置条件:
1. DeerFlow 后端在 http://127.0.0.1:8001 在跑
2. OUT.py 已经 `python -m uvicorn OUT:app --host 0.0.0.0 --port 8095` 起好
3. 下面 SUB_AGENT_NAMES 里的 agent 已经在 DeerFlow 里创建好(/api/agents 能查到)
直接 `python demo_call.py` 即可。
"""
import asyncio
import json
import httpx
# ============ 改这里 ============
OUT_BASE = "http://127.0.0.1:8095"
# 已经在 DeerFlow 里建好的子 agent 名字 (会被 OUT.py 强制 lowercase)
SUB_AGENT_NAMES = [
"us-intelligence-gathering-agent",
"taiwan-intelligence-gathering-agent",
]
# 总控 agent 名字 —— OUT.py 会现场创建它,所以填一个新名字即可 (hyphen-case)
MAIN_AGENT_NAME = "demo-coordinator"
# 用户提问 —— 总控会基于此派活
USER_QUESTION = "对比中美两国 2024 年在 AI 监管政策上的差异"
# leader 阶段命中即停的 skill 名字单 (OUT.py 用 agent_orchestration 作为派活信号)
SKILL_STOP_NAMES = ["agent_orchestration"]
# ================================
async def call_agent_init() -> tuple[str, dict[str, str]]:
"""步骤 1: 初始化 —— 拿回 thread_ids 字典 {agent_name → thread_id}."""
print(">>> [1/3] POST /agent_init")
async with httpx.AsyncClient(timeout=300.0) as client:
r = await client.post(
f"{OUT_BASE}/agent_init",
json={
"agent_names": SUB_AGENT_NAMES,
"main_agent_name": MAIN_AGENT_NAME,
},
)
r.raise_for_status()
body = r.json()
main_name = body["main_agent_name"]
thread_ids = body["thread_ids"]
print(f" main_agent_name = {main_name}")
print(f" thread_ids = {json.dumps(thread_ids, ensure_ascii=False, indent=2)}")
return main_name, thread_ids
async def stream_generate(payload: dict) -> object | None:
"""通用 SSE 消费:边收边打印,最后返回 OUT.py 自己 yield 的 {'status': ...} 里的 status 字段."""
final_status = None
async with httpx.AsyncClient(timeout=None) as client:
async with client.stream("POST", f"{OUT_BASE}/generate_mul_agent", json=payload) as resp:
print(f" HTTP {resp.status_code}")
if resp.status_code != 200:
err = await resp.aread()
raise RuntimeError(f"OUT.py /generate_mul_agent failed: {err.decode('utf-8', 'replace')[:500]}")
async for line in resp.aiter_lines():
if not line:
continue
# OUT.py 把 DeerFlow 的原始 SSE 流直接转发,自己最后会再 yield 一行
# `data: {"status": ...}\n\n`。两边都以 `data:` 开头,我们都尝试 JSON parse
# 一下,能解析成 {"status": ...} 的就是 OUT.py 的终态。
if line.startswith("data:"):
body_text = line[5:].strip()
try:
obj = json.loads(body_text)
except json.JSONDecodeError:
continue
if isinstance(obj, dict) and "status" in obj:
final_status = obj["status"]
# 想看上游 DeerFlow 的完整流,把下面打开即可
# else:
# print(" raw:", line[:200])
return final_status
async def call_leader(main_agent_name: str, thread_ids: dict[str, str]) -> list[list[str]]:
"""步骤 2: leader 阶段 —— 总控读完用户问题,产出派活清单."""
print(f"\n>>> [2/3] POST /generate_mul_agent (leader, agent={main_agent_name})")
status = await stream_generate(
{
"agent_type": "leader",
"d_agent_thread_id": thread_ids,
"agent_name": main_agent_name,
"new_message": USER_QUESTION,
"skill_stop_names": SKILL_STOP_NAMES,
}
)
dispatch = status or []
print(f" 派活清单 = {json.dumps(dispatch, ensure_ascii=False, indent=2)}")
return dispatch
async def call_specials(thread_ids: dict[str, str], dispatch: list[list[str]]) -> None:
"""步骤 3: 把派活清单里每一项交给对应子 agent 跑."""
if not dispatch:
print("\n>>> [3/3] 总控没派出任何任务,直接结束。")
return
for idx, item in enumerate(dispatch, 1):
agent_name, task_text = item[0], item[1]
print(f"\n>>> [3/3] ({idx}/{len(dispatch)}) POST /generate_mul_agent (special, agent={agent_name})")
print(f" task = {task_text[:120]}{'...' if len(task_text) > 120 else ''}")
result = await stream_generate(
{
"agent_type": "special",
"d_agent_thread_id": thread_ids,
"agent_name": agent_name,
"new_message": task_text,
"skill_stop_names": [],
}
)
print(f" 完成: {result}")
async def main() -> None:
main_agent_name, thread_ids = await call_agent_init()
dispatch = await call_leader(main_agent_name, thread_ids)
await call_specials(thread_ids, dispatch)
print("\n✅ demo 跑完")
if __name__ == "__main__":
asyncio.run(main())