From 14bdb21a34e60ea859ef92d9068369f6cfa0da47 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=94=D0=B0=D0=BD=D0=B8=D0=B8=D0=BB=20=D0=92=D0=B8=D0=BA?= =?UTF-8?q?=D1=82=D0=BE=D1=80=D0=BE=D0=B2?= Date: Tue, 30 Jun 2026 08:22:25 +0000 Subject: [PATCH] =?UTF-8?q?add:=20main.py=20=E2=80=94=20=D0=9F=D0=BE=D0=B2?= =?UTF-8?q?=D1=82=D0=BE=D1=80=D0=BD=D1=8B=D0=B9=20=D1=8D=D0=BA=D0=B7=D0=B0?= =?UTF-8?q?=D0=BC=D0=B5=D0=BD:=20FAQ-=D0=B1=D0=BE=D1=82=20=E2=80=94=20Chro?= =?UTF-8?q?maDB=20+=20=D0=BE=D0=B4=D0=B8=D0=BD=20MCP-tool?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main.py | 138 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 138 insertions(+) create mode 100644 main.py diff --git a/main.py b/main.py new file mode 100644 index 0000000..0b37309 --- /dev/null +++ b/main.py @@ -0,0 +1,138 @@ +import os +import asyncio +import json +from pathlib import Path +from dotenv import load_dotenv + +from langchain_openai import ChatOpenAI, OpenAIEmbeddings +from langchain_chroma import Chroma +from langchain_core.documents import Document +from langchain.tools import tool +from deepagents import create_deep_agent +from deepagents.backends import FilesystemBackend, LocalShellBackend, CompositeBackend +from langchain_core.messages import HumanMessage + +# Загрузка переменных окружения +load_dotenv() + +# Настройка LLM и эмбеддингов через OpenRouter +llm = ChatOpenAI( + model="openai/gpt-oss-20b:free", + base_url="https://openrouter.ai/api/v1", + api_key=os.getenv("OPENAI_API_KEY"), + temperature=0.0, +) + +embeddings = OpenAIEmbeddings( + model="text-embedding-3-small", + base_url="https://openrouter.ai/api/v1", + api_key=os.getenv("OPENAI_API_KEY"), +) + +# Инициализация Chroma +vector_store = Chroma( + collection_name="faq_collection", + embedding_function=embeddings, + persist_directory="./chroma_faq", +) + +# Загрузка FAQ из markdown файлов в Chroma +def load_faq_to_chroma() -> None: + data_dir = Path("data") + if not data_dir.exists(): + return + docs = [] + for md_file in data_dir.glob("*.md"): + content = md_file.read_text(encoding="utf-8") + title = md_file.stem + docs.append(Document(page_content=content, metadata={"title": title})) + if docs: + vector_store.add_documents(docs) + +# Tool: поиск по базе знаний +@tool +def search_course_docs(query: str, k: int = 3) -> str: + """Search the knowledge base for relevant information.""" + docs = vector_store.similarity_search(query, k=k) + return "\n".join(d.page_content for d in docs) if docs else "No results." + +# Tool: получение метаданных курса (MCP-стиль) +@tool +def fetch_course_meta(query: str) -> str: + """Fetch course metadata from local JSON.""" + meta_path = Path("meta.json") + if not meta_path.exists(): + return "Metadata file not found." + data = json.loads(meta_path.read_text(encoding="utf-8")) + # Простая фильтрация по ключевому слову в запросе + if "schedule" in query.lower(): + return json.dumps(data.get("schedule", []), indent=2) + if "instructor" in query.lower(): + return data.get("instructor", "Unknown") + return json.dumps(data, indent=2) + +# Системный промпт агента +system_prompt = ( + "You are a helpful FAQ bot. Use only one tool per query. " + "If the question is about course materials, use search_course_docs. " + "If about schedule or metadata, use fetch_course_meta. " + "Indicate source in answer: source: chroma | mcp_meta." +) + +# Backend для deepagents +backend = CompositeBackend( + [ + LocalShellBackend(workspace_dir="./workspace"), + FilesystemBackend(), + ] +) + +# Создание агента +agent = create_deep_agent( + model=llm, + tools=[search_course_docs, fetch_course_meta], + backend=backend, + system_prompt=system_prompt, +) + +# Предварительная загрузка FAQ +load_faq_to_chroma() + +# Предустановленные вопросы +preset_questions = [ + "Где находятся материалы по лекции 3?", + "Как изменить пароль?", + "Какой график занятий на следующую неделю?", +] + +async def main() -> None: + print("FAQ Bot CLI") + print("1-3: Предустановленные вопросы") + print("4: Ввести собственный вопрос") + print("5: Выход") + while True: + choice = input("Выберите вариант (1-5): ").strip() + if choice == "5": + print("До свидания!") + break + if choice in {"1", "2", "3"}: + question = preset_questions[int(choice) - 1] + elif choice == "4": + question = input("Введите ваш вопрос: ").strip() + if not question: + print("Пустой вопрос, попробуйте снова.") + continue + else: + print("Неверный выбор, попробуйте снова.") + continue + + print(f"Вопрос: {question}") + result = await agent.ainvoke( + {"messages": [HumanMessage(content=question)]}, + {"configurable": {"thread_id": "session-1"}}, + ) + answer = result["messages"][-1].content + print(f"Ответ:\n{answer}\n") + +if __name__ == "__main__": + asyncio.run(main()) \ No newline at end of file