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

141 lines
6.4 KiB
Python

# -*- coding: utf-8 -*-
"""开放问答接口测试脚本(流式版,Jupyter / 命令行均可)。
接口只返回「最终结果」:智能体把调研 / 工具调用全部跑完后,只把最后那段
文本(markdown)放在 answer 里返回 —— 不含中间过程、工具调用、思考。
为什么默认用流式(/api/open/chat/stream):
- 长任务(联网调研、出报告)同步阻塞那版整段没有下行字节,容易被前置 nginx 反代
的 proxy_read_timeout(默认 60s)掐成 504。流式版每隔几秒就有心跳/增量下行,
代理不会判定空闲超时,**多久都不会超时**。
- 客户端只需读到 `event: result` 解析 answer 即可,中途 delta/progress/心跳都忽略。
用法:改下面「配置」段,然后整段运行;answer 变量就是最终 md 文本。
无第三方依赖(纯标准库 urllib)。
"""
import json
import time
import urllib.error
import urllib.request
# ====================== 配置(按需修改这里) ======================
BASE_URL = "http://47.94.209.59:2026" # 走 nginx 用 :2026;Gateway 直连用 :8001
STREAM = True # True=流式(推荐,不超时);False=同步阻塞(短问题用)
MESSAGE = "帮我调研一下 赖清德,输出 markdown 报告。" # 要问的问题
MODEL_NAME = "deepseek-ai/DeepSeek-V4-Flash" # 模型名(config.yaml models[].name);None=默认模型
AGENT_NAME = "你好君" # 智能体中文名称;None=默认 lead agent。不确定填啥先跑 ping_agents()
THINKING_ENABLED = False # False=关闭思考(SiliconFlow/内网模型也会被强制关掉);True=开启
SHOW_LIVE = True # 流式时是否实时打印增量正文(看着它一点点出),只看结果可设 False
READ_TIMEOUT = 120 # 单次 socket 读超时(秒)。服务端每 ~15s 发心跳,故 120s 绰绰有余
TIMEOUT = 6000 # 同步(非流式)模式的整体超时(秒)
# ===============================================================
def open_chat(message, model_name=None, agent_name=None, thinking_enabled=False,
base_url=BASE_URL, timeout=TIMEOUT):
"""同步阻塞版:整段跑完才返回。短问题用;长任务请用 open_chat_stream。"""
url = base_url.rstrip("/") + "/api/open/chat"
payload = {
"message": message,
"model_name": model_name,
"agent_name": agent_name, # 按中文名称选智能体
"thinking_enabled": thinking_enabled,
}
data = json.dumps(payload, ensure_ascii=False).encode("utf-8")
req = urllib.request.Request(
url, data=data,
headers={"Content-Type": "application/json; charset=utf-8"},
method="POST",
)
with urllib.request.urlopen(req, timeout=timeout) as resp:
result = json.loads(resp.read().decode("utf-8"))
return result["answer"] # 只返回最终结果
def open_chat_stream(message, model_name=None, agent_name=None, thinking_enabled=False,
base_url=BASE_URL, read_timeout=READ_TIMEOUT, show_live=SHOW_LIVE):
"""流式版:读 SSE,边跑边收心跳/增量(不超时),最终从 event: result 取 answer。"""
url = base_url.rstrip("/") + "/api/open/chat/stream"
payload = {
"message": message,
"model_name": model_name,
"agent_name": agent_name,
"thinking_enabled": thinking_enabled,
}
data = json.dumps(payload, ensure_ascii=False).encode("utf-8")
req = urllib.request.Request(
url, data=data,
headers={"Content-Type": "application/json; charset=utf-8", "Accept": "text/event-stream"},
method="POST",
)
answer = None
error = None
event_name = None
data_lines = []
with urllib.request.urlopen(req, timeout=read_timeout) as resp:
for raw in resp: # 按行读取,服务端心跳让读取永不空闲超时
line = raw.decode("utf-8").rstrip("\r\n")
if line.startswith(":"): # SSE 注释行(心跳),忽略
continue
if line == "": # 空行 = 一个事件结束,分发
if data_lines:
payload_str = "\n".join(data_lines)
try:
obj = json.loads(payload_str)
except json.JSONDecodeError:
obj = {}
if event_name == "result":
answer = obj.get("answer")
elif event_name == "error":
error = obj.get("detail") or payload_str
elif event_name == "delta" and show_live:
print(obj.get("text", ""), end="", flush=True)
event_name, data_lines = None, []
continue
if line.startswith("event:"):
event_name = line[len("event:"):].strip()
elif line.startswith("data:"):
data_lines.append(line[len("data:"):].lstrip())
if error:
raise RuntimeError(f"接口返回错误:{error}")
if answer is None:
raise RuntimeError("未收到 event: result(流可能被中途断开)。")
return answer
def ping_agents(base_url=BASE_URL, timeout=30):
"""探活:GET /api/open/agents。秒回说明链路 OK,顺带看有哪些 agent_name 可填。"""
with urllib.request.urlopen(base_url.rstrip("/") + "/api/open/agents", timeout=timeout) as r:
return json.loads(r.read().decode("utf-8"))
# ====================== 调用 ======================
if __name__ == "__main__":
# 先探活(失败说明是链路/部署问题,而非模型问题)
try:
info = ping_agents()
print(f"[探活OK] 可用智能体 {info.get('count', 0)} 个\n")
except Exception as exc: # noqa: BLE001
print(f"[探活失败] {exc!r} —— 先排查 BASE_URL / 端口 / 服务是否在跑\n")
_t0 = time.time()
if STREAM:
answer = open_chat_stream(MESSAGE, MODEL_NAME, AGENT_NAME, THINKING_ENABLED)
else:
answer = open_chat(MESSAGE, MODEL_NAME, AGENT_NAME, THINKING_ENABLED)
print(f"\n\n(耗时 {time.time() - _t0:.1f}s)\n")
print(answer) # answer 就是最终 markdown 文本
# ============ (可选)Jupyter 里渲染 markdown ============
# from IPython.display import Markdown, display
# display(Markdown(answer))
# ============ (可选)存成 .md ============
# with open("result.md", "w", encoding="utf-8") as f:
# f.write(answer)