87461d1dac
POST /api/v1/ecom/intent/recognize 和 /api/v1/ecom/step 透传到 pricebot-backend 的 /api/ecom/intent/recognize 和 /api/ecom/step。前端电商 mode 已经开放, 无这两条路由会 404;现在和外卖那两条对称。模块 docstring 改成「外卖+电商两套」。
102 lines
3.7 KiB
Python
102 lines
3.7 KiB
Python
"""比价业务透传端点(外卖 + 电商两套)。
|
|
|
|
把客户端的 POST /api/v1/{intent/recognize, price/step, ecom/intent/recognize, ecom/step}
|
|
请求原样转发给 pricebot-backend 的对应路径。MVP 阶段**不鉴权**(同 coupon/step,
|
|
见 docs/待办与技术债.md P1:device_id 透传区分设备,待补 JWT + device_id↔user_id
|
|
绑定后才能做用户级画像)。
|
|
|
|
外卖(food):
|
|
- Phase 1 /intent/recognize:从源平台(淘宝闪购/美团/京东外卖)购物车页识别店名+菜品+
|
|
价格,一次性。
|
|
- Phase 2 /price/step:多轮循环,在目标平台复现订单读到手价,直到 done。
|
|
|
|
电商(ecom):
|
|
- Phase 1 /ecom/intent/recognize:从源平台(淘宝/抖音/拼多多)SKU+详情页识别商品,一次性。
|
|
- Phase 2 /ecom/step:多轮循环,在目标平台搜索商品并采价,直到 done。
|
|
|
|
真正的目标驱动比价逻辑在 pricebot-backend(GoalEngine,另一个 repo),本接口只是透传壳。
|
|
|
|
pricebot 协议文档:
|
|
pricebot-backend/docs/main/02_api_protocol.md
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import logging
|
|
from typing import Any
|
|
|
|
import httpx
|
|
from fastapi import APIRouter, HTTPException, Request, status
|
|
|
|
from app.core.config import settings
|
|
|
|
logger = logging.getLogger("shagua.compare")
|
|
|
|
router = APIRouter(prefix="/api/v1", tags=["compare"])
|
|
|
|
|
|
async def _passthrough(request: Request, upstream_path: str) -> dict[str, Any]:
|
|
"""把请求体原样转发给 pricebot-backend 的 {upstream_path}。
|
|
|
|
跟 coupon_step 同款透传壳:不鉴权、不做 schema 校验,仅读 device_id/trace_id/step
|
|
打日志。比价单帧是大上下文 / 逐帧 LLM,超时用 PRICEBOT_COMPARE_TIMEOUT_SEC(60s,
|
|
比领券的 30s 长)。
|
|
"""
|
|
try:
|
|
body = await request.json()
|
|
except Exception as e:
|
|
raise HTTPException(status_code=400, detail=f"invalid json body: {e}") from e
|
|
|
|
url = f"{settings.PRICEBOT_BASE_URL.rstrip('/')}{upstream_path}"
|
|
timeout = settings.PRICEBOT_COMPARE_TIMEOUT_SEC
|
|
|
|
logger.info(
|
|
"compare %s device_id=%s trace_id=%s step=%s",
|
|
upstream_path,
|
|
body.get("device_id"),
|
|
body.get("trace_id"),
|
|
body.get("step"),
|
|
)
|
|
|
|
try:
|
|
async with httpx.AsyncClient(timeout=timeout) as client:
|
|
resp = await client.post(url, json=body)
|
|
except httpx.RequestError as e:
|
|
logger.error("[pricebot] request failed: %s", e)
|
|
raise HTTPException(
|
|
status_code=status.HTTP_502_BAD_GATEWAY,
|
|
detail=f"pricebot upstream unreachable: {e}",
|
|
) from e
|
|
|
|
if resp.status_code >= 500:
|
|
logger.error(
|
|
"[pricebot] 5xx status=%d body=%s",
|
|
resp.status_code,
|
|
resp.text[:500],
|
|
)
|
|
raise HTTPException(
|
|
status_code=status.HTTP_502_BAD_GATEWAY,
|
|
detail=f"pricebot upstream returned {resp.status_code}",
|
|
)
|
|
|
|
return resp.json()
|
|
|
|
|
|
@router.post("/intent/recognize", summary="外卖比价 Phase 1 意图识别 (透传到 pricebot)")
|
|
async def intent_recognize(request: Request) -> dict[str, Any]:
|
|
return await _passthrough(request, "/api/intent/recognize")
|
|
|
|
|
|
@router.post("/price/step", summary="外卖比价 Phase 2 步进 (透传到 pricebot)")
|
|
async def price_step(request: Request) -> dict[str, Any]:
|
|
return await _passthrough(request, "/api/price/step")
|
|
|
|
|
|
@router.post("/ecom/intent/recognize", summary="电商比价 Phase 1 意图识别 (透传到 pricebot)")
|
|
async def ecom_intent_recognize(request: Request) -> dict[str, Any]:
|
|
return await _passthrough(request, "/api/ecom/intent/recognize")
|
|
|
|
|
|
@router.post("/ecom/step", summary="电商比价 Phase 2 步进 (透传到 pricebot)")
|
|
async def ecom_step(request: Request) -> dict[str, Any]:
|
|
return await _passthrough(request, "/api/ecom/step")
|