Files
task-6a1d75c5fd30e81cf3126ae7/main.py
T
2026-06-04 16:25:00 +00:00

124 lines
4.4 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import os
import asyncio
import json
import httpx
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
# --------------------- Configuration ---------------------
BASE_DIR = Path(__file__).parent
DATA_DIR = BASE_DIR / "data"
CHROMA_DIR = BASE_DIR / "chroma_faq"
META_JSON = BASE_DIR / "course_meta.json"
# --------------------- LLM & 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"),
)
# --------------------- Chroma DB ---------------------
vector_store = Chroma(
collection_name="faq_collection",
embedding_function=embeddings,
persist_directory=str(CHROMA_DIR),
)
# Load markdown files into Chroma if not already loaded
if not CHROMA_DIR.exists() or not any(CHROMA_DIR.iterdir()):
docs = []
for md_file in DATA_DIR.glob("*.md"):
text = md_file.read_text(encoding="utf-8")
docs.append(Document(page_content=text, metadata={"source": md_file.name}))
vector_store.add_documents(docs)
vector_store.persist()
# --------------------- Tools ---------------------
@tool
def search_course_docs(query: str) -> str:
"""Search the FAQ knowledge base for relevant information."""
results = vector_store.similarity_search(query, k=3)
if not results:
return "No relevant information found in the course materials."
return "\n---\n".join(f"{doc.metadata.get('source', 'unknown')}\n{doc.page_content}" for doc in results)
@tool
def fetch_course_meta(query: str) -> str:
"""Fetch course metadata (e.g., schedule) from a local JSON mock."""
if not META_JSON.exists():
return "Metadata file not found."
data = json.loads(META_JSON.read_text(encoding="utf-8"))
# Simple keyword search in the metadata
matches = [f"{k}: {v}" for k, v in data.items() if query.lower() in k.lower() or query.lower() in str(v).lower()]
return "\n".join(matches) if matches else "No metadata matches the query."
# --------------------- Backend ---------------------
backend = CompositeBackend([
LocalShellBackend(workspace_dir="./workspace"),
FilesystemBackend(),
])
# --------------------- Agent ---------------------
agent = create_deep_agent(
model=llm,
tools=[search_course_docs, fetch_course_meta],
backend=backend,
system_prompt=(
"You are a helpful FAQ assistant for the course.\n"
"When a user asks about course content, use the search_course_docs tool.\n"
"When a user asks about schedule, metadata, or other noncontent info, use fetch_course_meta.\n"
"Do not use both tools unless absolutely necessary.\n"
"In your final answer, prefix the source with 'source: chroma' or 'source: mcp_meta'."
),
)
# --------------------- CLI ---------------------
SAMPLE_QUESTIONS = [
"What topics are covered in the first lecture?", # should hit chroma
"Explain the concept of tokenization in NLP.", # chroma
"When is the next class scheduled?", # meta
]
async def run_interactive():
print("Welcome to the Course FAQ Bot! Type 'exit' to quit.")
while True:
user_input = input("\nYou: ")
if user_input.lower() in {"exit", "quit"}:
break
result = await agent.ainvoke(
{"messages": [{"role": "user", "content": user_input}]},
{"configurable": {"thread_id": "interactive-session"}},
)
print("\nAssistant:", result["messages"][-1]["content"])
async def run_samples():
for q in SAMPLE_QUESTIONS:
print("\nQuestion:", q)
result = await agent.ainvoke(
{"messages": [{"role": "user", "content": q}]},
{"configurable": {"thread_id": "sample-session"}},
)
print("Answer:", result["messages"][-1]["content"])
async def main():
# Run sample questions first
await run_samples()
# Then enter interactive mode
await run_interactive()
if __name__ == "__main__":
asyncio.run(main())