From 17747c62c9f4548ba2780e8ccfad08e2afe2d6c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=93=D0=BB=D0=B5=D0=B1=20=D0=9D=D0=B8=D0=BA=D0=B8=D1=88?= =?UTF-8?q?=D0=B8=D0=BD?= Date: Thu, 4 Jun 2026 15:58:14 +0000 Subject: [PATCH] add main.py --- main.py | 114 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 114 insertions(+) create mode 100644 main.py diff --git a/main.py b/main.py new file mode 100644 index 0000000..0a7335a --- /dev/null +++ b/main.py @@ -0,0 +1,114 @@ +import asyncio, os +from pathlib import Path +from langchain_openai import ChatOpenAI +from langchain_core.messages import HumanMessage +from langchain.tools import tool +from deepagents import create_deep_agent +from deepagents.backends import FilesystemBackend, LocalShellBackend, CompositeBackend +from langchain_chroma import Chroma +from langchain_ollama import OllamaEmbeddings +import httpx +import json + +# ---------- LLM ---------- +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, +) + +# ---------- Backend ---------- +backend = CompositeBackend([ + LocalShellBackend(workspace_dir="./workspace"), + FilesystemBackend(), +]) + +# ---------- Chroma DB ---------- +CHROMA_PATH = Path("./chroma_faq") +CHROMA_PATH.mkdir(exist_ok=True) + +embeddings = OllamaEmbeddings(model="nomic-embed-text") + +# Load or create vector store +if CHROMA_PATH.exists() and any(CHROMA_PATH.iterdir()): + chroma = Chroma(persist_directory=str(CHROMA_PATH), embedding_function=embeddings) +else: + # Load markdown files + docs = [] + for md_file in Path("data").glob("*.md"): + text = md_file.read_text(encoding="utf-8") + docs.append(text) + chroma = Chroma.from_texts(docs, embedding=embeddings, persist_directory=str(CHROMA_PATH)) + chroma.persist() + +@tool +def search_course_docs(query: str, k: int = 3) -> str: + """Search local course documents in Chroma.""" + results = chroma.similarity_search(query, k=k) + return "\n---\n".join(doc.page_content for doc in results) if results else "No relevant docs found." + +# ---------- MCP‑style tool ---------- +# For demo we use a static JSON file. In production this would be an HTTP call. +META_JSON = Path("meta.json") +if not META_JSON.exists(): + # Create a simple mock meta file + META_JSON.write_text(json.dumps({ + "schedule": "Mon 10-12, Wed 14-16, Fri 9-11", + "instructor": "Dr. Smith", + "location": "Room 101" + })) + +@tool +def fetch_course_meta(query: str) -> str: + """Return course metadata matching the query keyword.""" + data = json.loads(META_JSON.read_text()) + # Simple keyword search in values + for key, value in data.items(): + if query.lower() in key.lower() or query.lower() in str(value).lower(): + return f"{key}: {value}" + return "No metadata found for the query." + +# ---------- Agent ---------- +agent = create_deep_agent( + model=llm, + tools=[search_course_docs, fetch_course_meta], + backend=backend, + system_prompt=( + "You are a helpful FAQ bot for the course.\n" + "If the question is about course content, use search_course_docs.\n" + "If the question is about schedule, instructor, or location, use fetch_course_meta.\n" + "Do not call both tools unless necessary.\n" + "In your answer, prefix the source with 'source: chroma' or 'source: mcp_meta'." + ), +) + +# ---------- CLI ---------- +PRESET_QUESTIONS = [ + "What is the main topic of the first lecture?", + "How can I access the lecture slides?", + "When is the next class?" +] + +async def run_cli(): + print("--- FAQ Bot CLI ---") + for i, q in enumerate(PRESET_QUESTIONS, 1): + print(f"\nPreset {i}: {q}") + result = await agent.ainvoke( + {"messages": [HumanMessage(content=q)]}, + {"configurable": {"thread_id": f"preset-{i}"}}, + ) + print(result["messages"][-1].content) + print("\nEnter your own question (or 'exit'): ") + while True: + user_q = input("> ") + if user_q.lower() in {"exit", "quit"}: + break + result = await agent.ainvoke( + {"messages": [HumanMessage(content=user_q)]}, + {"configurable": {"thread_id": "interactive"}}, + ) + print(result["messages"][-1].content) + +if __name__ == "__main__": + asyncio.run(run_cli())