Files
inquiry_robot/inquiry-agent/agent/schema/contracts_loader.py
T
jillion886andCursor 99da1faf30 落地私聊空运询价与TMS报价回填,按后台模板出单。
查到价后把币种、单位、数量、单价、备注写入报价单,避免销售只看到光秃秃的金额。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-14 20:25:35 +08:00

99 lines
2.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.
"""
字段 / 字典合同加载。
本文件职责:从仓库 contracts/ 读 JSON;优先正式文件名,其次 PLACEHOLDER。
没读到真合同前,业务校验代码不得假装已激活字段规则(prompt/05)。
"""
from __future__ import annotations
import json
import logging
from functools import lru_cache
from pathlib import Path
from typing import Any, Optional
logger = logging.getLogger(__name__)
def contracts_dir() -> Path:
"""
定位仓库 contracts 目录。
查找顺序:inquiry-agent 上一级 / 再上一级(兼容拷贝到 C:\\ytd)。
"""
here = Path(__file__).resolve()
for parent in here.parents:
candidate = parent / "contracts"
# 空目录不算:发布包曾留下空 contracts,会挡住仓库根的真合同
if candidate.is_dir() and any(candidate.glob("*.json")):
return candidate
# 回退:与 agent 包同级仓库根的常见布局
return here.parents[3] / "contracts"
def _load_json_file(path: Path) -> dict[str, Any]:
return json.loads(path.read_text(encoding="utf-8"))
def resolve_contract_path(stem: str) -> Optional[Path]:
"""
解析合同文件路径。
stem 例:inquiry-required-fields-v1 / tms-dictionary-projection-v2
优先 `{stem}.json`,否则 `{stem}.PLACEHOLDER.json`。
"""
root = contracts_dir()
formal = root / f"{stem}.json"
if formal.is_file():
return formal
placeholder = root / f"{stem}.PLACEHOLDER.json"
if placeholder.is_file():
return placeholder
return None
@lru_cache(maxsize=8)
def load_contract(stem: str) -> dict[str, Any]:
"""
加载合同 JSON。
返回:dict;文件缺失抛 FileNotFoundError。
is_placeholder:由 meta 或文件名推断,供调用方拒绝「用占位做生产校验」。
"""
path = resolve_contract_path(stem)
if path is None:
raise FileNotFoundError(f"合同文件不存在: {stem}(目录 {contracts_dir()})")
data = _load_json_file(path)
is_ph = "PLACEHOLDER" in path.name or str(data.get("schemaVersion", "")).endswith(
"placeholder"
)
data = dict(data)
data["_meta"] = {
"path": str(path),
"is_placeholder": is_ph,
"stem": stem,
}
logger.info(
"已加载合同 stem=%s placeholder=%s path=%s",
stem,
is_ph,
path.name,
)
return data
def require_formal_contract(stem: str) -> dict[str, Any]:
"""
加载且必须为正式文件(非 PLACEHOLDER)。
骨架联调请用 load_contract;写字段校验业务前必须走本函数。
"""
data = load_contract(stem)
if data.get("_meta", {}).get("is_placeholder"):
raise RuntimeError(
f"合同仍为占位,禁止做业务校验: {stem}。"
"请将正式 JSON 放到 contracts/(见 _ref/prod-pull 对照)"
)
return data