Files
inquiry_robot/inquiry-agent/agent/ledger/__init__.py
T

154 lines
4.9 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.
"""
主账 HTTP 客户端:只打主账 API,不直连 TMS / MySQL。
正式写操作必须带幂等键;Graph 成功 ≠ 主账成功。
本文件提供可联调的 ping / JSON 请求骨架;不做六态业务分支。
"""
from __future__ import annotations
import logging
from dataclasses import dataclass, field
from typing import Any, Optional
import httpx
from agent.config import Settings, get_settings
logger = logging.getLogger(__name__)
@dataclass
class LedgerResponse:
"""主账一次 HTTP 调用的结果摘要(不含密钥)。"""
ok: bool
status_code: int
data: Any = None
error: str = ""
@dataclass
class LedgerClient:
"""
主账客户端。
base_url:BIZ_API_BASE_URL(新系统多为 http://127.0.0.1:8180/jeecgboot)。
超时:默认 10s,禁止无限阻塞 Worker 槽。
禁止:在本类拼 TMS HMAC 或写工单 SQL。
"""
base_url: str
timeout_seconds: float = 10.0
service_token: str = ""
_client: Optional[httpx.Client] = field(default=None, repr=False)
@classmethod
def from_settings(cls, settings: Optional[Settings] = None) -> "LedgerClient":
"""从环境组装客户端。"""
cfg = settings or get_settings()
return cls(
base_url=(cfg.biz_api_base_url or "").rstrip("/"),
timeout_seconds=float(getattr(cfg, "biz_api_timeout_seconds", 10) or 10),
service_token=(getattr(cfg, "biz_api_token", None) or ""),
)
def _http(self) -> httpx.Client:
if self._client is None:
headers = {"Accept": "application/json"}
if self.service_token:
headers["X-Access-Token"] = self.service_token
self._client = httpx.Client(
base_url=self.base_url,
timeout=self.timeout_seconds,
headers=headers,
)
return self._client
def close(self) -> None:
"""关闭连接池。"""
if self._client is not None:
self._client.close()
self._client = None
def health_url(self) -> str:
"""健康检查相对路径候选的完整 URL(日志用)。"""
return f"{self.base_url}/"
def ping(self) -> LedgerResponse:
"""
探测主账是否可达。
尝试 GET / 或常见健康路径;4xx/5xx 记入 error,不抛给回调线程。
副作用:出站 HTTP。
"""
if not self.base_url:
return LedgerResponse(ok=False, status_code=0, error="BIZ_API_BASE_URL 未配置")
paths = ["/", "/sys/randomImage/ping-check"]
last: LedgerResponse = LedgerResponse(ok=False, status_code=0, error="no_attempt")
for path in paths:
try:
resp = self._http().get(path)
last = LedgerResponse(
ok=resp.status_code < 500,
status_code=resp.status_code,
data={"path": path},
)
if resp.status_code < 500:
logger.info(
"ledger.ping ok status=%s path=%s",
resp.status_code,
path,
)
return last
except httpx.HTTPError as exc:
last = LedgerResponse(ok=False, status_code=0, error=str(exc))
logger.warning("ledger.ping 失败 path=%s err=%s", path, exc)
return last
def request_json(
self,
method: str,
path: str,
*,
json_body: Optional[dict[str, Any]] = None,
idempotency_key: str = "",
) -> LedgerResponse:
"""
通用 JSON 请求骨架。
写操作应传入 idempotency_key(写入头 X-Idempotency-Key)。
不做业务语义解析;调用方根据主账合同处理 result。
"""
headers: dict[str, str] = {}
if idempotency_key:
headers["X-Idempotency-Key"] = idempotency_key
try:
resp = self._http().request(
method.upper(),
path,
json=json_body,
headers=headers,
)
data: Any
try:
data = resp.json()
except ValueError:
data = {"text": resp.text[:500]}
ok = 200 <= resp.status_code < 300
if not ok:
logger.warning(
"ledger.request 非成功 status=%s path=%s",
resp.status_code,
path,
)
return LedgerResponse(ok=ok, status_code=resp.status_code, data=data)
except httpx.HTTPError as exc:
logger.warning("ledger.request 异常 path=%s err=%s", path, exc)
return LedgerResponse(ok=False, status_code=0, error=str(exc))
from agent.ledger.api_shell import LedgerApiShell # noqa: E402
__all__ = ["LedgerClient", "LedgerResponse", "LedgerApiShell"]