Подключил вызов search_knowledge_base вручную. Начал улучшение индексации RAG
This commit is contained in:
157
agent_core.py
157
agent_core.py
@@ -7,9 +7,12 @@ from langgraph.graph import StateGraph, END
|
|||||||
from langgraph.graph.message import add_messages
|
from langgraph.graph.message import add_messages
|
||||||
from langchain_core.messages import AIMessage, SystemMessage
|
from langchain_core.messages import AIMessage, SystemMessage
|
||||||
from langchain.tools import tool
|
from langchain.tools import tool
|
||||||
|
from local_knowledge_base import LocalKnowledgeBase
|
||||||
|
|
||||||
# from langchain.agents import AgentExecutor, create_react_agent
|
# from langchain.agents import AgentExecutor, create_react_agent
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
|
||||||
|
|
||||||
# Определяем состояние агента
|
# Определяем состояние агента
|
||||||
class AgentState(TypedDict):
|
class AgentState(TypedDict):
|
||||||
messages: Annotated[List, add_messages] # История сообщений
|
messages: Annotated[List, add_messages] # История сообщений
|
||||||
@@ -18,6 +21,7 @@ class AgentState(TypedDict):
|
|||||||
needs_search: bool # Нужен ли поиск в RAG
|
needs_search: bool # Нужен ли поиск в RAG
|
||||||
final_answer: Optional[str] # Финальный ответ
|
final_answer: Optional[str] # Финальный ответ
|
||||||
|
|
||||||
|
|
||||||
# Инициализация LLM через Ollama
|
# Инициализация LLM через Ollama
|
||||||
llm = Ollama(
|
llm = Ollama(
|
||||||
model="qwen2.5-coder-16k:14b",
|
model="qwen2.5-coder-16k:14b",
|
||||||
@@ -25,12 +29,46 @@ llm = Ollama(
|
|||||||
num_predict=1024, # Максимальная длина ответа
|
num_predict=1024, # Максимальная длина ответа
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
# Создаем инструменты для агента
|
# Создаем инструменты для агента
|
||||||
@tool
|
@tool
|
||||||
def search_knowledge_base(query: str) -> str:
|
def search_knowledge_base(knowledge_base: LocalKnowledgeBase, query: str) -> str:
|
||||||
"""Поиск информации в локальной базе знаний"""
|
"""
|
||||||
# Здесь будет подключение к Qdrant
|
🔍 Поиск информации в локальной базе знаний (база кода и документов).
|
||||||
return "Найденная информация из базы знаний"
|
|
||||||
|
🎯 КОГДА ИСПОЛЬЗОВАТЬ:
|
||||||
|
- Когда нужно найти определение класса, функции или структуры.
|
||||||
|
- Когда нужно найти реализацию алгоритма.
|
||||||
|
- Когда нужно найти описание ошибки или паттерна.
|
||||||
|
- Когда нужно найти документацию по проекту.
|
||||||
|
|
||||||
|
🚫 КОГДА НЕ ИСПОЛЬЗОВАТЬ:
|
||||||
|
- Для простых математических вычислений (используй calculate).
|
||||||
|
- Для общих вопросов, не связанных с загруженными файлами.
|
||||||
|
|
||||||
|
📝 ПРИМЕРЫ ЗАПРОСОВ:
|
||||||
|
- "Как работает класс Parser?"
|
||||||
|
- "Найди функцию обработки JSON"
|
||||||
|
- "Где реализован алгоритм сортировки?"
|
||||||
|
- "Объясни паттерн Singleton в этом проекте"
|
||||||
|
"""
|
||||||
|
|
||||||
|
# --- ЛОГИКА ПОИСКА ---
|
||||||
|
# Здесь должен быть реальный вызов к базе знаний.
|
||||||
|
# Пример реализации (нужно подключить к kb_instance):
|
||||||
|
#
|
||||||
|
results = knowledge_base.search(query, top_k=5)
|
||||||
|
if results:
|
||||||
|
context = "\n\n--- Найденные фрагменты ---\n\n"
|
||||||
|
for i, res in enumerate(results, 1):
|
||||||
|
context += f"[{i}] {res['text']}\n"
|
||||||
|
if len(res["text"]) > 500:
|
||||||
|
context += "..."
|
||||||
|
return context
|
||||||
|
else:
|
||||||
|
return "В базе знаний не найдено информации по запросу."
|
||||||
|
# return "Найденная информация из базы знаний"
|
||||||
|
|
||||||
|
|
||||||
@tool
|
@tool
|
||||||
def calculate(expression: str) -> str:
|
def calculate(expression: str) -> str:
|
||||||
@@ -41,14 +79,23 @@ def calculate(expression: str) -> str:
|
|||||||
except:
|
except:
|
||||||
return "Ошибка в выражении"
|
return "Ошибка в выражении"
|
||||||
|
|
||||||
|
|
||||||
@tool
|
@tool
|
||||||
def web_search(query: str) -> str:
|
def web_search(query: str) -> str:
|
||||||
"""Поиск в интернете (если нужно)"""
|
"""Поиск в интернете (если нужно)"""
|
||||||
# Можно подключить локальный поиск через DuckDuckGo
|
# Можно подключить локальный поиск через DuckDuckGo
|
||||||
return "Результаты поиска из интернета"
|
return "Результаты поиска из интернета"
|
||||||
|
|
||||||
|
|
||||||
|
# --- Глобальная инициализация базы знаний ---
|
||||||
|
# Мы создаем экземпляр базы знаний здесь, но он будет передан в граф
|
||||||
|
# В production лучше передавать как аргумент, но для простоты оставим здесь
|
||||||
|
# kb_instance = LocalKnowledgeBase(project_name="default")
|
||||||
|
|
||||||
|
|
||||||
# Создаем граф агента
|
# Создаем граф агента
|
||||||
def create_agent_graph():
|
def create_agent_graph(knowledge_base_instance: LocalKnowledgeBase):
|
||||||
|
kb = knowledge_base_instance
|
||||||
workflow = StateGraph(AgentState)
|
workflow = StateGraph(AgentState)
|
||||||
|
|
||||||
# Узел: анализ запроса
|
# Узел: анализ запроса
|
||||||
@@ -56,14 +103,64 @@ def create_agent_graph():
|
|||||||
messages = state["messages"]
|
messages = state["messages"]
|
||||||
last_message = messages[-1].content if messages else ""
|
last_message = messages[-1].content if messages else ""
|
||||||
|
|
||||||
# Простой анализ: проверяем, нужен ли поиск
|
print(f"\n🔍 АНАЛИЗ ЗАПРОСА: {last_message}")
|
||||||
search_keywords = ["информация", "документ", "найди", "ищи", "база знаний"]
|
|
||||||
needs_search = any(keyword in last_message.lower() for keyword in search_keywords)
|
|
||||||
|
|
||||||
return {
|
# 1. Исключаем запросы, которые точно НЕ нужны RAG
|
||||||
"needs_search": needs_search,
|
non_rag_keywords = [
|
||||||
"current_step": "analyzing_query"
|
"посчитай",
|
||||||
}
|
"рассчитай",
|
||||||
|
"сколько",
|
||||||
|
"сколько же",
|
||||||
|
"простой пример",
|
||||||
|
"1+1",
|
||||||
|
"2*2",
|
||||||
|
]
|
||||||
|
if any(kw in last_message.lower() for kw in non_rag_keywords):
|
||||||
|
return {"needs_search": False, "current_step": "generating_answer"}
|
||||||
|
|
||||||
|
# 2. Ищем паттерны, связанные с кодом и проектом
|
||||||
|
# Используем регулярные выражения или простые проверки
|
||||||
|
code_patterns = [
|
||||||
|
r"class\s+\w+", # Поиск классов
|
||||||
|
r"function\s+\w+", # Поиск функций
|
||||||
|
r"namespace\s+\w+", # Поиск пространств имен
|
||||||
|
r"#include", # Поиск включений
|
||||||
|
r"enum\s+\w+", # Поиск enum
|
||||||
|
r"struct\s+\w+", # Поиск структур
|
||||||
|
]
|
||||||
|
|
||||||
|
# Если запрос содержит технические термины, скорее всего нужен RAG
|
||||||
|
has_code_keywords = any(pattern in last_message for pattern in code_patterns)
|
||||||
|
|
||||||
|
# 3. Явные запросы на поиск информации
|
||||||
|
search_keywords = [
|
||||||
|
"как работает",
|
||||||
|
"найди",
|
||||||
|
"где реализовано",
|
||||||
|
"ошибка",
|
||||||
|
"баг",
|
||||||
|
"документация",
|
||||||
|
"описание",
|
||||||
|
"реализация",
|
||||||
|
"класс",
|
||||||
|
"функция",
|
||||||
|
"алгоритм",
|
||||||
|
"паттерн",
|
||||||
|
"структура",
|
||||||
|
"метод",
|
||||||
|
]
|
||||||
|
|
||||||
|
needs_search = any(
|
||||||
|
keyword in last_message.lower() for keyword in search_keywords
|
||||||
|
)
|
||||||
|
|
||||||
|
# Комбинированное решение
|
||||||
|
needs_search = needs_search or has_code_keywords
|
||||||
|
|
||||||
|
print(f"🔍 Паттерны кода найдены: {has_code_keywords}")
|
||||||
|
print(f"🔍 Ключевые слова найдены: {needs_search}")
|
||||||
|
|
||||||
|
return {"needs_search": needs_search, "current_step": "analyzing_query"}
|
||||||
|
|
||||||
# Узел: поиск в RAG
|
# Узел: поиск в RAG
|
||||||
def rag_search(state: AgentState):
|
def rag_search(state: AgentState):
|
||||||
@@ -75,20 +172,34 @@ def create_agent_graph():
|
|||||||
|
|
||||||
# Здесь должен быть реальный поиск в Qdrant
|
# Здесь должен быть реальный поиск в Qdrant
|
||||||
# Пока заглушка
|
# Пока заглушка
|
||||||
results = ["Документ 1: Информация о...", "Документ 2: Данные по..."]
|
# results = ["Документ 1: Информация о...", "Документ 2: Данные по..."]
|
||||||
|
search_result = search_knowledge_base.invoke(
|
||||||
|
{"knowledge_base": kb, "query": query}
|
||||||
|
)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"knowledge_base": "\n".join(results),
|
"knowledge_base": search_result,
|
||||||
"current_step": "generating_answer"
|
"current_step": "generating_answer",
|
||||||
}
|
}
|
||||||
|
|
||||||
# Узел: генерация ответа
|
# Узел: генерация ответа
|
||||||
def generate_answer(state: AgentState):
|
def generate_answer(state: AgentState):
|
||||||
|
# Добавляем отладочный вывод
|
||||||
|
print("\n=== 🧠 ПРОМПТ ДЛЯ LLM (что видит модель) ===")
|
||||||
|
print(f"Доступные инструменты:")
|
||||||
|
print(f"1. search_knowledge_base: {search_knowledge_base.description}")
|
||||||
|
print(f"2. calculate: {calculate.description}")
|
||||||
|
print(f"3. web_search: {web_search.description}")
|
||||||
|
print(f"\nКонтекст из поиска: {state.get('knowledge_base', '')}")
|
||||||
|
print(f"=== Конец промпта ===\n")
|
||||||
|
|
||||||
messages = state["messages"]
|
messages = state["messages"]
|
||||||
knowledge = state.get("knowledge_base", "")
|
knowledge = state.get("knowledge_base", "")
|
||||||
|
|
||||||
# Формируем промпт с контекстом
|
# Формируем промпт с контекстом
|
||||||
prompt = f"""Ты — интеллектуальный ассистент. Используй следующую информацию если она релевантна:
|
prompt = f"""Ты — интеллектуальный ассистент по C++ и архитектуре ПО.
|
||||||
|
|
||||||
|
Твоя задача: отвечать на вопросы пользователя, опираясь на загруженный код и документацию.
|
||||||
|
|
||||||
Контекст из базы знаний:
|
Контекст из базы знаний:
|
||||||
{knowledge}
|
{knowledge}
|
||||||
@@ -98,6 +209,18 @@ def create_agent_graph():
|
|||||||
|
|
||||||
Текущий запрос: {messages[-1].content if messages else ''}
|
Текущий запрос: {messages[-1].content if messages else ''}
|
||||||
|
|
||||||
|
ИНСТРУКЦИИ:
|
||||||
|
1. Если контекст из базы знаний содержит информацию, релевантную запросу:
|
||||||
|
- Используй его для точного ответа.
|
||||||
|
- Цитируй фрагменты кода, если это нужно для объяснения.
|
||||||
|
- Объясняй код простым языком.
|
||||||
|
|
||||||
|
2. Если контекст пуст или не релевантен:
|
||||||
|
- Ответь что не знаешь
|
||||||
|
|
||||||
|
4. Если в найденных фрагментах есть несколько упоминаний одного и того же класса:
|
||||||
|
- Объясни каждый из них отдельно.
|
||||||
|
|
||||||
Ответь максимально полезно и точно:"""
|
Ответь максимально полезно и точно:"""
|
||||||
|
|
||||||
response = llm.invoke(prompt)
|
response = llm.invoke(prompt)
|
||||||
@@ -105,7 +228,7 @@ def create_agent_graph():
|
|||||||
return {
|
return {
|
||||||
"final_answer": response,
|
"final_answer": response,
|
||||||
"current_step": "completed",
|
"current_step": "completed",
|
||||||
"messages": messages + [AIMessage(content=response)]
|
"messages": messages + [AIMessage(content=response)],
|
||||||
}
|
}
|
||||||
|
|
||||||
# Добавляем узлы в граф
|
# Добавляем узлы в граф
|
||||||
|
|||||||
@@ -104,7 +104,7 @@ class AgenticRAGInterface:
|
|||||||
kb = LocalKnowledgeBase(project_name="default")
|
kb = LocalKnowledgeBase(project_name="default")
|
||||||
|
|
||||||
# Создаем интерфейс
|
# Создаем интерфейс
|
||||||
interface = AgenticRAGInterface(create_agent_graph(), kb)
|
interface = AgenticRAGInterface(create_agent_graph(kb), kb)
|
||||||
|
|
||||||
# Создаем Gradio интерфейс
|
# Создаем Gradio интерфейс
|
||||||
def create_gradio_interface():
|
def create_gradio_interface():
|
||||||
|
|||||||
@@ -110,9 +110,9 @@ class LocalKnowledgeBase:
|
|||||||
# Разбиваем на чанки
|
# Разбиваем на чанки
|
||||||
text_splitter = RecursiveCharacterTextSplitter.from_language(
|
text_splitter = RecursiveCharacterTextSplitter.from_language(
|
||||||
language=Language.CPP,
|
language=Language.CPP,
|
||||||
chunk_size=1000,
|
chunk_size=500,
|
||||||
chunk_overlap=200,
|
chunk_overlap=100,
|
||||||
length_function=len,
|
length_function=len
|
||||||
)
|
)
|
||||||
|
|
||||||
chunks = text_splitter.split_documents(all_documents)
|
chunks = text_splitter.split_documents(all_documents)
|
||||||
@@ -131,6 +131,18 @@ class LocalKnowledgeBase:
|
|||||||
# Создаем эмбеддинг для каждого чанка
|
# Создаем эмбеддинг для каждого чанка
|
||||||
embedding = self.embeddings.embed_query(doc.page_content)
|
embedding = self.embeddings.embed_query(doc.page_content)
|
||||||
|
|
||||||
|
# Извлекаем название функции/класса/строки из контекста
|
||||||
|
metadata = doc.metadata
|
||||||
|
|
||||||
|
# Простая эвристика: ищем объявление функции/класса в начале чанка
|
||||||
|
import re
|
||||||
|
# Ищем паттерны: "void funcName(...)", "class ClassName", "int funcName(...)"
|
||||||
|
func_match = re.search(r'(?:class|struct|enum)\s+(\w+)', doc.page_content)
|
||||||
|
if not func_match:
|
||||||
|
func_match = re.search(r'(?:void|int|float|double|bool|auto)\s+(\w+)\s*\(', doc.page_content)
|
||||||
|
|
||||||
|
function_name = func_match.group(1) if func_match else "unknown"
|
||||||
|
|
||||||
point = PointStruct(
|
point = PointStruct(
|
||||||
id=i,
|
id=i,
|
||||||
vector=embedding,
|
vector=embedding,
|
||||||
@@ -139,7 +151,16 @@ class LocalKnowledgeBase:
|
|||||||
"source": doc.metadata.get("source", "unknown"),
|
"source": doc.metadata.get("source", "unknown"),
|
||||||
"page": doc.metadata.get("page", 0),
|
"page": doc.metadata.get("page", 0),
|
||||||
"file_type": "cpp",
|
"file_type": "cpp",
|
||||||
|
"function_name": function_name, # ✅ Добавил
|
||||||
|
"class_name": func_match.group(1) if func_match else None, # ✅ Добавил
|
||||||
|
"line_start": doc.metadata.get("line", 0), # Если есть в метаданных
|
||||||
},
|
},
|
||||||
|
# payload={
|
||||||
|
# "text": doc.page_content,
|
||||||
|
# "source": doc.metadata.get("source", "unknown"),
|
||||||
|
# "page": doc.metadata.get("page", 0),
|
||||||
|
# "file_type": "cpp",
|
||||||
|
# },
|
||||||
)
|
)
|
||||||
points.append(point)
|
points.append(point)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user