Second
This commit is contained in:
132
agent_core.py
Normal file
132
agent_core.py
Normal file
@@ -0,0 +1,132 @@
|
|||||||
|
from typing import TypedDict, List, Optional, Annotated
|
||||||
|
import operator
|
||||||
|
from langchain_community.llms import Ollama
|
||||||
|
from langchain_community.embeddings import OllamaEmbeddings
|
||||||
|
from langchain_community.vectorstores import Qdrant
|
||||||
|
from langchain.text_splitter import RecursiveCharacterTextSplitter
|
||||||
|
from langchain_community.document_loaders import DirectoryLoader, PyPDFLoader
|
||||||
|
from langgraph.graph import StateGraph, END
|
||||||
|
from langgraph.graph.message import add_messages
|
||||||
|
from langchain_core.messages import HumanMessage, AIMessage, SystemMessage
|
||||||
|
from langchain.tools import tool
|
||||||
|
from langchain.agents import AgentExecutor, create_react_agent
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
# Определяем состояние агента
|
||||||
|
class AgentState(TypedDict):
|
||||||
|
messages: Annotated[List, add_messages] # История сообщений
|
||||||
|
knowledge_base: Optional[str] # Релевантные документы
|
||||||
|
current_step: str # Текущий шаг выполнения
|
||||||
|
needs_search: bool # Нужен ли поиск в RAG
|
||||||
|
final_answer: Optional[str] # Финальный ответ
|
||||||
|
|
||||||
|
# Инициализация LLM через Ollama
|
||||||
|
llm = Ollama(
|
||||||
|
model="llama3.2:3b",
|
||||||
|
temperature=0.1, # Низкая температура для консистентности
|
||||||
|
num_predict=1024, # Максимальная длина ответа
|
||||||
|
)
|
||||||
|
|
||||||
|
# Инициализация эмбеддингов
|
||||||
|
embeddings = OllamaEmbeddings(
|
||||||
|
model="nomic-embed-text", # Хорошие локальные эмбеддинги
|
||||||
|
)
|
||||||
|
|
||||||
|
# Создаем инструменты для агента
|
||||||
|
@tool
|
||||||
|
def search_knowledge_base(query: str) -> str:
|
||||||
|
"""Поиск информации в локальной базе знаний"""
|
||||||
|
# Здесь будет подключение к Qdrant
|
||||||
|
return "Найденная информация из базы знаний"
|
||||||
|
|
||||||
|
@tool
|
||||||
|
def calculate(expression: str) -> str:
|
||||||
|
"""Выполнение математических вычислений"""
|
||||||
|
try:
|
||||||
|
result = eval(expression)
|
||||||
|
return f"Результат: {result}"
|
||||||
|
except:
|
||||||
|
return "Ошибка в выражении"
|
||||||
|
|
||||||
|
@tool
|
||||||
|
def web_search(query: str) -> str:
|
||||||
|
"""Поиск в интернете (если нужно)"""
|
||||||
|
# Можно подключить локальный поиск через DuckDuckGo
|
||||||
|
return "Результаты поиска из интернета"
|
||||||
|
|
||||||
|
# Создаем граф агента
|
||||||
|
def create_agent_graph():
|
||||||
|
workflow = StateGraph(AgentState)
|
||||||
|
|
||||||
|
# Узел: анализ запроса
|
||||||
|
def analyze_query(state: AgentState):
|
||||||
|
messages = state["messages"]
|
||||||
|
last_message = messages[-1].content if messages else ""
|
||||||
|
|
||||||
|
# Простой анализ: проверяем, нужен ли поиск
|
||||||
|
search_keywords = ["информация", "документ", "найди", "ищи", "база знаний"]
|
||||||
|
needs_search = any(keyword in last_message.lower() for keyword in search_keywords)
|
||||||
|
|
||||||
|
return {
|
||||||
|
"needs_search": needs_search,
|
||||||
|
"current_step": "analyzing_query"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Узел: поиск в RAG
|
||||||
|
def rag_search(state: AgentState):
|
||||||
|
if not state["needs_search"]:
|
||||||
|
return {"knowledge_base": None, "current_step": "generating_answer"}
|
||||||
|
|
||||||
|
# Получаем последний запрос
|
||||||
|
query = state["messages"][-1].content
|
||||||
|
|
||||||
|
# Здесь должен быть реальный поиск в Qdrant
|
||||||
|
# Пока заглушка
|
||||||
|
results = ["Документ 1: Информация о...", "Документ 2: Данные по..."]
|
||||||
|
|
||||||
|
return {
|
||||||
|
"knowledge_base": "\n".join(results),
|
||||||
|
"current_step": "generating_answer"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Узел: генерация ответа
|
||||||
|
def generate_answer(state: AgentState):
|
||||||
|
messages = state["messages"]
|
||||||
|
knowledge = state.get("knowledge_base", "")
|
||||||
|
|
||||||
|
# Формируем промпт с контекстом
|
||||||
|
prompt = f"""Ты — интеллектуальный ассистент. Используй следующую информацию если она релевантна:
|
||||||
|
|
||||||
|
Контекст из базы знаний:
|
||||||
|
{knowledge}
|
||||||
|
|
||||||
|
История разговора:
|
||||||
|
{messages[-5:] if len(messages) > 5 else messages}
|
||||||
|
|
||||||
|
Текущий запрос: {messages[-1].content if messages else ''}
|
||||||
|
|
||||||
|
Ответь максимально полезно и точно:"""
|
||||||
|
|
||||||
|
response = llm.invoke(prompt)
|
||||||
|
|
||||||
|
return {
|
||||||
|
"final_answer": response,
|
||||||
|
"current_step": "completed",
|
||||||
|
"messages": messages + [AIMessage(content=response)]
|
||||||
|
}
|
||||||
|
|
||||||
|
# Добавляем узлы в граф
|
||||||
|
workflow.add_node("analyze", analyze_query)
|
||||||
|
workflow.add_node("search", rag_search)
|
||||||
|
workflow.add_node("generate", generate_answer)
|
||||||
|
|
||||||
|
# Определяем edges (переходы)
|
||||||
|
workflow.set_entry_point("analyze")
|
||||||
|
workflow.add_edge("analyze", "search")
|
||||||
|
workflow.add_edge("search", "generate")
|
||||||
|
workflow.add_edge("generate", END)
|
||||||
|
|
||||||
|
return workflow.compile()
|
||||||
|
|
||||||
|
# Создаем и компилируем граф
|
||||||
|
agent_graph = create_agent_graph()
|
||||||
@@ -133,12 +133,3 @@ def create_gradio_interface():
|
|||||||
)
|
)
|
||||||
|
|
||||||
return demo
|
return demo
|
||||||
|
|
||||||
# Запускаем интерфейс
|
|
||||||
if __name__ == "__main__":
|
|
||||||
demo = create_gradio_interface()
|
|
||||||
demo.launch(
|
|
||||||
server_name="0.0.0.0",
|
|
||||||
server_port=7860,
|
|
||||||
share=False # Не создавать публичную ссылку
|
|
||||||
)
|
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
import os
|
import os
|
||||||
from qdrant_client import QdrantClient
|
from qdrant_client import QdrantClient
|
||||||
from qdrant_client.models import Distance, VectorParams, PointStruct
|
from qdrant_client.models import Distance, VectorParams, PointStruct
|
||||||
|
from langchain_community.document_loaders import TextLoader, PyPDFLoader, DirectoryLoader
|
||||||
|
from langchain.text_splitter import Language
|
||||||
|
|
||||||
class LocalKnowledgeBase:
|
class LocalKnowledgeBase:
|
||||||
def __init__(self, collection_name="documents", persist_dir="./qdrant_data"):
|
def __init__(self, collection_name="documents", persist_dir="./qdrant_data"):
|
||||||
@@ -37,6 +39,11 @@ class LocalKnowledgeBase:
|
|||||||
'.pdf': PyPDFLoader,
|
'.pdf': PyPDFLoader,
|
||||||
'.txt': lambda path: DirectoryLoader(path, glob="**/*.txt"),
|
'.txt': lambda path: DirectoryLoader(path, glob="**/*.txt"),
|
||||||
'.docx': lambda path: DirectoryLoader(path, glob="**/*.docx"),
|
'.docx': lambda path: DirectoryLoader(path, glob="**/*.docx"),
|
||||||
|
'.hpp': lambda path: TextLoader(path, encoding='utf-8'),
|
||||||
|
'.cpp': lambda path: TextLoader(path, encoding='utf-8'), # добавить
|
||||||
|
'.h': lambda path: TextLoader(path, encoding='utf-8'), # добавить
|
||||||
|
'.cc': lambda path: TextLoader(path, encoding='utf-8'), # добавить
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
all_documents = []
|
all_documents = []
|
||||||
@@ -54,7 +61,8 @@ class LocalKnowledgeBase:
|
|||||||
print(f"Ошибка загрузки {file_path}: {e}")
|
print(f"Ошибка загрузки {file_path}: {e}")
|
||||||
|
|
||||||
# Разбиваем на чанки
|
# Разбиваем на чанки
|
||||||
text_splitter = RecursiveCharacterTextSplitter(
|
text_splitter = RecursiveCharacterTextSplitter.from_language(
|
||||||
|
language=Language.CPP,
|
||||||
chunk_size=1000,
|
chunk_size=1000,
|
||||||
chunk_overlap=200,
|
chunk_overlap=200,
|
||||||
length_function=len
|
length_function=len
|
||||||
@@ -82,7 +90,8 @@ class LocalKnowledgeBase:
|
|||||||
payload={
|
payload={
|
||||||
"text": doc.page_content,
|
"text": doc.page_content,
|
||||||
"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"
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
points.append(point)
|
points.append(point)
|
||||||
|
|||||||
140
main.py
140
main.py
@@ -1,132 +1,10 @@
|
|||||||
from typing import TypedDict, List, Optional, Annotated
|
from agentic_rag_interface import create_gradio_interface
|
||||||
import operator
|
|
||||||
from langchain_community.llms import Ollama
|
|
||||||
from langchain_community.embeddings import OllamaEmbeddings
|
|
||||||
from langchain_community.vectorstores import Qdrant
|
|
||||||
from langchain.text_splitter import RecursiveCharacterTextSplitter
|
|
||||||
from langchain_community.document_loaders import DirectoryLoader, PyPDFLoader
|
|
||||||
from langgraph.graph import StateGraph, END
|
|
||||||
from langgraph.graph.message import add_messages
|
|
||||||
from langchain_core.messages import HumanMessage, AIMessage, SystemMessage
|
|
||||||
from langchain.tools import tool
|
|
||||||
from langchain.agents import AgentExecutor, create_react_agent
|
|
||||||
import numpy as np
|
|
||||||
|
|
||||||
# Определяем состояние агента
|
# Запускаем интерфейс
|
||||||
class AgentState(TypedDict):
|
if __name__ == "__main__":
|
||||||
messages: Annotated[List, add_messages] # История сообщений
|
demo = create_gradio_interface()
|
||||||
knowledge_base: Optional[str] # Релевантные документы
|
demo.launch(
|
||||||
current_step: str # Текущий шаг выполнения
|
server_name="0.0.0.0",
|
||||||
needs_search: bool # Нужен ли поиск в RAG
|
server_port=7860,
|
||||||
final_answer: Optional[str] # Финальный ответ
|
share=False # Не создавать публичную ссылку
|
||||||
|
)
|
||||||
# Инициализация LLM через Ollama
|
|
||||||
llm = Ollama(
|
|
||||||
model="llama3.2:3b",
|
|
||||||
temperature=0.1, # Низкая температура для консистентности
|
|
||||||
num_predict=1024, # Максимальная длина ответа
|
|
||||||
)
|
|
||||||
|
|
||||||
# Инициализация эмбеддингов
|
|
||||||
embeddings = OllamaEmbeddings(
|
|
||||||
model="nomic-embed-text", # Хорошие локальные эмбеддинги
|
|
||||||
)
|
|
||||||
|
|
||||||
# Создаем инструменты для агента
|
|
||||||
@tool
|
|
||||||
def search_knowledge_base(query: str) -> str:
|
|
||||||
"""Поиск информации в локальной базе знаний"""
|
|
||||||
# Здесь будет подключение к Qdrant
|
|
||||||
return "Найденная информация из базы знаний"
|
|
||||||
|
|
||||||
@tool
|
|
||||||
def calculate(expression: str) -> str:
|
|
||||||
"""Выполнение математических вычислений"""
|
|
||||||
try:
|
|
||||||
result = eval(expression)
|
|
||||||
return f"Результат: {result}"
|
|
||||||
except:
|
|
||||||
return "Ошибка в выражении"
|
|
||||||
|
|
||||||
@tool
|
|
||||||
def web_search(query: str) -> str:
|
|
||||||
"""Поиск в интернете (если нужно)"""
|
|
||||||
# Можно подключить локальный поиск через DuckDuckGo
|
|
||||||
return "Результаты поиска из интернета"
|
|
||||||
|
|
||||||
# Создаем граф агента
|
|
||||||
def create_agent_graph():
|
|
||||||
workflow = StateGraph(AgentState)
|
|
||||||
|
|
||||||
# Узел: анализ запроса
|
|
||||||
def analyze_query(state: AgentState):
|
|
||||||
messages = state["messages"]
|
|
||||||
last_message = messages[-1].content if messages else ""
|
|
||||||
|
|
||||||
# Простой анализ: проверяем, нужен ли поиск
|
|
||||||
search_keywords = ["информация", "документ", "найди", "ищи", "база знаний"]
|
|
||||||
needs_search = any(keyword in last_message.lower() for keyword in search_keywords)
|
|
||||||
|
|
||||||
return {
|
|
||||||
"needs_search": needs_search,
|
|
||||||
"current_step": "analyzing_query"
|
|
||||||
}
|
|
||||||
|
|
||||||
# Узел: поиск в RAG
|
|
||||||
def rag_search(state: AgentState):
|
|
||||||
if not state["needs_search"]:
|
|
||||||
return {"knowledge_base": None, "current_step": "generating_answer"}
|
|
||||||
|
|
||||||
# Получаем последний запрос
|
|
||||||
query = state["messages"][-1].content
|
|
||||||
|
|
||||||
# Здесь должен быть реальный поиск в Qdrant
|
|
||||||
# Пока заглушка
|
|
||||||
results = ["Документ 1: Информация о...", "Документ 2: Данные по..."]
|
|
||||||
|
|
||||||
return {
|
|
||||||
"knowledge_base": "\n".join(results),
|
|
||||||
"current_step": "generating_answer"
|
|
||||||
}
|
|
||||||
|
|
||||||
# Узел: генерация ответа
|
|
||||||
def generate_answer(state: AgentState):
|
|
||||||
messages = state["messages"]
|
|
||||||
knowledge = state.get("knowledge_base", "")
|
|
||||||
|
|
||||||
# Формируем промпт с контекстом
|
|
||||||
prompt = f"""Ты — интеллектуальный ассистент. Используй следующую информацию если она релевантна:
|
|
||||||
|
|
||||||
Контекст из базы знаний:
|
|
||||||
{knowledge}
|
|
||||||
|
|
||||||
История разговора:
|
|
||||||
{messages[-5:] if len(messages) > 5 else messages}
|
|
||||||
|
|
||||||
Текущий запрос: {messages[-1].content if messages else ''}
|
|
||||||
|
|
||||||
Ответь максимально полезно и точно:"""
|
|
||||||
|
|
||||||
response = llm.invoke(prompt)
|
|
||||||
|
|
||||||
return {
|
|
||||||
"final_answer": response,
|
|
||||||
"current_step": "completed",
|
|
||||||
"messages": messages + [AIMessage(content=response)]
|
|
||||||
}
|
|
||||||
|
|
||||||
# Добавляем узлы в граф
|
|
||||||
workflow.add_node("analyze", analyze_query)
|
|
||||||
workflow.add_node("search", rag_search)
|
|
||||||
workflow.add_node("generate", generate_answer)
|
|
||||||
|
|
||||||
# Определяем edges (переходы)
|
|
||||||
workflow.set_entry_point("analyze")
|
|
||||||
workflow.add_edge("analyze", "search")
|
|
||||||
workflow.add_edge("search", "generate")
|
|
||||||
workflow.add_edge("generate", END)
|
|
||||||
|
|
||||||
return workflow.compile()
|
|
||||||
|
|
||||||
# Создаем и компилируем граф
|
|
||||||
agent_graph = create_agent_graph()
|
|
||||||
Reference in New Issue
Block a user