- Added new endpoints to the analyst API for managing assets, including `GET /assets` to list assets and `POST /assets/{kind}/{asset_id}/publish` to publish assets.
- Introduced `DictAmbiguityCheckRequest` schema for checking metric ambiguities, enhancing the analyst's ability to clarify definitions and aliases.
- Implemented `detect_dict_ambiguity` function to analyze potential ambiguities in metrics, providing structured feedback for users.
- Updated `AnalystAgent` to support the new asset management functionalities and ambiguity detection logic, improving overall user experience.
- Enhanced existing schemas and services to accommodate new features, ensuring robust data handling and validation.
This update significantly improves the analyst API's capabilities, allowing for better asset management and clarity in metric definitions.
136 lines
4.3 KiB
Python
136 lines
4.3 KiB
Python
"""口径字典歧义检测(D-11 · 沉淀前 / 问数 clarify 共用结构)。"""
|
||
from __future__ import annotations
|
||
|
||
import json
|
||
from typing import Any
|
||
|
||
from app.service.dict_service import Ambiguity, Metric, MetricRegistry, merge_registries, registry_with_published
|
||
from app.service.llm import DeepSeekLLM
|
||
|
||
DICT_CHECK_SYSTEM = (
|
||
"你是金融数据口径审核助手。根据已有口径与新指标草稿,判断用户说法是否会混淆。"
|
||
"只输出 JSON:{\"summary\":\"一句话建议\",\"actions\":[\"建议1\",\"建议2\"]}"
|
||
"不要 markdown。"
|
||
)
|
||
|
||
|
||
def _parse_aliases(raw: Any) -> list[str]:
|
||
if raw is None:
|
||
return []
|
||
if isinstance(raw, list):
|
||
return [str(x).strip() for x in raw if str(x).strip()]
|
||
s = str(raw).strip()
|
||
if not s:
|
||
return []
|
||
if s.startswith("["):
|
||
try:
|
||
arr = json.loads(s)
|
||
if isinstance(arr, list):
|
||
return [str(x).strip() for x in arr if str(x).strip()]
|
||
except json.JSONDecodeError:
|
||
pass
|
||
return [p.strip() for p in s.replace(",", ",").split(",") if p.strip()]
|
||
|
||
|
||
def _issue_from_ambiguity(term: str, amb: Ambiguity) -> dict[str, Any]:
|
||
return {
|
||
"term": term,
|
||
"options": [
|
||
{
|
||
"metric_key": c.key,
|
||
"metric_name": c.name,
|
||
"definition": c.definition,
|
||
}
|
||
for c in amb.candidates
|
||
],
|
||
}
|
||
|
||
|
||
def detect_dict_ambiguity(
|
||
*,
|
||
metric_key: str,
|
||
metric_name: str,
|
||
definition: str,
|
||
aliases: Any,
|
||
repo: Any | None = None,
|
||
base_registry: MetricRegistry | None = None,
|
||
llm: DeepSeekLLM | None = None,
|
||
) -> dict[str, Any]:
|
||
"""合并草稿进 registry 后扫描别名/名称是否触发消歧。"""
|
||
if base_registry is not None:
|
||
reg = base_registry
|
||
else:
|
||
from app.service.analytics_repo import AnalyticsRepo
|
||
|
||
reg = registry_with_published(repo or AnalyticsRepo())
|
||
|
||
alias_list = _parse_aliases(aliases)
|
||
draft = Metric(
|
||
key=(metric_key or "").strip(),
|
||
name=(metric_name or "").strip(),
|
||
aliases=alias_list,
|
||
definition=(definition or "").strip(),
|
||
)
|
||
merged = merge_registries(reg, [draft]) if draft.key and draft.name else reg
|
||
|
||
terms: list[str] = []
|
||
if draft.name:
|
||
terms.append(draft.name)
|
||
terms.extend(alias_list)
|
||
|
||
issues: list[dict[str, Any]] = []
|
||
seen: set[str] = set()
|
||
for term in terms:
|
||
if not term or term in seen:
|
||
continue
|
||
seen.add(term)
|
||
resolved = merged.resolve(term)
|
||
if isinstance(resolved, Ambiguity):
|
||
issues.append(_issue_from_ambiguity(term, resolved))
|
||
|
||
llm_summary = ""
|
||
llm_actions: list[str] = []
|
||
if issues and llm is not None:
|
||
try:
|
||
payload = {
|
||
"existing": [m.to_dict() for m in reg.all()],
|
||
"draft": draft.to_dict(),
|
||
"conflicts": issues,
|
||
}
|
||
text, _ = llm.complete(
|
||
[
|
||
{"role": "system", "content": DICT_CHECK_SYSTEM},
|
||
{
|
||
"role": "user",
|
||
"content": f"数据:{json.dumps(payload, ensure_ascii=False)[:6000]}\n请输出 JSON:",
|
||
},
|
||
],
|
||
temperature=0.1,
|
||
max_tokens=400,
|
||
)
|
||
data = json.loads(text.strip().strip("`").replace("json", "", 1).strip())
|
||
llm_summary = str(data.get("summary") or "")
|
||
raw_actions = data.get("actions") or []
|
||
if isinstance(raw_actions, list):
|
||
llm_actions = [str(a) for a in raw_actions[:5]]
|
||
except Exception:
|
||
llm_summary = "存在与现有口径重叠的说法,请收窄别名或改用更具体的指标名。"
|
||
|
||
return {
|
||
"needs_choice": bool(issues),
|
||
"issues": issues,
|
||
"llm_summary": llm_summary,
|
||
"llm_actions": llm_actions,
|
||
}
|
||
|
||
|
||
def clarify_payload_from_ambiguity(amb: Ambiguity, *, llm_hint: str | None = None) -> dict[str, Any]:
|
||
return {
|
||
"term": amb.term,
|
||
"options": [
|
||
{"metric_key": c.key, "metric_name": c.name, "definition": c.definition}
|
||
for c in amb.candidates
|
||
],
|
||
"llm_hint": llm_hint,
|
||
}
|