commit 7fe682e97e533c0238287f42c48a72e4e5597618 Author: Илья 5f1b81b8-4f5d-11e8-9c2d-fa7ae01bbebc Date: Sat Jun 27 13:51:03 2026 +0000 add: main.py diff --git a/main.py b/main.py new file mode 100644 index 0000000..b2e3232 --- /dev/null +++ b/main.py @@ -0,0 +1,146 @@ +import os +import asyncio +import argparse +from pathlib import Path + +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 + +# -------------------- 1. LLM and Embeddings -------------------- +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"), +) + +# -------------------- 2. Chroma DB -------------------- +CHROMA_PATH = Path("./chroma_faq") +CHROMA_COLLECTION = "course_faq" + +vector_store = Chroma( + collection_name=CHROMA_COLLECTION, + embedding_function=embeddings, + persist_directory=str(CHROMA_PATH), +) + +# Persist changes +vector_store.persist() + +# -------------------- 3. Tools -------------------- +@tool +def search_knowledge(query: str) -> str: + """Search the knowledge base for relevant information.""" + docs = vector_store.similarity_search(query, k=3) + return "\n".join(d.page_content for d in docs) if docs else "No results found in course materials." + +@tool +def fetch_course_meta(query: str) -> str: + """Fetch course metadata (schedule, syllabus, etc.) from a static JSON file.""" + meta_path = Path("meta.json") + if not meta_path.exists(): + return "Metadata file not found." + import json + data = json.loads(meta_path.read_text()) + query_lower = query.lower() + if "schedule" in query_lower: + return f"Course schedule: {data.get('schedule', 'Not available')}" + if "syllabus" in query_lower: + return f"Syllabus URL: {data.get('syllabus_url', 'Not available')}" + if "instructor" in query_lower: + return f"Instructor: {data.get('instructor', 'Not available')}" + # Default: return all + return json.dumps(data, indent=2) + +# -------------------- 4. Backend -------------------- +backend = CompositeBackend([ + LocalShellBackend(workspace_dir="./workspace"), + FilesystemBackend(), +]) + +# -------------------- 5. Agent -------------------- +SYSTEM_PROMPT = ( + "You are a helpful FAQ bot for the course.\n" + "Use the 'search_knowledge' tool to answer questions about course materials.\n" + "Use the 'fetch_course_meta' tool for questions about schedule, syllabus, instructor, etc.\n" + "Do not call both tools unnecessarily.\n" + "In your final answer, prepend 'source: chroma' or 'source: mcp_meta' to indicate which tool provided the information." +) + +agent = create_deep_agent( + model=llm, + tools=[search_knowledge, fetch_course_meta], + backend=backend, + system_prompt=SYSTEM_PROMPT, +) + +# -------------------- 6. Data Loader -------------------- + +def load_faq_to_chroma(): + data_dir = Path("data") + if not data_dir.exists(): + data_dir.mkdir() + # Create sample markdown files if missing + (data_dir / "file1.md").write_text( + "## Course Overview\nThis course covers advanced topics in AI. Topics include machine learning, deep learning, and natural language processing." + ) + (data_dir / "file2.md").write_text( + "## FAQ\nQ: What is the schedule?\nA: Sessions are held on Mondays and Wednesdays.\nQ: Where can I find the syllabus?\nA: Syllabus is available on the course website." + ) + docs = [] + for md_file in data_dir.glob("*.md"): + text = md_file.read_text() + docs.append(Document(page_content=text, metadata={"source": md_file.name})) + vector_store.add_documents(docs) + vector_store.persist() + print(f"Loaded {len(docs)} documents into Chroma collection '{CHROMA_COLLECTION}'.") + +# -------------------- 7. CLI -------------------- +PRESET_QUESTIONS = { + "1": "What topics are covered in the course?", + "2": "Where can I find the syllabus?", + "3": "What is the course schedule?", +} + +async def run_question(question: str, thread_id: str = "session-1"): + result = await agent.ainvoke( + {"messages": ["HumanMessage(content=\"{}\")".format(question)]}, + {"configurable": {"thread_id": thread_id}}, + ) + # The agent returns a dict with 'messages'; extract last message + content = result["messages"][-1].content + print(content) + +async def main(): + # Ensure data is loaded + load_faq_to_chroma() + + parser = argparse.ArgumentParser(description="FAQ Bot CLI") + parser.add_argument("--preset", choices=["1", "2", "3"], help="Run a preset question") + args = parser.parse_args() + + if args.preset: + question = PRESET_QUESTIONS[args.preset] + print(f"Preset question {args.preset}: {question}") + await run_question(question) + else: + print("Enter your question (type 'exit' to quit):") + while True: + q = input("> ") + if q.lower() in {"exit", "quit"}: + break + if q.strip(): + await run_question(q) + +if __name__ == "__main__": + asyncio.run(main())