136 lines
4.5 KiB
Python
136 lines
4.5 KiB
Python
from __future__ import annotations
|
||
|
||
from .shared import *
|
||
|
||
from .charter_seed_utils import _localize_seed_to_zh, _needs_seed_localize_zh, _parse_json_object_text, _to_bullet_text
|
||
|
||
def _has_non_empty_module_data(content: dict[str, Any]) -> bool:
|
||
modules = content.get("modules")
|
||
if not isinstance(modules, dict):
|
||
return False
|
||
for module in modules.values():
|
||
if not isinstance(module, dict):
|
||
continue
|
||
for value in module.values():
|
||
if str(value or "").strip():
|
||
return True
|
||
return False
|
||
|
||
def _has_complete_module_data(content: dict[str, Any]) -> bool:
|
||
modules = content.get("modules")
|
||
if not isinstance(modules, dict):
|
||
return False
|
||
for module_key, required_fields in _CHARTER_REQUIRED_FIELDS.items():
|
||
module_payload = modules.get(module_key)
|
||
if not isinstance(module_payload, dict):
|
||
return False
|
||
for field_key in required_fields:
|
||
if not str(module_payload.get(field_key) or "").strip():
|
||
return False
|
||
return True
|
||
|
||
def _merge_prefill_content(
|
||
existing: dict[str, Any], incoming: dict[str, Any], *, overwrite_existing: bool
|
||
) -> dict[str, Any]:
|
||
merged: dict[str, Any] = dict(existing)
|
||
for key, value in incoming.items():
|
||
if overwrite_existing or key not in merged:
|
||
merged[key] = value
|
||
continue
|
||
current = merged.get(key)
|
||
if isinstance(current, dict) and isinstance(value, dict):
|
||
merged[key] = _merge_prefill_content(
|
||
current, value, overwrite_existing=overwrite_existing
|
||
)
|
||
continue
|
||
if isinstance(current, list):
|
||
if not current and value:
|
||
merged[key] = value
|
||
continue
|
||
if current is None or not str(current).strip():
|
||
merged[key] = value
|
||
return merged
|
||
|
||
def _default_ai_validations(module_name: str) -> str:
|
||
return _to_bullet_text(
|
||
[
|
||
f"校验「{module_name}」与信号源证据链一致",
|
||
"校验字段可验证、可执行、可追踪",
|
||
"发现冲突时自动标注并等待人工否决/确认",
|
||
]
|
||
)
|
||
|
||
def _default_escalation_trigger() -> str:
|
||
return _to_bullet_text(
|
||
[
|
||
"置信度低于 0.75",
|
||
"关键字段缺失或来源证据冲突",
|
||
"超过模型能力边界或预算/时限约束",
|
||
]
|
||
)
|
||
|
||
async def _generate_charter_seed(
|
||
*,
|
||
db: AsyncSession,
|
||
tenant_id: int,
|
||
project_id: int,
|
||
signal_context: str,
|
||
) -> dict[str, Any]:
|
||
if not signal_context.strip():
|
||
return {}
|
||
|
||
prompt = (
|
||
"你是 AI 项目经理,请基于输入信号生成可落地的宪章草稿。"
|
||
"所有自然语言字段必须使用简体中文,禁止英文句子;模型名称或技术名词可保留英文。"
|
||
"只输出 JSON,不要 markdown。\n"
|
||
"字段:\n"
|
||
"{\n"
|
||
' "summary": "一句话问题摘要",\n'
|
||
' "background": "背景与动机",\n'
|
||
' "objectives": ["目标1","目标2"],\n'
|
||
' "scope": "范围说明",\n'
|
||
' "in_scope": ["范围内事项1","范围内事项2"],\n'
|
||
' "out_of_scope": ["范围外事项1","范围外事项2"],\n'
|
||
' "success_metrics": ["指标1","指标2"],\n'
|
||
' "risks": ["风险1","风险2"],\n'
|
||
' "dependencies": ["依赖1","依赖2"],\n'
|
||
' "primary_model": "模型名",\n'
|
||
' "fallback_model": "备用模型名"\n'
|
||
"}\n\n"
|
||
f"信号输入:\n{signal_context[:6000]}"
|
||
)
|
||
try:
|
||
raw = await llm_service.generate(
|
||
prompt,
|
||
function_name="sstc_charter_prefill",
|
||
db=db,
|
||
tenant_id=tenant_id,
|
||
project_id=project_id,
|
||
)
|
||
parsed = _parse_json_object_text(raw)
|
||
if parsed:
|
||
if _needs_seed_localize_zh(parsed):
|
||
return await _localize_seed_to_zh(
|
||
db=db, tenant_id=tenant_id, project_id=project_id, seed=parsed
|
||
)
|
||
return parsed
|
||
except Exception:
|
||
pass
|
||
|
||
try:
|
||
ai_seed = await AIService().generate_charter_from_signal(
|
||
signal_context,
|
||
tenant_id=tenant_id,
|
||
db=db,
|
||
)
|
||
if isinstance(ai_seed, dict):
|
||
if _needs_seed_localize_zh(ai_seed):
|
||
return await _localize_seed_to_zh(
|
||
db=db, tenant_id=tenant_id, project_id=project_id, seed=ai_seed
|
||
)
|
||
return ai_seed
|
||
return {}
|
||
except Exception:
|
||
return {}
|
||
|