""" 工作流公共工具函数 提取自 workflow_fault_diagnosis.py 和 workflow_operate.py 的共享代码 """ from typing import TypedDict, Optional, Dict, Any, List from prompts import COMMON_PROMPTS from modelsAPI.model_api import OpenaiAPI from utils.function_tracker import get_all_callbacks, get_current_stream_handler from utils.image_utils import extract_image_paths, normalize_markdown_images import json import re import random def safe_json_extract(text: str) -> Optional[Any]: if not text: return None cleaned = text.strip() cleaned = re.sub(r"```json\s*", "", cleaned, flags=re.IGNORECASE) cleaned = re.sub(r"```\s*", "", cleaned) m = re.search(r"\[[\s\S]*\]", cleaned) if m: try: return json.loads(m.group(0)) except Exception: pass m = re.search(r"\{[\s\S]*\}", cleaned) if m: try: return json.loads(m.group(0)) except Exception: pass try: return json.loads(cleaned) except Exception: return None from workflows.history_manager import parse_history, build_history_str, filter_image_urls, init_history # noqa: F401 def normalize_latex_spacing(text: str) -> str: placeholder = "\x00DOUBLE_DOLLAR\x00" text = text.replace("$$", placeholder) text = re.sub(r'(? str: if not text or len(text) < 10: return text lines = text.split('\n') result = [] seen_segments = set() for line in lines: line = line.rstrip() if not line: result.append(line) continue normalized_line = line.strip().lower() if normalized_line in seen_segments: continue seen_segments.add(normalized_line) result.append(line) cleaned_text = '\n'.join(result) return cleaned_text def convert_rag_result(rag_result): if not rag_result or not rag_result.get("success", False): return [] source_citation = rag_result.get("sourceCitation", {}) flat_results = [] for resource, items in source_citation.items(): for item in items: flat_results.append(item) return flat_results def calculate_info_completeness(weights: Dict[str, float], values: Dict[str, str]) -> float: score = 0.0 for field, weight in weights.items(): value = values.get(field, "") or "" if value and str(value).strip(): score += weight return round(score, 2) def emit_callback_event(title_options: List[str], details: str): start_event = {"type": "function_execution", "title": random.choice(title_options), "details": details} for callback in get_all_callbacks(): try: callback(start_event) except Exception: pass def build_detail_info(fields: Dict[str, str]) -> str: detail_info = "" for label, value in fields.items(): if value: detail_info += f"- {label}:{value}\n" return detail_info def append_atlas_section(answer: str, atlas_results: Dict[str, Any]) -> str: if not atlas_results: return answer atlas_section = "\n\n---\n\n**参考图册:**\n" for node_name, node_data in atlas_results.items(): if isinstance(node_data, dict): for title, urls in node_data.items(): if isinstance(urls, list) and urls: for url in urls: atlas_section += f"- {title}: [ `{title}` ]({url})\n" return answer + atlas_section async def stream_format_and_postprocess( raw_content: str, original_image_paths: List[str], temperature: float = 0.3, content_label: str = None, ) -> str: if content_label: format_user_content = COMMON_PROMPTS["format_user_simple"].format( content_label=content_label, raw_content=raw_content, ) else: format_user_content = COMMON_PROMPTS["format_user_with_image_examples"].format(raw_content=raw_content) format_system_prompt = COMMON_PROMPTS["format_system"] stream_handler = get_current_stream_handler() answer = "" accumulated_content = "" async for chunk in OpenaiAPI.open_api_chat_stream( model=None, system_prompt=format_system_prompt, messages=[{"role": "user", "content": format_user_content}], temperature=temperature, ): answer += chunk accumulated_content += chunk if stream_handler: stream_handler.send_stream_content(accumulated_content) answer = answer.strip() if answer else raw_content answer = remove_duplicate_content(answer) answer = normalize_markdown_images(answer, original_paths=original_image_paths) answer = normalize_latex_spacing(answer) return answer