Files
inquiry_robot/inquiry-agent/agent/channel/quote_template_parse.py
T

129 lines
4.6 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.
"""
报价模板 AI 映射入队路由。
本文件职责:校验 callback token,接收主账单元格清单,写入 recognition Stream 后快回。
禁止:在本请求线程调千问 / 等完整 mapping;禁止直连 MySQL。
Worker:recognition 槽认领 kind=quote_template_parse 后跑 mode_sheet_map。
"""
from __future__ import annotations
import logging
from typing import Any, Optional
from fastapi import APIRouter, Header, HTTPException, Response
from pydantic import BaseModel, Field
from agent.config import get_settings
from agent.jobs.recognition import enqueue_recognition
logger = logging.getLogger(__name__)
router = APIRouter(prefix="/internal/quote-template", tags=["quote-template"])
class QuoteTemplateParseBody(BaseModel):
"""主账提交的模板映射解析任务。"""
version_id: str = Field(default="", alias="versionId")
template_id: str = Field(default="", alias="templateId")
template_name: str = Field(default="", alias="templateName")
file_name: str = Field(default="", alias="fileName")
biz_type: str = Field(default="", alias="bizType")
transport_mode: str = Field(default="sea", alias="transportMode")
sheet: str = ""
cell_inventory: list[Any] = Field(default_factory=list, alias="cellInventory")
cell_count: int = Field(default=0, alias="cellCount")
yellow_count: int = Field(default=0, alias="yellowCount")
truncated: bool = False
request_id: str = Field(default="", alias="requestId")
model_config = {"populate_by_name": True}
def _expected_token() -> str:
s = get_settings()
return (s.agent_callback_token or s.inquiry_agent_callback_token or "").strip()
def _check_token(authorization: Optional[str], x_agent_token: Optional[str]) -> None:
expected = _expected_token()
if not expected:
if (get_settings().ytd_env or "").lower() == "prod":
raise HTTPException(status_code=503, detail="callback token 未配置")
logger.warning("quote-template parse:未配置 callback token,test 允许空校验")
return
provided = (x_agent_token or "").strip()
if not provided and authorization:
auth = authorization.strip()
if auth.lower().startswith("bearer "):
provided = auth[7:].strip()
else:
provided = auth
if provided != expected:
raise HTTPException(status_code=401, detail="unauthorized")
@router.get("/ping")
def parse_ping() -> dict[str, str]:
return {"status": "ok", "channel": "quote-template-parse"}
@router.post("/parse")
def enqueue_parse(
body: QuoteTemplateParseBody,
response: Response,
authorization: Optional[str] = Header(default=None),
x_agent_callback_token: Optional[str] = Header(
default=None, alias="X-Agent-Callback-Token"
),
) -> dict[str, Any]:
"""
接收主账解析任务:入 recognition 队列后立即 202。
不在此调用千问。
"""
_check_token(authorization, x_agent_callback_token)
version_id = (body.version_id or "").strip()
if not version_id:
raise HTTPException(status_code=400, detail="versionId 必填")
inventory = body.cell_inventory or []
if not inventory:
raise HTTPException(status_code=400, detail="cellInventory 不能为空")
payload = {
"versionId": version_id,
"templateId": body.template_id,
"templateName": body.template_name,
"fileName": body.file_name,
"bizType": body.biz_type,
"transportMode": (body.transport_mode or "sea").strip().lower() or "sea",
"sheet": body.sheet or "",
"cellInventory": inventory,
"cellCount": body.cell_count or len(inventory),
"yellowCount": body.yellow_count,
"truncated": bool(body.truncated),
}
result = enqueue_recognition(
kind="quote_template_parse",
payload=payload,
idempotency_key=(body.request_id or "").strip()
or f"quote-tpl-parse:{version_id}:{len(inventory)}",
)
if not result.accepted:
# 幂等重复:视为已接受,避免主账把「重复」当成失败
if result.reason == "duplicate_idempotency_key":
response.status_code = 202
return {"accepted": True, "entry_id": "", "reason": "duplicate_idempotency_key"}
response.status_code = 503
return {"accepted": False, "reason": result.reason or "enqueue_failed"}
response.status_code = 202
logger.info(
"quote_template.parse.enqueued versionId=%s cells=%s entry=%s",
version_id,
len(inventory),
result.entry_id,
)
return {"accepted": True, "entry_id": result.entry_id}