RAG/utils/code_intent.py

404 lines
14 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
代码意图识别模块
用于识别用户问题是否与代码相关,并进行详细的意图分类
支持多种代码问题类型的识别为后续检索和Prompt生成提供指导
"""
import sys
import os
# 添加项目根目录到 Python 模块搜索路径
current_dir = os.path.dirname(os.path.abspath(__file__))
project_root = os.path.dirname(current_dir)
if project_root not in sys.path:
sys.path.insert(0, project_root)
import json
import asyncio
from typing import Dict, Any, Optional, List
from enum import Enum
from functools import lru_cache
from loguru import logger
from config import settings
from llama_index.llms.ollama import Ollama
from utils.prompt import INTENT_DETECTION_TEMPLATE
class CodeIntentCategory(Enum):
"""代码意图分类枚举 - 细粒度分类体系"""
# 代码解释与逻辑类 (Existing Code Focus)
LOGIC_EXPLANATION = "logic_explanation" # 解释既有代码的底层逻辑
ENTITY_INTRODUCTION = "entity_introduction" # 介绍具体的代码实体函数定义、类属性、API参数
CODE_STRUCTURE = "code_structure" # 询问项目组织
# 代码生成与实现类 (New Code Focus)
CODE_GENERATION = "code_generation" # 请求从零编写完整代码或功能块
BOILERPLATE_IMPLEMENTATION = "boilerplate_implementation" # 请求提供标准算法/模板
# 调试、优化与理论类
ERROR_DEBUGGING = "error_debugging" # 排查 Bug 或异常
CODE_OPTIMIZATION = "code_optimization" # 改进既有代码的性能或质量
ALGORITHM_THEORY = "algorithm_theory" # 算法原理或复杂度分析
# 非代码类
GENERAL_TECHNICAL = "general_technical" # 通用技术咨询
NON_TECHNICAL = "non_technical" # 非技术问题
UNKNOWN = "unknown" # 未知类型
class PromptTemplateType(Enum):
"""Prompt模板类型枚举"""
CODE_EXPLANATION = "code_explanation" # 代码解释模板(逻辑解释、实体介绍、代码结构)
CODE_GENERATION = "code_generation" # 代码生成模板(代码生成、模板实现)
CODE_DEBUGGING = "code_debugging" # 代码调试模板(错误调试)
CODE_OPTIMIZATION = "code_optimization" # 代码优化模板(代码优化)
ALGORITHM_EXPLANATION = "algorithm_explanation" # 算法解释模板(算法理论)
GENERAL_QA = "general_qa" # 通用问答模板(通用技术咨询、非技术问题)
class CodeIntentResult:
"""代码意图识别结果"""
def __init__(
self,
is_code_related: bool,
category: CodeIntentCategory,
confidence: float,
prompt_template_type: PromptTemplateType,
keywords: List[str],
reasoning: str,
requires_code_context: bool,
suggested_search_terms: List[str],
):
self.is_code_related = is_code_related # 是否与代码相关True/False
self.category = category # 代码意图分类
self.confidence = confidence # 置信度分数范围0-1之间
self.prompt_template_type = prompt_template_type # Prompt模板类型
self.keywords = keywords # 相关关键词列表
self.reasoning = reasoning # 解释或理由
self.requires_code_context = requires_code_context # 是否需要代码上下文True/False
self.suggested_search_terms = suggested_search_terms # 建议搜索条款列表
def to_dict(self) -> Dict[str, Any]:
"""转换为字典格式"""
return {
"is_code_related": self.is_code_related,
"category": self.category.value,
"confidence": self.confidence,
"prompt_template_type": self.prompt_template_type.value,
"keywords": self.keywords,
"reasoning": self.reasoning,
"requires_code_context": self.requires_code_context,
"suggested_search_terms": self.suggested_search_terms,
}
def to_json(self) -> str:
"""转换为JSON格式"""
return json.dumps(self.to_dict(), ensure_ascii=False, indent=2)
class CodeIntentDetector:
"""代码意图检测器"""
def __init__(self, llm: Optional[Ollama] = None):
"""
初始化代码意图检测器
Args:
llm: LLM实例如果为None则使用默认配置
"""
if llm is None:
self.llm = Ollama(
model=settings.OLLAMA_MODEL,
base_url=settings.OLLAMA_BASE_URL,
temperature=0.1, # 低温度确保输出稳定
request_timeout=1200.0
)
else:
self.llm = llm
logger.info("代码意图检测器初始化完成")
def _build_classification_prompt(self, query: str, history: Optional[str] = None) -> str:
"""
构建分类Prompt
Args:
query: 用户问题
history: 对话历史
Returns:
str: 分类Prompt
"""
history_str = "" if not history else history
prompt = INTENT_DETECTION_TEMPLATE.format(
history_str=history_str,
query=query
)
return prompt
def _parse_llm_response(self, response_text: str) -> Optional[Dict[str, Any]]:
"""
解析LLM响应
Args:
response_text: LLM响应文本
Returns:
解析后的字典如果解析失败返回None
"""
try:
response_text = response_text.strip()
# 尝试提取JSON部分
json_start = response_text.find('{')
json_end = response_text.rfind('}')
if json_start == -1 or json_end == -1:
logger.warning(f"未找到JSON格式响应: {response_text}")
return None
json_str = response_text[json_start:json_end + 1]
result = json.loads(json_str)
# 验证必需字段并使用默认值
required_fields = {
'is_code_related': False,
'category': 'unknown',
'confidence': 0.5,
'keywords': [],
'reasoning': '',
'requires_code_context': False,
'suggested_search_terms': []
}
for field, default_value in required_fields.items():
if field not in result:
logger.warning(f"缺少必需字段: {field}, 使用默认值: {default_value}")
result[field] = default_value
return result
except json.JSONDecodeError as e:
logger.error(f"JSON解析失败: {e}, 响应: {response_text}")
return None
except Exception as e:
logger.error(f"解析响应失败: {e}")
return None
def _map_to_enums(
self,
parsed_result: Dict[str, Any]
) -> tuple[CodeIntentCategory, PromptTemplateType]:
"""
将解析结果映射到枚举类型
Args:
parsed_result: 解析后的结果字典
Returns:
(CodeIntentCategory, PromptTemplateType)
"""
category_str = parsed_result.get('category', 'unknown')
try:
category = CodeIntentCategory(category_str)
except ValueError:
logger.warning(f"未知的分类: {category_str}, 使用默认值")
category = CodeIntentCategory.UNKNOWN
# 根据分类确定Prompt模板类型
if not parsed_result.get('is_code_related', False):
prompt_template_type = PromptTemplateType.GENERAL_QA
# 代码解释与逻辑类 -> CODE_EXPLANATION
elif category in [CodeIntentCategory.LOGIC_EXPLANATION, CodeIntentCategory.ENTITY_INTRODUCTION,
CodeIntentCategory.CODE_STRUCTURE]:
prompt_template_type = PromptTemplateType.CODE_EXPLANATION
# 代码生成与实现类 -> CODE_GENERATION
elif category in [CodeIntentCategory.CODE_GENERATION, CodeIntentCategory.BOILERPLATE_IMPLEMENTATION]:
prompt_template_type = PromptTemplateType.CODE_GENERATION
# 调试、优化与理论类
elif category == CodeIntentCategory.ERROR_DEBUGGING:
prompt_template_type = PromptTemplateType.CODE_DEBUGGING
elif category == CodeIntentCategory.CODE_OPTIMIZATION:
prompt_template_type = PromptTemplateType.CODE_OPTIMIZATION
elif category == CodeIntentCategory.ALGORITHM_THEORY:
prompt_template_type = PromptTemplateType.ALGORITHM_EXPLANATION
# 非代码问题 -> GENERAL_QA
elif category in [CodeIntentCategory.GENERAL_TECHNICAL, CodeIntentCategory.NON_TECHNICAL,
CodeIntentCategory.UNKNOWN]:
prompt_template_type = PromptTemplateType.GENERAL_QA
else:
prompt_template_type = PromptTemplateType.GENERAL_QA
return category, prompt_template_type
async def detect_intent_async(self, query: str, history: Optional[str] = None) -> CodeIntentResult:
"""
异步检测代码意图
Args:
query: 用户问题
history: 对话历史
Returns:
CodeIntentResult: 意图识别结果
"""
try:
# 构建Prompt
prompt = self._build_classification_prompt(query, history)
# 调用LLM
response = await self.llm.acomplete(prompt)
response_text = response.text
logger.debug(f"LLM响应: {response_text}")
# 解析响应
parsed_result = self._parse_llm_response(response_text)
if parsed_result is None:
logger.error("解析LLM响应失败使用默认分类")
return self._get_default_result(query)
# 映射到枚举类型
category, prompt_template_type = self._map_to_enums(parsed_result)
# 构建结果对象
result = CodeIntentResult(
is_code_related=parsed_result.get('is_code_related', False),
category=category,
confidence=parsed_result.get('confidence', 0.5),
prompt_template_type=prompt_template_type,
keywords=parsed_result.get('keywords', []),
reasoning=parsed_result.get('reasoning', ''),
requires_code_context=parsed_result.get('requires_code_context', False),
suggested_search_terms=parsed_result.get('suggested_search_terms', [])
)
logger.info(f"代码意图检测完成: {result.to_json()}")
return result
except Exception as e:
logger.error(f"代码意图检测失败: {e}")
return self._get_default_result(query)
def detect_intent(self, query: str) -> CodeIntentResult:
"""
同步检测代码意图(包装异步方法)
Args:
query: 用户问题
Returns:
CodeIntentResult: 意图识别结果
"""
try:
loop = asyncio.get_event_loop()
except RuntimeError:
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
return loop.run_until_complete(self.detect_intent_async(query))
def _get_default_result(self, query: str) -> CodeIntentResult:
"""
获取默认结果(当检测失败时使用)
Args:
query: 用户问题
Returns:
CodeIntentResult: 默认结果
"""
logger.warning(f"使用默认意图识别结果: {query}")
return CodeIntentResult(
is_code_related=False,
category=CodeIntentCategory.UNKNOWN,
confidence=0.0,
prompt_template_type=PromptTemplateType.GENERAL_QA,
keywords=[],
reasoning="意图识别失败,使用默认分类",
requires_code_context=False,
suggested_search_terms=[query]
)
@lru_cache(maxsize=100)
def detect_code_intent_cached(query: str) -> CodeIntentResult:
"""
带缓存的代码意图检测(同步版本)
Args:
query: 用户问题
Returns:
CodeIntentResult: 意图识别结果
"""
detector = CodeIntentDetector()
return detector.detect_intent(query)
async def detect_code_intent_async_cached(query: str) -> CodeIntentResult:
"""
带缓存的代码意图检测(异步版本)
Args:
query: 用户问题
Returns:
CodeIntentResult: 意图识别结果
"""
detector = CodeIntentDetector()
return await detector.detect_intent_async(query)
def create_intent_detector(llm: Optional[Ollama] = None) -> CodeIntentDetector:
"""
创建代码意图检测器实例
Args:
llm: LLM实例如果为None则使用默认配置
Returns:
CodeIntentDetector: 检测器实例
"""
return CodeIntentDetector(llm)
if __name__ == "__main__":
import asyncio
async def test_intent_detection():
"""测试代码意图检测"""
detector = CodeIntentDetector()
test_queries = [
"kth_number怎么实现的",
"kth_number的时间复杂度是多少",
"kth_number的测试用例有哪些",
"kth_number的代码质量如何",
"trieste实现的采集函数有哪些",
"这个函数是做什么的?",
"如何优化这段代码?",
"Python中如何实现异步",
"今天天气怎么样?",
"为什么会报这个错误?",
"项目结构是怎样的?",
"如何调用这个API"
]
for query in test_queries:
print(f"\n{'='*60}")
print(f"问题: {query}")
print('='*60)
result = await detector.detect_intent_async(query)
print(result.to_json())
asyncio.run(test_intent_detection())