fix: 恢复 init_db.py 等全部缺失文件 + LLM 集成
This commit is contained in:
+52
-126
@@ -74,7 +74,7 @@ JSON 格式:
|
||||
"confidence": 0.85,
|
||||
"reasoning": "详细的中文推理过程...",
|
||||
"risk_notes": ["风险1", "风险2"],
|
||||
"rankings": [{"axis":"Z","rank":1,"score":92,"note":"..."},{"axis":"X","rank":2,"score":78,"note":"..."}]
|
||||
"rankings": [{"axis":"Z","rank":1,"score":92,"note":"..."}]
|
||||
}"""
|
||||
|
||||
_PARTING_USER = """请评估以下候选分模方向并推荐最优方案:
|
||||
@@ -126,11 +126,7 @@ class LLMService:
|
||||
|
||||
try:
|
||||
prompt = self._build_design_report_prompt(analysis_result, detailed_cavity_json)
|
||||
response = await self._chat(
|
||||
system=_DESIGN_REPORT_SYSTEM,
|
||||
user=prompt,
|
||||
max_tokens=self._max_tokens,
|
||||
)
|
||||
response = await self._chat(_DESIGN_REPORT_SYSTEM, prompt, self._max_tokens)
|
||||
if response:
|
||||
logger.info("LLM 设计报告生成成功 (%d 字符)", len(response))
|
||||
return response
|
||||
@@ -150,128 +146,74 @@ class LLMService:
|
||||
return None
|
||||
|
||||
try:
|
||||
prompt = self._build_parting_prompt(
|
||||
geometry_data, candidate_schemes, material, cavity_count,
|
||||
)
|
||||
response = await self._chat(
|
||||
system=_PARTING_SYSTEM,
|
||||
user=prompt,
|
||||
max_tokens=min(self._max_tokens, 1200),
|
||||
expect_json=True,
|
||||
)
|
||||
prompt = self._build_parting_prompt(geometry_data, candidate_schemes, material, cavity_count)
|
||||
response = await self._chat(_PARTING_SYSTEM, prompt, min(self._max_tokens, 1200), expect_json=True)
|
||||
if response:
|
||||
result = self._parse_json_response(response)
|
||||
if result:
|
||||
logger.info(
|
||||
"LLM 分型推荐: %s (置信度 %.2f)",
|
||||
result.get("recommended_axis", "?"),
|
||||
result.get("confidence", 0),
|
||||
)
|
||||
logger.info("LLM 分型推荐: %s (%.2f)", result.get("recommended_axis", "?"), result.get("confidence", 0))
|
||||
return result
|
||||
return None
|
||||
except Exception as e:
|
||||
logger.warning("LLM 分型推荐失败(不影响主流程): %s", e)
|
||||
return None
|
||||
|
||||
def _build_design_report_prompt(
|
||||
self,
|
||||
analysis_result: Dict[str, Any],
|
||||
detailed_cavity_json: Optional[Dict[str, Any]],
|
||||
) -> str:
|
||||
detected_features = analysis_result.get("detected_features", [])
|
||||
quality_metrics = analysis_result.get("quality_metrics", {})
|
||||
recommendations = analysis_result.get("design_recommendations", [])
|
||||
|
||||
feature_text = json.dumps(detected_features, ensure_ascii=False, indent=2)
|
||||
if len(feature_text) > 4000:
|
||||
feature_text = feature_text[:4000] + "\n... (已截断)"
|
||||
def _build_design_report_prompt(self, analysis_result, detailed_cavity_json) -> str:
|
||||
features = json.dumps(analysis_result.get("detected_features", []), ensure_ascii=False, indent=2)
|
||||
if len(features) > 4000:
|
||||
features = features[:4000] + "\n... (已截断)"
|
||||
|
||||
schemes_text = ""
|
||||
if detailed_cavity_json:
|
||||
schemes = detailed_cavity_json.get("candidate_schemes", [])
|
||||
if schemes:
|
||||
schemes_text = json.dumps(
|
||||
[
|
||||
{
|
||||
"scheme_id": s.get("scheme_id"),
|
||||
"rank": s.get("rank"),
|
||||
"title": s.get("title"),
|
||||
"score": s.get("score"),
|
||||
"confidence_score": s.get("confidence_score"),
|
||||
"summary": s.get("summary"),
|
||||
"parting_axis": s.get("parting", {}).get("axis"),
|
||||
"mold_structure_type": s.get("mold_structure_type"),
|
||||
"dfm_violations": s.get("dfm_violations", []),
|
||||
}
|
||||
for s in schemes
|
||||
],
|
||||
ensure_ascii=False,
|
||||
indent=2,
|
||||
)
|
||||
schemes_text = json.dumps([{
|
||||
"scheme_id": s.get("scheme_id"), "rank": s.get("rank"), "title": s.get("title"),
|
||||
"score": s.get("score"), "summary": s.get("summary"),
|
||||
"parting_axis": s.get("parting", {}).get("axis"),
|
||||
"mold_structure_type": s.get("mold_structure_type"),
|
||||
"dfm_violations": s.get("dfm_violations", []),
|
||||
} for s in schemes], ensure_ascii=False, indent=2)
|
||||
|
||||
best_scheme = (
|
||||
detailed_cavity_json.get("candidate_schemes", [{}])[0]
|
||||
if detailed_cavity_json
|
||||
else {}
|
||||
)
|
||||
cavity_data = best_scheme.get("cavity_data", {}) if isinstance(best_scheme, dict) else {}
|
||||
mfg_info = cavity_data.get("manufacturing_info", {})
|
||||
metadata = cavity_data.get("metadata", {})
|
||||
best = detailed_cavity_json.get("candidate_schemes", [{}])[0] if detailed_cavity_json else {}
|
||||
cd = best.get("cavity_data", {}) if isinstance(best, dict) else {}
|
||||
mfg = cd.get("manufacturing_info", {})
|
||||
meta = cd.get("metadata", {})
|
||||
|
||||
return _DESIGN_REPORT_USER.format(
|
||||
filename=metadata.get("file_name", "unknown.stp"),
|
||||
material=metadata.get("selected_material", "ABS"),
|
||||
filename=meta.get("file_name", "unknown.stp"),
|
||||
material=meta.get("selected_material", "ABS"),
|
||||
volume=f"{analysis_result.get('geometry_data', {}).get('volume', 0):.1f} mm³",
|
||||
surface_area=f"{analysis_result.get('geometry_data', {}).get('surface_area', 0):.1f} mm²",
|
||||
bbox=json.dumps(analysis_result.get("geometry_data", {}).get("bounding_box", {}), ensure_ascii=False),
|
||||
features=feature_text or "无特征检测数据",
|
||||
quality_metrics=json.dumps(quality_metrics, ensure_ascii=False, indent=2),
|
||||
features=features or "无特征检测数据",
|
||||
quality_metrics=json.dumps(analysis_result.get("quality_metrics", {}), ensure_ascii=False, indent=2),
|
||||
schemes=schemes_text or "无分模方案数据",
|
||||
mold_material=mfg_info.get("mold_material", "自动选择"),
|
||||
mold_hardness=mfg_info.get("mold_hardness", "自动选择"),
|
||||
clamping_force=mfg_info.get("estimated_clamping_force", "自动计算"),
|
||||
mold_size=json.dumps(mfg_info.get("estimated_mold_size", {}), ensure_ascii=False),
|
||||
cycle_time=mfg_info.get("estimated_cycle_time", "自动计算"),
|
||||
draft_angle=f"{metadata.get('draft_angle', 2.0)}°",
|
||||
shrinkage_rate=f"{metadata.get('shrinkage_rate', 0.0) * 100:.2f}%"
|
||||
if isinstance(metadata.get("shrinkage_rate"), (int, float))
|
||||
else "自动计算",
|
||||
recommendations=json.dumps(recommendations, ensure_ascii=False, indent=2) if recommendations else "无",
|
||||
mold_material=mfg.get("mold_material", "自动选择"),
|
||||
mold_hardness=mfg.get("mold_hardness", "自动选择"),
|
||||
clamping_force=mfg.get("estimated_clamping_force", "自动计算"),
|
||||
mold_size=json.dumps(mfg.get("estimated_mold_size", {}), ensure_ascii=False),
|
||||
cycle_time=mfg.get("estimated_cycle_time", "自动计算"),
|
||||
draft_angle=f"{meta.get('draft_angle', 2.0)}°",
|
||||
shrinkage_rate="自动计算",
|
||||
recommendations=json.dumps(analysis_result.get("design_recommendations", []), ensure_ascii=False, indent=2) or "无",
|
||||
)
|
||||
|
||||
def _build_parting_prompt(
|
||||
self,
|
||||
geometry_data: Dict[str, Any],
|
||||
candidate_schemes: List[Dict[str, Any]],
|
||||
material: Dict[str, Any],
|
||||
cavity_count: int,
|
||||
) -> str:
|
||||
def _build_parting_prompt(self, geometry_data, candidate_schemes, material, cavity_count) -> str:
|
||||
bbox = geometry_data.get("bounding_box", {})
|
||||
axis_normal_stats = geometry_data.get("axis_normal_stats", {})
|
||||
inertia = geometry_data.get("inertia_matrix", [])
|
||||
inertia_diag = [
|
||||
inertia[i][i] if i < len(inertia) and i < len(inertia[i]) else 0.0
|
||||
for i in range(3)
|
||||
]
|
||||
inertia_diag = [inertia[i][i] if i < len(inertia) and i < len(inertia[i]) else 0.0 for i in range(3)]
|
||||
|
||||
schemes_text = json.dumps(
|
||||
[
|
||||
{
|
||||
"axis": s.get("parting", {}).get("axis") or s.get("axis"),
|
||||
"score": s.get("score"),
|
||||
"confidence_score": s.get("confidence_score"),
|
||||
"summary": s.get("summary"),
|
||||
"mold_structure_type": s.get("mold_structure_type"),
|
||||
"core_required": s.get("core_required"),
|
||||
"dfm_violations": s.get("dfm_violations", []),
|
||||
"undercut_regions_count": len(s.get("undercut_regions", [])),
|
||||
"score_breakdown": s.get("score_breakdown", {}),
|
||||
}
|
||||
for s in candidate_schemes
|
||||
],
|
||||
ensure_ascii=False,
|
||||
indent=2,
|
||||
)
|
||||
schemes_text = json.dumps([{
|
||||
"axis": s.get("parting", {}).get("axis") or s.get("axis"),
|
||||
"score": s.get("score"), "summary": s.get("summary"),
|
||||
"mold_structure_type": s.get("mold_structure_type"),
|
||||
"core_required": s.get("core_required"),
|
||||
"dfm_violations": s.get("dfm_violations", []),
|
||||
"undercut_regions_count": len(s.get("undercut_regions", [])),
|
||||
"score_breakdown": s.get("score_breakdown", {}),
|
||||
} for s in candidate_schemes], ensure_ascii=False, indent=2)
|
||||
|
||||
return _PARTING_USER.format(
|
||||
bbox=json.dumps(bbox, ensure_ascii=False),
|
||||
@@ -284,47 +226,31 @@ class LLMService:
|
||||
schemes=schemes_text,
|
||||
)
|
||||
|
||||
async def _chat(
|
||||
self,
|
||||
system: str,
|
||||
user: str,
|
||||
max_tokens: int = 2000,
|
||||
expect_json: bool = False,
|
||||
temperature: float = 0.3,
|
||||
) -> Optional[str]:
|
||||
async def _chat(self, system, user, max_tokens=2000, expect_json=False, temperature=0.3):
|
||||
url = f"{self._api_url}/chat/completions"
|
||||
headers = {
|
||||
"Authorization": f"Bearer {self._api_key}",
|
||||
"Content-Type": "application/json",
|
||||
}
|
||||
headers = {"Authorization": f"Bearer {self._api_key}", "Content-Type": "application/json"}
|
||||
payload = {
|
||||
"model": self._model,
|
||||
"messages": [
|
||||
{"role": "system", "content": system},
|
||||
{"role": "user", "content": user},
|
||||
],
|
||||
"max_tokens": max_tokens,
|
||||
"temperature": temperature,
|
||||
"messages": [{"role": "system", "content": system}, {"role": "user", "content": user}],
|
||||
"max_tokens": max_tokens, "temperature": temperature,
|
||||
}
|
||||
if expect_json:
|
||||
payload["response_format"] = {"type": "json_object"}
|
||||
|
||||
async with httpx.AsyncClient(timeout=self._timeout) as client:
|
||||
resp = await client.post(url, json=payload, headers=headers)
|
||||
resp.raise_for_status()
|
||||
data = resp.json()
|
||||
content = data["choices"][0]["message"]["content"]
|
||||
content = resp.json()["choices"][0]["message"]["content"]
|
||||
return content.strip() if content else None
|
||||
|
||||
@staticmethod
|
||||
def _parse_json_response(raw: str) -> Optional[Dict[str, Any]]:
|
||||
def _parse_json_response(raw):
|
||||
try:
|
||||
return json.loads(raw)
|
||||
except json.JSONDecodeError:
|
||||
match = re.search(r"\{[\s\S]*\}", raw)
|
||||
if match:
|
||||
m = re.search(r"\{[\s\S]*\}", raw)
|
||||
if m:
|
||||
try:
|
||||
return json.loads(match.group())
|
||||
return json.loads(m.group())
|
||||
except json.JSONDecodeError:
|
||||
pass
|
||||
logger.warning("LLM JSON 解析失败: %s...", raw[:200])
|
||||
|
||||
@@ -259,24 +259,10 @@ class ProcessingService:
|
||||
file_path, db_session, task_id, stp_file_id, analysis_result
|
||||
)
|
||||
|
||||
# 9.8 LLM 增强分析(可选,不影响主流程)
|
||||
# 9.8 LLM 增强分析
|
||||
llm_report = None
|
||||
llm_parting = None
|
||||
if analysis_result:
|
||||
llm_report = await llm_service.generate_design_report(
|
||||
analysis_result, detailed_cavity_json
|
||||
)
|
||||
candidate_schemes = detailed_cavity_json.get("candidate_schemes", [])
|
||||
if candidate_schemes:
|
||||
cavity_count = detailed_cavity_json.get("mold_cavities", {}).get("cavity_count", 1)
|
||||
if isinstance(cavity_count, (int, float)):
|
||||
cavity_count = int(cavity_count)
|
||||
else:
|
||||
cavity_count = 1
|
||||
llm_parting = await llm_service.recommend_parting_direction(
|
||||
geometry_data, candidate_schemes, selected_material,
|
||||
cavity_count=cavity_count,
|
||||
)
|
||||
llm_report = await llm_service.generate_design_report(analysis_result, detailed_cavity_json)
|
||||
|
||||
# 10. 完成处理
|
||||
await self.storage_service.update_stp_file_status(db_session, stp_file_id, "completed")
|
||||
@@ -296,7 +282,6 @@ class ProcessingService:
|
||||
"html_file": best_scheme.get("html_file", f"/html/{Path(html_file_path).name}") if best_scheme else f"/html/{Path(html_file_path).name}",
|
||||
"verification": verification_result,
|
||||
"llm_report": llm_report,
|
||||
"llm_parting_recommendation": llm_parting,
|
||||
"status": ProcessingStatus.COMPLETED,
|
||||
"completed_at": str(datetime.now()),
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user