docs: 品牌全量口径统一为「南方基金」+ 作废文档清理
1) 客服 Agent 四份交付文档 + 构建脚手架:品牌由包装占位 XX科技 / 旧名 南方财富 统一为南方基金(热线 400-889-8899 / 官网 nffund.com),系统名改为「智能服务系统」; 同步追加 §0.4 修订记录行,工程记录行保留原占位字面以支撑硬编码扫描验收。 2) 开发文档:清理 28 份已作废/残留文档(14 份移出归档 + 14 份仓库副本), 新增《文档规整方案与开发前待决事项-2026-09-17》。 3) 客服agent 四份交付文档首次纳入本分支。
This commit is contained in:
@@ -0,0 +1,112 @@
|
||||
"""风控 Agent 专用的模型工具调用客户端。"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Protocol
|
||||
|
||||
import httpx
|
||||
|
||||
from app.core.errors import (
|
||||
DependencyUnavailableError,
|
||||
RecoverableAgentError,
|
||||
UpstreamTimeoutError,
|
||||
)
|
||||
from app.service.model_gateway import (
|
||||
DatabaseModelEndpointResolver,
|
||||
EnvironmentSecretResolver,
|
||||
)
|
||||
|
||||
|
||||
class RiskAgentEndpointResolver(Protocol):
|
||||
async def resolve(self, *, agent_type: str, task_type: str) -> list[Any]: ...
|
||||
|
||||
|
||||
class RiskAgentSecretResolver(Protocol):
|
||||
def resolve(self, secret_ref: str) -> str: ...
|
||||
|
||||
|
||||
class RiskAgentModelClient:
|
||||
"""调用 OpenAI 兼容接口,并原样返回模型消息供业务编排校验。"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
endpoint_resolver: RiskAgentEndpointResolver | None = None,
|
||||
secret_resolver: RiskAgentSecretResolver | None = None,
|
||||
client: httpx.AsyncClient | None = None,
|
||||
max_attempts: int = 2,
|
||||
) -> None:
|
||||
self.endpoint_resolver = endpoint_resolver or DatabaseModelEndpointResolver()
|
||||
self.secret_resolver = secret_resolver or EnvironmentSecretResolver()
|
||||
self.client = client
|
||||
self.max_attempts = max(1, max_attempts)
|
||||
self._owns_client = client is None
|
||||
|
||||
async def chat(
|
||||
self,
|
||||
messages: list[dict[str, Any]],
|
||||
*,
|
||||
tools: list[dict[str, Any]],
|
||||
) -> dict[str, Any]:
|
||||
endpoints = await self.endpoint_resolver.resolve(
|
||||
agent_type="risk",
|
||||
task_type="risk_agent_chat",
|
||||
)
|
||||
if not endpoints:
|
||||
raise RecoverableAgentError("没有可用的风控 Agent 模型端点")
|
||||
|
||||
last_error: Exception | None = None
|
||||
client = self.client or httpx.AsyncClient()
|
||||
try:
|
||||
for endpoint in endpoints[: self.max_attempts]:
|
||||
try:
|
||||
return await self._chat_with_endpoint(client, endpoint, messages, tools)
|
||||
except Exception as exc:
|
||||
last_error = exc
|
||||
raise RecoverableAgentError("风控 Agent 模型端点调用失败") from last_error
|
||||
finally:
|
||||
if self._owns_client:
|
||||
await client.aclose()
|
||||
|
||||
async def _chat_with_endpoint(
|
||||
self,
|
||||
client: httpx.AsyncClient,
|
||||
endpoint: Any,
|
||||
messages: list[dict[str, Any]],
|
||||
tools: list[dict[str, Any]],
|
||||
) -> dict[str, Any]:
|
||||
token = self.secret_resolver.resolve(endpoint.secret_ref)
|
||||
url = endpoint.base_url.rstrip("/") + "/chat/completions"
|
||||
payload: dict[str, Any] = {
|
||||
"model": endpoint.model_name,
|
||||
"messages": messages,
|
||||
"temperature": 0.2,
|
||||
"tools": tools,
|
||||
"tool_choice": "auto",
|
||||
}
|
||||
try:
|
||||
response = await client.post(
|
||||
url,
|
||||
headers={
|
||||
"Authorization": f"Bearer {token}",
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
json=payload,
|
||||
timeout=httpx.Timeout(endpoint.timeout_ms / 1000),
|
||||
)
|
||||
response.raise_for_status()
|
||||
body = response.json()
|
||||
except httpx.TimeoutException as exc:
|
||||
raise UpstreamTimeoutError("风控 Agent 模型调用超时") from exc
|
||||
except httpx.HTTPError as exc:
|
||||
raise DependencyUnavailableError("风控 Agent 模型调用失败") from exc
|
||||
except ValueError as exc:
|
||||
raise DependencyUnavailableError("风控 Agent 模型响应不是有效 JSON") from exc
|
||||
|
||||
choices = body.get("choices") if isinstance(body, dict) else None
|
||||
if not isinstance(choices, list) or not choices:
|
||||
raise DependencyUnavailableError("风控 Agent 模型响应缺少 choices")
|
||||
message = choices[0].get("message") if isinstance(choices[0], dict) else None
|
||||
if not isinstance(message, dict):
|
||||
raise DependencyUnavailableError("风控 Agent 模型响应缺少 message")
|
||||
return message
|
||||
Reference in New Issue
Block a user