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

121 lines
3.7 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 langgraph.graph import StateGraph, START, END
from langgraph.graph.message import add_messages
from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage, AIMessage
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,
)
# ---------- 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
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 23 bullet points of critique."
)
response = await llm.ainvoke([HumanMessage(content=prompt)])
# Simple parsing: first line verdict, rest critique
lines = response.content.strip().splitlines()
verdict_line = lines[0].lower()
verdict = "ok" if "ok" in verdict_line else "needs_revision"
critique = "\n".join(lines[1:]) if len(lines) > 1 else ""
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
state['round'] += 1
return state
# ---------- Graph ----------
builder = StateGraph(ReflectState)
builder.add_node("draft_answer", draft_answer)
builder.add_node("reflect", reflect)
builder.add_node("rewrite", rewrite)
builder.set_entry_point("draft_answer")
# Transition logic
def should_rewrite(state: ReflectState) -> str:
if state['verdict'] == "ok":
return "END"
if state['round'] >= state['max_rounds']:
return "END"
return "rewrite"
builder.add_conditional_edges("reflect", should_rewrite, {
"rewrite": "rewrite",
"END": "END",
})
builder.add_edge("rewrite", "reflect")
graph = builder.compile()
# ---------- DeepAgent wrapper ----------
backend = CompositeBackend([
LocalShellBackend(workspace_dir="./workspace"),
FilesystemBackend(),
])
agent = create_deep_agent(
model=llm,
tools=[],
backend=backend,
system_prompt="You are a helper that runs a reflection graph.",
)
# ---------- CLI ----------
async def run_graph(question: str, max_rounds: int = 2):
initial_state: ReflectState = {
"question": question,
"draft": "",
"critique": "",
"verdict": "",
"round": 0,
"max_rounds": max_rounds,
}
result = await graph.ainvoke(initial_state)
return result
async def main():
question = "Объясни студенту разницу между tool и resource в MCP"
result = await run_graph(question)
print("\n--- Final Draft ---\n")
print(result["draft"])
print("\n--- Critique ---\n")
print(result["critique"])
print("\n--- Verdict ---\n")
print(result["verdict"])
if __name__ == "__main__":
asyncio.run(main())