commit a91ef19a2c144f2bba93025cabca6de1c1b0d947 Author: Danil Parunin 5f1b81b8-4f5d-11e8-9c2d-fa7ae01bbebc Date: Mon Jun 15 12:15:07 2026 +0000 add: main.py diff --git a/main.py b/main.py new file mode 100644 index 0000000..8261a85 --- /dev/null +++ b/main.py @@ -0,0 +1,120 @@ +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 for deepagents ---------- +backend = CompositeBackend([ + LocalShellBackend(workspace_dir="./workspace"), + FilesystemBackend(), +]) + +# ---------- State definition ---------- +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() + return state + +async def reflect(state: ReflectState) -> ReflectState: + prompt = ( + f"You are a critic. Evaluate the following draft answer for completeness, specificity, and lack of filler.\n\nDraft: {state['draft']}\n\nProvide a verdict (ok or needs_revision) and 2–3 bullet points of critique." + ) + response = await llm.ainvoke([HumanMessage(content=prompt)]) + text = response.content.strip() + # Simple parsing: first line verdict, rest critique + lines = text.splitlines() + verdict_line = lines[0].lower() + verdict = "ok" if "ok" in verdict_line else "needs_revision" + critique = "\n".join(lines[1:]).strip() + state['verdict'] = verdict + state['critique'] = critique + return state + +async def rewrite(state: ReflectState) -> ReflectState: + prompt = ( + f"Rewrite the draft answer taking into account the following critique: {state['critique']}\n\nOriginal draft: {state['draft']}" + ) + response = await llm.ainvoke([HumanMessage(content=prompt)]) + state['draft'] = response.content.strip() + state['round'] += 1 + return state + +# ---------- Graph ---------- +async def run_graph(question: str, max_rounds: int = 2) -> str: + graph = StateGraph(ReflectState) + graph.add_node("draft_answer", draft_answer) + graph.add_node("reflect", reflect) + graph.add_node("rewrite", rewrite) + + graph.set_entry_point("draft_answer") + graph.add_edge("draft_answer", "reflect") + graph.add_conditional_edges( + "reflect", + lambda x: "rewrite" if x["verdict"] == "needs_revision" and x["round"] < x["max_rounds"] else "END", + ) + graph.add_edge("rewrite", "reflect") + + graph.add_edge("END", END) + + app = graph.compile() + initial_state: ReflectState = { + "question": question, + "draft": "", + "critique": "", + "verdict": "", + "round": 0, + "max_rounds": max_rounds, + } + final_state = await app.ainvoke(initial_state) + return final_state["draft"] + +# ---------- DeepAgent tool ---------- +@tool +async def answer_question(query: str) -> str: + """Generate a refined answer using self‑reflection graph.""" + return await run_graph(query) + +# ---------- DeepAgent ---------- +agent = create_deep_agent( + model=llm, + tools=[answer_question], + backend=backend, + system_prompt="You are an AI assistant that answers questions. Use the provided tool to generate answers.", +) + +# ---------- CLI ---------- +async def main(): + question = "Объясни студенту разницу между tool и resource в MCP" + result = await agent.ainvoke( + {"messages": [HumanMessage(content=question)]}, + {"configurable": {"thread_id": "session-1"}}, + ) + print("\nFinal answer:\n", result["messages"][-1].content) + +if __name__ == "__main__": + asyncio.run(main())