transcription/src/rag/query.py

155 lines
4.8 KiB
Python
Raw Normal View History

"""Запросы к RAG и генерация ответов чат-бота через DeepSeek."""
import os
from pathlib import Path
from typing import Any, Dict, List, Optional
from lightrag import QueryParam
from openai import AsyncOpenAI
from src.rag.indexer import get_global_rag, get_project_rag
async def retrieve_context(
question: str,
working_dir_base: Path,
project_name: Optional[str] = None,
mode: str = "hybrid",
api_key: str = "",
base_url: str = "https://opencode.ai/zen/v1",
index_model: str = "mimo-v2.5-free",
) -> str:
"""Извлекает релевантный контекст из LightRAG.
Args:
question: вопрос пользователя.
working_dir_base: базовая директория индексов.
project_name: если None ищет в глобальном индексе.
mode: режим поиска LightRAG (naive, local, global, hybrid).
Returns:
Строка с найденным контекстом.
"""
if project_name:
rag = await get_project_rag(
project_name,
working_dir_base,
model=index_model,
api_key=api_key,
base_url=base_url,
)
else:
rag = await get_global_rag(
working_dir_base,
model=index_model,
api_key=api_key,
base_url=base_url,
)
# only_need_context=True — возвращает только найденные фрагменты без генерации ответа
param = QueryParam(mode=mode, only_need_context=True)
context = await rag.aquery(question, param=param)
return context if context else ""
async def generate_chat_response(
question: str,
context: str,
history: List[Dict[str, str]],
api_key: str,
base_url: str = "https://opencode.ai/zen/v1",
model: str = "deepseek-v4-flash-free",
) -> str:
"""Генерирует ответ чат-бота через DeepSeek (или другую модель).
Args:
question: вопрос пользователя.
context: контекст из RAG.
history: список {"question": ..., "answer": ...} предыдущих сообщений.
api_key: API ключ.
base_url: base URL OpenCode.
model: модель для чата.
Returns:
Ответ ассистента.
"""
if not api_key:
raise ValueError(
"OPENCODE_API_KEY не задан. Укажите rag.opencode_api_key в config.yaml "
"или переменную окружения OPENCODE_API_KEY."
)
client = AsyncOpenAI(base_url=base_url, api_key=api_key)
system_prompt = (
"Ты — ассистент по протоколам совещаний строительной компании. "
"Отвечай на основе предоставленного контекста из протоколов. "
"Если в контексте нет ответа — так и скажи, не выдумывай. "
"Отвечай кратко и по делу."
)
messages = [{"role": "system", "content": system_prompt}]
for h in history:
messages.append({"role": "user", "content": h["question"]})
messages.append({"role": "assistant", "content": h["answer"]})
user_prompt = f"""Контекст из протоколов совещаний:
---
{context}
---
Вопрос: {question}
"""
messages.append({"role": "user", "content": user_prompt})
response = await client.chat.completions.create(
model=model,
messages=messages,
temperature=0.4,
max_tokens=2048,
)
content = response.choices[0].message.content
return content if content is not None else ""
async def rag_chat(
question: str,
working_dir_base: Path,
history: List[Dict[str, str]],
api_key: str,
project_name: Optional[str] = None,
base_url: str = "https://opencode.ai/zen/v1",
chat_model: str = "deepseek-v4-flash-free",
mode: str = "hybrid",
index_model: str = "mimo-v2.5-free",
) -> Dict[str, Any]:
"""Полный цикл RAG-чата: retrieval + generation.
Returns:
{"answer": str, "context": str, "project": str | None}
"""
context = await retrieve_context(
question=question,
working_dir_base=working_dir_base,
project_name=project_name,
mode=mode,
api_key=api_key,
base_url=base_url,
index_model=index_model,
)
answer = await generate_chat_response(
question=question,
context=context,
history=history,
api_key=api_key,
base_url=base_url,
model=chat_model,
)
return {
"answer": answer,
"context": context,
"project": project_name,
}