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 {}