136 lines
5.4 KiB
Python
136 lines
5.4 KiB
Python
"""
|
||
知你客服 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:
|
||
# 8037 使用 form 登录;先 form,失败再试 JSON
|
||
r = requests.post(
|
||
url,
|
||
data={"username": username, "password": password},
|
||
headers={"Content-Type": "application/x-www-form-urlencoded"},
|
||
timeout=10,
|
||
)
|
||
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 正确"
|
||
)
|
||
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 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
|
||
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)
|