483 lines
17 KiB
Python
483 lines
17 KiB
Python
"""
|
||
测试流式接口的完整脚本
|
||
API流程:
|
||
1. 登录获取token
|
||
2. 创建thread(可选,也可以使用无状态接口)
|
||
3. 调用流式接口
|
||
|
||
两种方式:
|
||
- 方式1: POST /api/runs/stream (无状态,自动创建thread)
|
||
- 方式2: POST /api/threads/{thread_id}/runs/stream (需要先创建thread)
|
||
"""
|
||
import requests
|
||
import json
|
||
import asyncio
|
||
import httpx # 异步 HTTP 客户端
|
||
# ============ 配置区域 ============
|
||
BASE_URL = "http://127.0.0.1:8001"
|
||
LOGIN_URL = f"{BASE_URL}/api/v1/auth/login/username"
|
||
THREADS_URL = f"{BASE_URL}/api/threads"
|
||
RUNS_STREAM_URL = f"{BASE_URL}/api/runs/stream" # 无状态流式接口
|
||
|
||
# ============ 登录获取token ============
|
||
def get_token(username: str) -> str:
|
||
"""登录获取token"""
|
||
response = requests.post(
|
||
LOGIN_URL,
|
||
json={"username": username},
|
||
headers={"Content-Type": "application/json"}
|
||
)
|
||
# print(f"登录状态码: {response.status_code}")
|
||
data = response.json()
|
||
# print(f"登录响应: {json.dumps(data, indent=2, ensure_ascii=False)}")
|
||
|
||
# 根据实际返回结构调整
|
||
token = data.get("token") or data.get("access_token") or data.get("data", {}).get("token")
|
||
return token
|
||
# ============ 方式2: 先创建thread,再调用流式接口 ============
|
||
def create_thread(token: str) -> str:
|
||
"""创建thread,返回thread_id"""
|
||
headers = {
|
||
"Content-Type": "application/json",
|
||
"Authorization": f"Bearer {token}"
|
||
}
|
||
|
||
response = requests.post(
|
||
THREADS_URL,
|
||
json={}, # 空body,使用默认值
|
||
headers=headers
|
||
)
|
||
|
||
print(f"创建thread状态码: {response.status_code}")
|
||
data = response.json()
|
||
print(f"创建thread响应: {json.dumps(data, indent=2, ensure_ascii=False)}")
|
||
|
||
return data.get("thread_id")
|
||
def get_agent_name_description(agent_name="Taiwan-intelligence-gathering-agent"):
|
||
url = f"{BASE_URL}/api/agents/{agent_name}"
|
||
token=get_token("ji")
|
||
headers = {
|
||
"Content-Type": "application/json",
|
||
"Authorization": f"Bearer {token}"
|
||
}
|
||
response = requests.get(
|
||
url,
|
||
headers=headers,
|
||
)
|
||
print(response.json())
|
||
return response.json()["name"].lower(),"agent_name:"+response.json()["name"].lower()+","+response.json()["description"]
|
||
|
||
# ============ 创建智能体 ============
|
||
def create_agent(
|
||
name: str,
|
||
description: str = "",
|
||
model: str | None = None,
|
||
tool_groups: list[str] | None = None,
|
||
skills: list[str] | None = None,
|
||
soul: str = "",
|
||
):
|
||
"""
|
||
创建一个新的智能体
|
||
|
||
Args:
|
||
name: 智能体名称(必须是 hyphen-case,如 my-agent)
|
||
description: 智能体描述
|
||
model: 可选的模型覆盖(如 gpt-4, claude-3-opus 等)
|
||
tool_groups: 工具组白名单(如 ["web", "file"])
|
||
skills: 技能白名单(如 ["skill1", "skill2"])
|
||
soul: SOUL.md 内容(智能体人格/身份描述)
|
||
|
||
Returns:
|
||
创建结果
|
||
"""
|
||
TOKEN =get_token("ji")
|
||
url = f"{BASE_URL}/api/agents"
|
||
headers = {
|
||
"Content-Type": "application/json",
|
||
"Authorization": f"Bearer {TOKEN}"
|
||
}
|
||
|
||
payload = {
|
||
"name": name,
|
||
"description": description,
|
||
"soul": soul
|
||
}
|
||
|
||
# 添加可选字段
|
||
if model is not None:
|
||
payload["model"] = model
|
||
if tool_groups is not None:
|
||
payload["tool_groups"] = tool_groups
|
||
if skills is not None:
|
||
payload["skills"] = skills
|
||
|
||
print(f"请求: POST {url}")
|
||
print(f"请求体: {json.dumps(payload, indent=2, ensure_ascii=False)}")
|
||
|
||
response = requests.post(url, json=payload, headers=headers)
|
||
|
||
print(f"状态码: {response.status_code}")
|
||
|
||
if response.status_code == 201:
|
||
result = response.json()
|
||
print(f"✓ 智能体创建成功!")
|
||
print(f"响应: {json.dumps(result, indent=2, ensure_ascii=False)}")
|
||
return result
|
||
else:
|
||
print(f"✗ 创建失败: {response.text}")
|
||
return f"✗ 创建失败: {response.text}"
|
||
|
||
def create_main_agent(zhihui_name,agent_names):
|
||
d={}
|
||
token=get_token("ji")
|
||
l=[]
|
||
for i in agent_names:
|
||
# print(i)
|
||
name,description=get_agent_name_description(i)
|
||
thread_id_tmp=create_thread(token)
|
||
d[name]=thread_id_tmp
|
||
l.append(description)
|
||
name="你是总控控制协调智能体,负责协调其他智能体干活"
|
||
miaoshu=f"""#核心定位:
|
||
用途:跟用用户的任务目标,调用其他智能体干活,只负责协调汇总,当你觉得已有信息支持回答用户问题时候,停止调度,并输出最后的结果
|
||
#调度方法:
|
||
通过使用agent_orchestration技能调用其他智能体干活,使用技能时传入agent_name和需要做的活
|
||
#可调度智能体:
|
||
{l}"""
|
||
thread_id_tmp=create_thread(token)
|
||
engname=zhihui_name
|
||
d[engname]=thread_id_tmp
|
||
tmp_skills=["agent_orchestration"]
|
||
tmpres=create_agent(
|
||
name=engname,
|
||
description=name,
|
||
model="glm-5", # 指定模型
|
||
skills=tmp_skills, # 关联技能
|
||
soul=miaoshu)
|
||
# print("创建智能体响应:",tmpres)
|
||
if "失败" in tmpres and "exists" not in tmpres:
|
||
return tmpres
|
||
print(f"创建协调智能体成功,可以协调列表:{l}")
|
||
return d
|
||
import re
|
||
def extract_name_task(text="python3 scripts/agent_orchestration.py \"Us-intelligence-gathering-agent\" \"收集和分析美国人民对特朗普的看法,包括支持率和反对率、不同群体的观点差异、主要支持理由和反对理由等\""):
|
||
try:
|
||
pattern = r'\.py\s+"([^"]*)"\s+"([^"]*)"'
|
||
match = re.search(pattern, text)
|
||
if match:
|
||
agent_name = match.group(1)
|
||
task_content = match.group(2)
|
||
return agent_name,task_content
|
||
except:
|
||
return "",""
|
||
def update_agent_message(thread_id,new_content):
|
||
token=get_token("ji")
|
||
url = f"{BASE_URL}/api/threads/{thread_id}/state"
|
||
headers = {
|
||
"Content-Type": "application/json",
|
||
"Authorization": f"Bearer {token}"
|
||
}
|
||
tmp = requests.get(
|
||
url,
|
||
headers=headers
|
||
).json()
|
||
# print(tmp)
|
||
if "messages" in tmp["values"]:
|
||
tmp["values"]["messages"].append({"type": "human", "content": new_content})
|
||
else:
|
||
tmp["values"]["messages"]=[]
|
||
tmp["values"]["messages"].append({"type": "human", "content": new_content})
|
||
url = f"{BASE_URL}/api/threads/{thread_id}/state"
|
||
headers = {
|
||
"Content-Type": "application/json",
|
||
"Authorization": f"Bearer {token}"
|
||
}
|
||
response = requests.post(
|
||
url,
|
||
json=tmp,
|
||
headers=headers,
|
||
stream=True
|
||
)
|
||
del tmp
|
||
print(f"线程{thread_id}更新状态码: {response.status_code}")
|
||
def Broadcast_message(message,d_agent_thread_id,pingbi_agent_name):
|
||
for key in d_agent_thread_id:
|
||
if key not in pingbi_agent_name:
|
||
update_agent_message(d_agent_thread_id[key],message)
|
||
print("广播消息成功:",key,message)
|
||
null=None
|
||
true=True
|
||
false=False
|
||
async def leader_agent(d_agent_thread_id, agent_name, new_message, skill_stop_names):
|
||
"""使用 httpx 异步请求的 leader_agent"""
|
||
# 使用 to_thread 异步运行同步的 Broadcast_message
|
||
await asyncio.to_thread(Broadcast_message, "总控智能体收到用户消息:" + new_message, d_agent_thread_id, [agent_name])
|
||
thread_id = d_agent_thread_id[agent_name]
|
||
l = []
|
||
token = get_token("ji")
|
||
url = f"{BASE_URL}/api/threads/{thread_id}/runs/stream"
|
||
headers = {
|
||
"Content-Type": "application/json",
|
||
"Authorization": f"Bearer {token}"
|
||
}
|
||
payload = {
|
||
"input": {
|
||
"messages": [
|
||
{
|
||
"type": "human",
|
||
"content": [
|
||
{
|
||
"type": "text",
|
||
"text": new_message
|
||
}
|
||
],
|
||
"additional_kwargs": {}
|
||
}
|
||
]
|
||
},
|
||
"config": {
|
||
"recursion_limit": 1000
|
||
},
|
||
"context": {
|
||
"agent_name": agent_name,
|
||
"model_name": "deepseek-chat",
|
||
"mode": "pro",
|
||
"reasoning_effort": "medium",
|
||
"thinking_enabled": true,
|
||
"is_plan_mode": false,
|
||
"subagent_enabled": false,
|
||
"thread_id": thread_id
|
||
},
|
||
"stream_mode": [
|
||
"messages-tuple",
|
||
"values",
|
||
],
|
||
"stream_subgraphs": true,
|
||
"stream_resumable": true,
|
||
"assistant_id": "lead_agent",
|
||
"on_disconnect": "continue",
|
||
"excluded_tools": ["web_search"],
|
||
"skill_stop_names": skill_stop_names,
|
||
}
|
||
|
||
async with httpx.AsyncClient(timeout=None) as client:
|
||
async with client.stream("POST", url, json=payload, headers=headers) as response:
|
||
print(f"状态码: {response.status_code}")
|
||
if response.status_code != 200:
|
||
body = await response.aread()
|
||
raise RuntimeError(
|
||
f"upstream {url} returned HTTP {response.status_code}: "
|
||
f"{body.decode('utf-8', 'replace')[:1000]}"
|
||
)
|
||
async for line in response.aiter_lines():
|
||
if line:
|
||
yield line
|
||
if line[:5] == "data:":
|
||
print(line)
|
||
l.append(line)
|
||
if len(l) < 2:
|
||
raise RuntimeError(
|
||
f"upstream stream produced no usable data lines (got {len(l)} line(s)). "
|
||
f"Check the server log on {url} for the real failure."
|
||
)
|
||
|
||
null = None
|
||
tmpres = eval(l[-2][5:])
|
||
tmpres = tmpres["messages"][-1]
|
||
tmp_task = []
|
||
if tmpres.get("tool_calls", []) == []:
|
||
yield {"status": []}
|
||
else:
|
||
for tool_call in tmpres.get("tool_calls", []):
|
||
try:
|
||
agentname, task_text = extract_name_task(str(tool_call["args"]))
|
||
print("agentname", agentname)
|
||
print("task_text:", task_text)
|
||
# 使用 to_thread 异步运行同步的 Broadcast_message
|
||
await asyncio.to_thread(Broadcast_message, f"总控智能体给{agentname}派活,内容为:" + task_text, d_agent_thread_id, [agent_name, agentname])
|
||
tmp_task.append([agentname, task_text])
|
||
except:
|
||
pass
|
||
yield {"status": tmp_task}
|
||
|
||
|
||
async def special_agent(d_agent_thread_id, agent_name, new_message, skill_stop_names):
|
||
"""使用 httpx 异步请求的 special_agent"""
|
||
thread_id = d_agent_thread_id[agent_name]
|
||
l = []
|
||
token = get_token("ji")
|
||
url = f"{BASE_URL}/api/threads/{thread_id}/runs/stream"
|
||
headers = {
|
||
"Content-Type": "application/json",
|
||
"Authorization": f"Bearer {token}"
|
||
}
|
||
payload = {
|
||
"input": {
|
||
"messages": [
|
||
{
|
||
"type": "human",
|
||
"content": [
|
||
{
|
||
"type": "text",
|
||
"text": new_message
|
||
}
|
||
],
|
||
"additional_kwargs": {}
|
||
}
|
||
]
|
||
},
|
||
"config": {
|
||
"recursion_limit": 1000
|
||
},
|
||
"context": {
|
||
"agent_name": agent_name,
|
||
"model_name": "deepseek-chat",
|
||
"mode": "pro",
|
||
"reasoning_effort": "medium",
|
||
"thinking_enabled": true,
|
||
"is_plan_mode": false,
|
||
"subagent_enabled": false,
|
||
"thread_id": thread_id
|
||
},
|
||
"stream_mode": [
|
||
"messages-tuple",
|
||
"values",
|
||
],
|
||
"stream_subgraphs": true,
|
||
"stream_resumable": true,
|
||
"assistant_id": "lead_agent",
|
||
"on_disconnect": "continue",
|
||
"excluded_tools": ["web_search", "ask_clarification", "present_files", "view_image"],
|
||
"skill_stop_names": skill_stop_names,
|
||
}
|
||
|
||
async with httpx.AsyncClient(timeout=None) as client:
|
||
async with client.stream("POST", url, json=payload, headers=headers) as response:
|
||
print(f"状态码: {response.status_code}")
|
||
if response.status_code != 200:
|
||
body = await response.aread()
|
||
raise RuntimeError(
|
||
f"upstream {url} returned HTTP {response.status_code}: "
|
||
f"{body.decode('utf-8', 'replace')[:1000]}"
|
||
)
|
||
async for line in response.aiter_lines():
|
||
if line:
|
||
yield line
|
||
if line[:5] == "data:":
|
||
print(line)
|
||
l.append(line)
|
||
if len(l) < 2:
|
||
raise RuntimeError(
|
||
f"upstream stream produced no usable data lines (got {len(l)} line(s)). "
|
||
f"Check the server log on {url} for the real failure."
|
||
)
|
||
|
||
null = None
|
||
tmpres = eval(l[-2][5:])
|
||
tmpres = tmpres["messages"][-1]
|
||
# 使用 to_thread 异步运行同步的 Broadcast_message
|
||
await asyncio.to_thread(Broadcast_message, f"子智能体{agent_name}完成{new_message}工作,交付内容为:" + tmpres["content"], d_agent_thread_id, [agent_name])
|
||
yield {"status": f"子智能体{agent_name}完成{new_message}工作"}
|
||
|
||
async def agent_init(agent_names, main_agent_name):
|
||
"""异步的 agent_init,同步函数调用使用 to_thread"""
|
||
agent_names = [i.lower() for i in agent_names]
|
||
# 使用 to_thread 异步运行同步的 create_main_agent
|
||
tmpd = await asyncio.to_thread(create_main_agent, main_agent_name, agent_names)
|
||
message = """
|
||
你的职责:你只是若干个智能体的其中一员,你们的共同目标是完成一个庞大的任务,总控控制协调智能体会分配特定的活给你,你只需要用你自身的能力来完成这个特定的活
|
||
注意:你只是一个子智能体,你不必把你的分析结果存放到文件里面去,你只关心于完成总控控制协调智能体分配的活即可
|
||
"""
|
||
# 使用 to_thread 异步运行同步的 Broadcast_message
|
||
await asyncio.to_thread(Broadcast_message, message, tmpd, [main_agent_name])
|
||
return main_agent_name, tmpd
|
||
|
||
|
||
from fastapi import FastAPI, HTTPException,Body
|
||
from fastapi.responses import StreamingResponse
|
||
from fastapi.middleware.cors import CORSMiddleware
|
||
import asyncio
|
||
import uvicorn
|
||
|
||
app = FastAPI()
|
||
# CORS 配置
|
||
app.add_middleware(
|
||
CORSMiddleware,
|
||
allow_origins=["*"],
|
||
allow_credentials=True,
|
||
allow_methods=["*"],
|
||
allow_headers=["*"],
|
||
)
|
||
|
||
import time
|
||
@app.post("/generate_mul_agent")
|
||
async def generate_stream(payload: dict = Body(...)):
|
||
"""
|
||
根据 payload 中的 agent_type 字段判断调用 leader_agent 还是 special_agent
|
||
|
||
payload 结构示例:
|
||
{
|
||
"agent_type": "leader", # 或 "special"
|
||
"d_agent_thread_id": {...},
|
||
"agent_name": "xxx",
|
||
"new_message": "xxx",
|
||
"skill_stop_names": [...]
|
||
}
|
||
"""
|
||
# 从 payload 中提取参数
|
||
agent_type = payload.get("agent_type", "leader") # 默认为 leader
|
||
d_agent_thread_id = payload.get("d_agent_thread_id", {})
|
||
agent_name = payload.get("agent_name", "")
|
||
new_message = payload.get("new_message", "")
|
||
skill_stop_names = payload.get("skill_stop_names", [])
|
||
|
||
# 流式响应生成器
|
||
async def stream_generator():
|
||
# 根据 agent_type 选择调用的函数
|
||
if agent_type == "leader":
|
||
async_generator = leader_agent(d_agent_thread_id, agent_name, new_message, skill_stop_names)
|
||
else: # special
|
||
async_generator = special_agent(d_agent_thread_id, agent_name, new_message, skill_stop_names)
|
||
|
||
# 异步迭代生成器
|
||
async for chunk in async_generator:
|
||
if isinstance(chunk, dict):
|
||
yield f"data: {json.dumps(chunk, ensure_ascii=False)}\n\n"
|
||
else:
|
||
yield f"{chunk}"
|
||
|
||
# 使用 SSE 流式响应
|
||
return StreamingResponse(
|
||
stream_generator(),
|
||
media_type="text/event-stream",
|
||
headers={
|
||
"Cache-Control": "no-cache",
|
||
"X-Accel-Buffering": "no",
|
||
"Connection": "keep-alive",
|
||
}
|
||
)
|
||
|
||
|
||
@app.post("/agent_init")
|
||
async def init_agents(payload: dict = Body(...)):
|
||
"""
|
||
初始化智能体接口
|
||
payload 结构示例:
|
||
{
|
||
"agent_names": ["agent1", "agent2"],
|
||
"main_agent_name": "main-agent"
|
||
}
|
||
"""
|
||
agent_names = payload.get("agent_names", [])
|
||
main_agent_name = payload.get("main_agent_name", "main-agent")
|
||
|
||
# 调用异步的 agent_init
|
||
name, thread_id_dict = await agent_init(agent_names, main_agent_name)
|
||
|
||
return {
|
||
"status": "success",
|
||
"main_agent_name": name,
|
||
"thread_ids": thread_id_dict
|
||
}
|
||
if __name__ == "__main__":
|
||
uvicorn.run("testapi:app", host="0.0.0.0", port=8095, workers=1,limit_concurrency=1000) # 多进程提升并发能力 |