Files
task-6a1d75c5fd30e81cf3126ae7/main.py
T

143 lines
4.9 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
from pathlib import Path
from langchain_community.embeddings import OllamaEmbeddings
from langchain_chroma import Chroma
from langchain_core.documents import Document
from langchain_openai import ChatOpenAI
from langchain.tools import tool
from deepagents import create_deep_agent
from deepagents.backends import FilesystemBackend, LocalShellBackend, CompositeBackend
from langchain_core.messages import HumanMessage
# ---------------------------
# 1. Embeddings & Chroma setup
# ---------------------------
# Using OllamaEmbeddings with nomic-embed-text as required by the "Исправить" section.
# The embeddings are used for both loading the FAQ and for the search tool.
embeddings = OllamaEmbeddings(model="nomic-embed-text")
# Persistent Chroma collection for the FAQ knowledge base.
vector_store = Chroma(
collection_name="faq_collection",
embedding_function=embeddings,
persist_directory="./chroma_faq"
)
# ---------------------------
# 2. Load FAQ markdown files into Chroma
# ---------------------------
def load_faq_to_chroma(data_dir: str = "data"):
"""Load all .md files from *data_dir* into the persistent Chroma collection.
Each file is split into documents with a simple linebased splitter.
"""
data_path = Path(data_dir)
if not data_path.exists():
raise FileNotFoundError(f"Data directory {data_dir} not found")
docs = []
for md_file in data_path.glob("*.md"):
text = md_file.read_text(encoding="utf-8")
# Simple split by double newlines to create chunks
for i, chunk in enumerate(text.split("\n\n")):
docs.append(Document(page_content=chunk, metadata={"source": md_file.name, "chunk": i}))
vector_store.add_documents(docs)
vector_store.persist()
# ---------------------------
# 3. Tools
# ---------------------------
@tool
def search_course_docs(query: str, k: int = 3) -> str:
"""Search the FAQ knowledge base for relevant information."""
docs = vector_store.similarity_search(query, k=k)
if not docs:
return "No relevant information found in the FAQ."
return "\n\n---\n\n".join(f"**{doc.metadata.get('source')}** (chunk {doc.metadata.get('chunk')}):\n{doc.page_content}" for doc in docs)
@tool
def fetch_course_meta(query: str) -> str:
"""Mock MCPstyle tool that returns course metadata.
In production this would perform an HTTP GET to an MCP server.
Here we simply return a static JSON string based on the query.
"""
# Simple static mapping for demo purposes
meta = {
"schedule": "Monday 10:00-12:00, Wednesday 14:00-16:00",
"instructor": "Dr. Ivanov",
"credits": "3"
}
key = query.lower().strip()
return meta.get(key, f"No metadata found for '{query}'.")
# ---------------------------
# 4. Agent setup with deepagents
# ---------------------------
# LLM via OpenRouter as per course requirement
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 = CompositeBackend([
LocalShellBackend(workspace_dir="./workspace"),
FilesystemBackend(),
])
# System prompt instructs the agent to choose the appropriate tool and to label the source.
system_prompt = (
"You are a helpful FAQ assistant.\n"
"When a user asks a question about course materials, use the tool `search_course_docs`.\n"
"When a user asks about schedule, instructor, or credits, use the tool `fetch_course_meta`.\n"
"Do not call both tools unless necessary.\n"
"In your final answer, prepend the source label: `source: chroma` or `source: mcp_meta`."
)
agent = create_deep_agent(
model=llm,
tools=[search_course_docs, fetch_course_meta],
backend=backend,
system_prompt=system_prompt,
)
# ---------------------------
# 5. CLI
# ---------------------------
PRESET_QUESTIONS = [
"What topics are covered in the first lecture?",
"Who is the instructor for this course?",
"When is the next class?"
]
async def run_agent(question: str, thread_id: str = "session-1"):
result = await agent.ainvoke(
{"messages": [HumanMessage(content=question)]},
{"configurable": {"thread_id": thread_id}},
)
# The last message is the agent's response
return result["messages"][-1].content
async def main():
# Ensure FAQ is loaded
load_faq_to_chroma()
print("--- FAQ Bot Demo ---\n")
for i, q in enumerate(PRESET_QUESTIONS, 1):
print(f"Q{i}: {q}")
answer = await run_agent(q, thread_id=f"demo-{i}")
print(f"A{i}: {answer}\n")
# Interactive mode
print("Enter your own questions (type 'exit' to quit):")
while True:
user_input = input("> ")
if user_input.lower() in {"exit", "quit"}:
break
answer = await run_agent(user_input, thread_id="interactive")
print(answer)
if __name__ == "__main__":
asyncio.run(main())