commit 9a9f56c120fa42ce95a3a89a3289edb4b1fded25 Author: Глеб Никишин Date: Thu Jun 4 15:57:03 2026 +0000 add main.py diff --git a/main.py b/main.py new file mode 100644 index 0000000..de288d8 --- /dev/null +++ b/main.py @@ -0,0 +1,125 @@ +import os +import asyncio +from typing import TypedDict, Annotated + +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 langgraph.graph import StateGraph, START, END +from langgraph.graph.message import add_messages + +# ---------- 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(), +]) + +# ---------- State ---------- +class ReflectState(TypedDict): + question: str + draft: str + critique: str + verdict: str # ok | needs_revision + round: int + max_rounds: int + +# ---------- Nodes ---------- +async def draft_answer(state: ReflectState) -> ReflectState: + prompt = f"Write a concise answer (5–10 sentences) to the following question: {state['question']}" + response = await llm.ainvoke([HumanMessage(content=prompt)]) + state["draft"] = response.content.strip() + state["round"] = 0 + state["max_rounds"] = state.get("max_rounds", 2) + return state + +async def reflect(state: ReflectState) -> ReflectState: + prompt = ( + "You are a critic. Evaluate the following draft answer for completeness, specificity, and lack of filler.\n" + f"Draft: {state['draft']}\n" + "Return a JSON object with fields: verdict (ok or needs_revision) and critique (2–3 bullet points)." + ) + response = await llm.ainvoke([HumanMessage(content=prompt)]) + # Simple extraction of JSON + import json, re + try: + data = json.loads(re.search(r"\{.*\}", response.content, re.S).group(0)) + except Exception: + data = {"verdict": "needs_revision", "critique": "Could not parse critique."} + state["critique"] = data.get("critique", "") + state["verdict"] = data.get("verdict", "needs_revision") + return state + +async def rewrite(state: ReflectState) -> ReflectState: + prompt = ( + "Rewrite the draft answer incorporating the following critique. Keep the answer concise (5–10 sentences).\n" + f"Critique: {state['critique']}\n" + f"Original draft: {state['draft']}" + ) + response = await llm.ainvoke([HumanMessage(content=prompt)]) + state["draft"] = response.content.strip() + state["round"] += 1 + return state + +# ---------- Graph ---------- +graph = StateGraph(ReflectState) +graph.add_node("draft_answer", draft_answer) +graph.add_node("reflect", reflect) +graph.add_node("rewrite", rewrite) + +# Entry point +graph.set_entry_point("draft_answer") + +# Transitions +graph.add_edge("draft_answer", "reflect") +# If ok -> END +graph.add_conditional_edges( + "reflect", + lambda state: "rewrite" if state["verdict"] == "needs_revision" and state["round"] < state["max_rounds"] else "END", +) +# rewrite -> reflect +graph.add_edge("rewrite", "reflect") + +app = graph.compile() + +# ---------- DeepAgent wrapper ---------- +@tool +def run_graph(question: str) -> str: + """Run the reflection graph for a given question.""" + state: ReflectState = { + "question": question, + "draft": "", + "critique": "", + "verdict": "", + "round": 0, + "max_rounds": 2, + } + result = app.invoke(state) + return result["draft"] + +agent = create_deep_agent( + model=llm, + tools=[run_graph], + backend=backend, + system_prompt="You are a helpful agent that can answer questions and self‑critique using the provided tool.", +) + +async def main(): + question = "Объясни студенту разницу между tool и resource в MCP" + result = await agent.ainvoke( + {"messages": [HumanMessage(content=question)]}, + {"configurable": {"thread_id": "session-1"}}, + ) + print("Final answer:\n", result["messages"][-1].content) + +if __name__ == "__main__": + asyncio.run(main())