Files
aiagent/saars/backend/app/services/agent_proxy.py

136 lines
5.4 KiB
Python
Raw Normal View History

2026-03-07 09:01:00 +08:00
"""
知你客服 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:
2026-03-07 13:59:49 +08:00
# 8037 使用 form 登录;先 form失败再试 JSON
2026-03-07 09:01:00 +08:00
r = requests.post(
url,
data={"username": username, "password": password},
headers={"Content-Type": "application/x-www-form-urlencoded"},
timeout=10,
)
2026-03-07 13:59:49 +08:00
if r.status_code == 422:
logger.warning("平台登录 form 返回 422尝试 JSON: %s", r.text[:200] if r.text else "")
r = requests.post(
url,
json={"username": username, "password": password},
headers={"Content-Type": "application/json"},
timeout=10,
)
if r.status_code == 401:
logger.warning("平台登录仍 401请检查 PLATFORM_USERNAME/PLATFORM_PASSWORD 及平台账号: %s", r.text[:200] if r.text else "")
raise ValueError(
"知你客服平台登录失败(401),请确认 8037 服务已启动且 PLATFORM_USERNAME/PLATFORM_PASSWORD 正确"
)
2026-03-07 09:01:00 +08:00
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
2026-03-07 13:59:49 +08:00
except requests.exceptions.HTTPError as e:
if e.response is not None and e.response.status_code == 401:
raise ValueError(
"知你客服平台登录失败(401),请确认 8037 服务已启动且账号配置正确"
)
logger.exception("平台登录失败: %s", e)
raise
except ValueError:
raise
2026-03-07 09:01:00 +08:00
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)