Files
task-6a1d75d1fd30e81cf3126af8/main.py
T
2026-06-04 16:37:08 +00:00

147 lines
4.5 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 CompositeBackend, LocalShellBackend, FilesystemBackend
# ---------- 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 ----------
from langgraph.graph import StateGraph, START, END
from langgraph.graph.message import add_messages
# Helper to format the prompt for each node
DRAFT_PROMPT = """Write a concise answer (510 sentences) to the following question:
{question}
"""
REFLECT_PROMPT = """You are a critic. Given the draft answer below, evaluate its completeness, specificity, and absence of filler. Respond with:
1. verdict: either "ok" or "needs_revision"
2. critique: 23 bullet points explaining what to improve (if any)
Draft:
{draft}
"""
REWRITE_PROMPT = """You are revising the draft answer based on the critique. Produce a new draft that addresses the points. Keep the answer concise (510 sentences).
Critique:
{critique}
Previous draft:
{draft}
"""
# Node functions
async def draft_answer(state: ReflectState) -> ReflectState:
response = await llm.ainvoke([HumanMessage(content=DRAFT_PROMPT.format(question=state["question"]))])
state["draft"] = response.content.strip()
return state
async def reflect(state: ReflectState) -> ReflectState:
response = await llm.ainvoke([HumanMessage(content=REFLECT_PROMPT.format(draft=state["draft"]))])
# Parse verdict and critique
text = response.content.strip()
verdict_line = next((l for l in text.splitlines() if l.lower().startswith("verdict:")), "")
critique_lines = [l for l in text.splitlines() if l.startswith("-") or l.startswith("")]
verdict = verdict_line.split(":",1)[1].strip().lower() if verdict_line else "needs_revision"
critique = "\n".join(critique_lines) if critique_lines else ""
state["verdict"] = verdict
state["critique"] = critique
return state
async def rewrite(state: ReflectState) -> ReflectState:
response = await llm.ainvoke([HumanMessage(content=REWRITE_PROMPT.format(critique=state["critique"], draft=state["draft"]))])
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
# After draft -> reflect
graph.add_edge("draft_answer", "reflect")
# After reflect
# if ok -> END
# if needs_revision and round < max_rounds -> rewrite
# else -> END
def reflect_conditional(state: ReflectState):
if state["verdict"] == "ok":
return "END"
if state["round"] < state["max_rounds"]:
return "rewrite"
return "END"
graph.add_conditional_edges("reflect", reflect_conditional, {"rewrite": "rewrite", "END": "END"})
# After rewrite -> reflect
graph.add_edge("rewrite", "reflect")
graph.compile()
# ---------- DeepAgent wrapper ----------
@tool
def run_reflect_graph(question: str, max_rounds: int = 2) -> str:
"""Run the reflection graph and return the final draft."""
initial_state: ReflectState = {
"question": question,
"draft": "",
"critique": "",
"verdict": "",
"round": 0,
"max_rounds": max_rounds,
}
result = graph.invoke(initial_state)
return result["draft"]
agent = create_deep_agent(
model=llm,
tools=[run_reflect_graph],
backend=backend,
system_prompt="You are an assistant that can answer questions and selfcritique using the provided tool.",
)
async def main():
question = "Объясни студенту разницу между tool и resource в MCP."
response = await agent.ainvoke(
{"messages": [HumanMessage(content=f"Please answer: {question}")]},
{"configurable": {"thread_id": "session-1"}},
)
print("Final answer:\n", response["messages"][-1].content)
if __name__ == "__main__":
asyncio.run(main())