Files
task-6a1d75d1fd30e81cf3126af8/main.py
T
2026-06-04 15:57:03 +00:00

126 lines
4.0 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 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 (510 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 (23 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 (510 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 selfcritique 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())