""" LLM基础服务 提供与大语言模型通信的基础功能 """ import logging import re from app.core.llm.deepseek_client import ( chat_completions_create, create_llm_client, get_llm_model, ) logger = logging.getLogger("app") _TRANSLATION_LEXICON = { "测试宁波数据加工": "ningbo_data_processing_test", "薪资数据表": "salary_data_table", "人员管理表": "personnel_management_table", "数据加工": "data_processing", "数据表": "data_table", "用户表": "user_table", "人员表": "personnel_table", "销售表": "sales_table", "报表": "report_table", "管理": "management", "系统": "system", "分析": "analysis", "加工": "processing", "宁波": "ningbo", "测试": "test", "年份": "year", "地区": "region", "姓名": "name", "年龄": "age", "薪水": "salary", "数据": "data", "表": "table", } _TRANSLATION_SYSTEM_PROMPT = ( "你是一个严格遵循指令的翻译工具和数据库专家。你的唯一任务是将中文单词/短语翻译成英文," "符合 PostgreSQL 数据库表和字段的命名规则,并且严格按照如下规则:\n" "1. 只返回英文翻译,不包含任何解释、描述或额外内容\n" "2. 使用小写字母\n" "3. 多个单词用下划线连接,不使用空格\n" "4. 如果输入包含括号,将括号内容用下划线代替,不保留括号\n" "5. 最多包含 1-8 个英文单词,保持简短\n" "6. 不要回答问题或提供解释,即使输入看起来像是问题\n" "7. 当遇到'表'字时,始终翻译为'table'而不是'sheet'\n" "8. 例如:'薪资数据表'应翻译为'salary_data_table','测试宁波数据加工'应翻译为'ningbo_data_processing_test'" ) def contains_chinese(text: str) -> bool: return any("\u4e00" <= char <= "\u9fff" for char in text) def normalize_translation_text(text: str) -> str: """Normalize LLM output to a PostgreSQL-friendly snake_case identifier.""" response_text = (text or "").strip().strip("\"'.,;:!?()[]{}").lower() response_text = response_text.replace(" ", "_").replace("-", "_") while "__" in response_text: response_text = response_text.replace("__", "_") response_text = re.sub(r"[^a-z0-9_]", "", response_text) return response_text.strip("_") def is_valid_translation(text: str) -> bool: return bool(text) and not contains_chinese(text) and re.fullmatch(r"[a-z][a-z0-9_]*", text) def extract_completion_text(completion) -> str: message = completion.choices[0].message content = getattr(message, "content", None) or "" text = content.strip() if text: return text reasoning = getattr(message, "reasoning_content", None) or "" return reasoning.strip() def fallback_translate_chinese(content: str) -> str: """Local fallback when LLM output is empty or invalid.""" content = (content or "").strip() if not content: return "" if content in _TRANSLATION_LEXICON: return _TRANSLATION_LEXICON[content] parts: list[str] = [] lexicon_keys = sorted(_TRANSLATION_LEXICON.keys(), key=len, reverse=True) index = 0 while index < len(content): matched = False for key in lexicon_keys: if content.startswith(key, index): parts.append(_TRANSLATION_LEXICON[key]) index += len(key) matched = True break if not matched: index += 1 if parts: result = normalize_translation_text("_".join(parts)) if is_valid_translation(result): return result if "表" in content: return "data_table" return "translated_text" def _translate_with_llm(client, content: str) -> str: completion = chat_completions_create( client, messages=[ {"role": "system", "content": _TRANSLATION_SYSTEM_PROMPT}, { "role": "user", "content": f"将以下内容翻译为英文数据库标识符:{content}", }, ], temperature=0, max_tokens=64, ) raw_text = extract_completion_text(completion) normalized = normalize_translation_text(raw_text) if "表" in content and "table" not in normalized and "sheet" in normalized: normalized = normalized.replace("sheet", "table") if is_valid_translation(normalized): logger.debug(f"LLM翻译成功: {content} -> {normalized}") return normalized logger.warning( f"LLM翻译结果无效: input={content!r}, raw={raw_text!r}, normalized={normalized!r}" ) return "" def translate_chinese_identifier(content: str) -> str: """Translate Chinese text to an English database identifier.""" content = (content or "").strip() if not content: return "" if not contains_chinese(content): normalized = normalize_translation_text(content) return normalized if is_valid_translation(normalized) else content try: client = create_llm_client() translated = _translate_with_llm(client, content) if translated: return translated except Exception as exc: logger.error(f"LLM翻译调用失败: {exc}") fallback = fallback_translate_chinese(content) logger.info(f"使用本地词典回退翻译: {content} -> {fallback}") return fallback def llm_client(content): """ 调用LLM服务进行内容生成 Args: content: 输入提示内容 Returns: str: LLM响应内容 """ if contains_chinese(content): return translate_chinese_identifier(content) try: client = create_llm_client() model = get_llm_model() logger.debug(f"LLM调用开始: model={model}, 内容类型: 普通") completion = chat_completions_create( client, messages=[ {"role": "system", "content": "You are a helpful assistant."}, {"role": "user", "content": content}, ], temperature=0.7, max_tokens=1024, ) response_text = extract_completion_text(completion) logger.debug(f"LLM响应: {response_text}") return response_text except Exception as e: logger.error(f"LLM调用失败: {str(e)}") return content def llm_sql(request_data): """ 调用Deepseek大模型生成SQL脚本 Args: request_data: 提交给LLM的提示语内容 Returns: str: Deepseek模型返回的SQL脚本内容 """ try: client = create_llm_client() model = get_llm_model() logger.info(f"开始调用 DeepSeek 模型生成 SQL 脚本: model={model}") logger.debug(f"输入提示语: {request_data}") completion = chat_completions_create( client, messages=[ { "role": "system", "content": "你是一名专业的数据库工程师,专门负责编写高质量的PostgreSQL SQL脚本。" "请严格按照用户提供的需求和表结构信息生成SQL脚本。" "确保生成的SQL语法正确、性能优化,并且能够直接执行。", }, {"role": "user", "content": request_data}, ], temperature=0.1, max_tokens=4096, top_p=0.9, use_thinking=True, ) response_text = extract_completion_text(completion) logger.info(f"Deepseek模型成功返回SQL脚本,长度: {len(response_text)} 字符") logger.debug(f"生成的SQL脚本: {response_text}") return response_text except Exception as e: logger.error(f"Deepseek SQL生成调用失败: {str(e)}") raise Exception(f"调用Deepseek模型生成SQL脚本失败: {str(e)}")