Files
aiagent/saars/backend/app/services/agent_proxy.py
2026-03-07 09:01:00 +08:00

113 lines
4.3 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
知你客服 Agent 代理方式一App 后端代理)
调用低代码平台执行 API登录拿 Token -> 创建执行 -> 轮询状态 -> 取 output_data 作为回复。
"""
import time
import logging
import requests
logger = logging.getLogger(__name__)
# 内存缓存platform_token, token_expires_at (简单实现,生产可用 Redis)
_platform_token = None
_token_expires_at = 0
TOKEN_BUFFER_SECONDS = 300 # 提前 5 分钟视为过期
def _get_platform_token(base_url, username, password):
"""登录平台获取 access_token带简单内存缓存。"""
global _platform_token, _token_expires_at
if _platform_token and time.time() < _token_expires_at - TOKEN_BUFFER_SECONDS:
return _platform_token
url = f"{base_url.rstrip('/')}/api/v1/auth/login"
try:
r = requests.post(
url,
data={"username": username, "password": password},
headers={"Content-Type": "application/x-www-form-urlencoded"},
timeout=10,
)
r.raise_for_status()
data = r.json()
token = data.get("access_token")
if not token:
raise ValueError("平台登录响应无 access_token")
_platform_token = token
_token_expires_at = time.time() + 3600 # 假设 1 小时有效
return token
except Exception as e:
logger.exception("平台登录失败: %s", e)
raise
def _create_execution(base_url, token, agent_id, input_data):
"""POST /api/v1/executions"""
url = f"{base_url.rstrip('/')}/api/v1/executions"
r = requests.post(
url,
json={"agent_id": agent_id, "input_data": input_data},
headers={"Authorization": f"Bearer {token}", "Content-Type": "application/json"},
timeout=15,
)
r.raise_for_status()
return r.json()
def _get_execution_status(base_url, token, execution_id):
"""GET /api/v1/executions/{id}/status"""
url = f"{base_url.rstrip('/')}/api/v1/executions/{execution_id}/status"
r = requests.get(url, headers={"Authorization": f"Bearer {token}"}, timeout=10)
r.raise_for_status()
return r.json()
def _get_execution(base_url, token, execution_id):
"""GET /api/v1/executions/{id}"""
url = f"{base_url.rstrip('/')}/api/v1/executions/{execution_id}"
r = requests.get(url, headers={"Authorization": f"Bearer {token}"}, timeout=10)
r.raise_for_status()
return r.json()
def _extract_reply(output_data):
"""从 output_data 中提取回复文本(知你客服 End 节点输出结构以实际为准)。"""
if output_data is None:
return ""
if isinstance(output_data, str):
return output_data
for key in ("reply", "content", "output", "text", "message", "result"):
if key in output_data and output_data[key]:
v = output_data[key]
return v if isinstance(v, str) else str(v)
if isinstance(output_data, dict):
for v in output_data.values():
if isinstance(v, str) and v.strip():
return v
return str(output_data)
def chat_with_agent(base_url, username, password, agent_id, message, user_id, poll_interval=0.8, poll_timeout=60):
"""
与知你客服对话:创建执行 -> 轮询直到 completed/failed -> 返回回复文本。
"""
token = _get_platform_token(base_url, username, password)
input_data = {"query": message, "user_id": user_id or "default"}
exec_body = _create_execution(base_url, token, agent_id, input_data)
execution_id = exec_body.get("id")
if not execution_id:
raise ValueError("创建执行未返回 id")
status = exec_body.get("status", "pending")
deadline = time.time() + poll_timeout
status_body = None
while status in ("pending", "running") and time.time() < deadline:
time.sleep(poll_interval)
status_body = _get_execution_status(base_url, token, execution_id)
status = status_body.get("status", status)
if status != "completed":
detail = _get_execution(base_url, token, execution_id)
err = detail.get("error_message") or (status_body or {}).get("error_message") or "执行未完成或失败"
raise ValueError(err)
detail = _get_execution(base_url, token, execution_id)
output_data = detail.get("output_data")
return _extract_reply(output_data)