From 0d45629e9b7e773bb3b30456120d058a4829a3ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=94=D0=B0=D0=BD=D0=B8=D0=B8=D0=BB=20=D0=92=D0=B8=D0=BA?= =?UTF-8?q?=D1=82=D0=BE=D1=80=D0=BE=D0=B2?= Date: Tue, 30 Jun 2026 16:10:00 +0000 Subject: [PATCH] =?UTF-8?q?add:=20main.py=20=E2=80=94=20=D0=9F=D0=BE=D0=B2?= =?UTF-8?q?=D1=82=D0=BE=D1=80=D0=BD=D1=8B=D0=B9=20=D1=8D=D0=BA=D0=B7=D0=B0?= =?UTF-8?q?=D0=BC=D0=B5=D0=BD:=20=D0=93=D1=80=D0=B0=D1=84=20=D1=81=20?= =?UTF-8?q?=D1=80=D0=B5=D1=84=D0=BB=D0=B5=D0=BA=D1=81=D0=B8=D0=B5=D0=B9=20?= =?UTF-8?q?=D0=B8=20=D0=B4=D0=BE=D1=80=D0=B0=D0=B1=D0=BE=D1=82=D0=BA=D0=BE?= =?UTF-8?q?=D0=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main.py | 129 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 129 insertions(+) create mode 100644 main.py diff --git a/main.py b/main.py new file mode 100644 index 0000000..059d39a --- /dev/null +++ b/main.py @@ -0,0 +1,129 @@ +import os +import asyncio +import json +from typing import TypedDict + +from langchain_openai import ChatOpenAI +from langchain_core.messages import HumanMessage +from langgraph.graph import StateGraph, START, END + +from deepagents import create_deep_agent, tool +from deepagents.backends import FilesystemBackend, LocalShellBackend, CompositeBackend + +# LLM configuration - OpenRouter +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, +) + +# State definition +class ReflectState(TypedDict): + question: str + draft: str + critique: str + verdict: str # ok | needs_revision + round: int + max_rounds: int + +# Node: draft_answer +def draft_answer(state: ReflectState) -> ReflectState: + prompt = f"Write a concise answer (5-10 sentences) to the following question:\n\n{state['question']}" + response = llm.invoke([HumanMessage(content=prompt)]) + state["draft"] = response.content.strip() + return state + +# Node: reflect +def reflect(state: ReflectState) -> ReflectState: + prompt = f"""You are a critic evaluating the following draft answer. Provide a verdict ('ok' or 'needs_revision') and 2-3 specific points of improvement. Do not provide the revised answer. Use JSON format: +{{ + "verdict": "ok" | "needs_revision", + "critique": "list of points" +}} +Draft: +{state['draft']}""" + response = llm.invoke([HumanMessage(content=prompt)]) + try: + data = json.loads(response.content) + 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 + +# Node: rewrite +def rewrite(state: ReflectState) -> ReflectState: + prompt = f"""You are revising the draft answer based on the following critique. Produce a revised answer (5-10 sentences). Do not include the critique. Use the critique points to improve clarity, specificity, and remove filler. Draft:\n{state['draft']}\nCritique:\n{state['critique']}""" + response = llm.invoke([HumanMessage(content=prompt)]) + state["draft"] = response.content.strip() + state["round"] = state.get("round", 0) + 1 + return state + +# Build the graph +def build_graph() -> StateGraph: + 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 s: "ok" if s["verdict"] == "ok" else ("rewrite" if s["round"] < s["max_rounds"] else "END"), + { + "ok": END, + "rewrite": "rewrite", + "END": END, + }, + ) + graph.add_edge("rewrite", "reflect") + return graph + +# Tool that runs the graph +def answer_question_tool(question: str, max_rounds: int = 2) -> str: + graph = build_graph() + initial_state: ReflectState = { + "question": question, + "draft": "", + "critique": "", + "verdict": "", + "round": 0, + "max_rounds": max_rounds, + } + final_state = graph.invoke(initial_state) + return final_state["draft"] + +# DeepAgent tool +@tool +def answer_question(query: str) -> str: + """Answer a question using a self-reflective process.""" + return answer_question_tool(query) + +# Backend for DeepAgent +backend = CompositeBackend( + [ + LocalShellBackend(workspace_dir="./workspace"), + FilesystemBackend(), + ] +) + +# Create the DeepAgent +agent = create_deep_agent( + model=llm, + tools=[answer_question], + backend=backend, + system_prompt="You are an assistant that answers questions using a self-reflective process. Use the tool 'answer_question' to answer the question.", +) + +# CLI demo +async def main(): + question = "Объясни студенту разницу между tool и resource в MCP" + result = await agent.ainvoke( + {"messages": [HumanMessage(content=question)]}, + {"configurable": {"thread_id": "session-1"}}, + ) + print(result["messages"][-1].content) + +if __name__ == "__main__": + asyncio.run(main()) \ No newline at end of file