Add main.py

This commit is contained in:
+77 -93
View File
@@ -1,109 +1,96 @@
"""
# main.py
# LangGraph agent with reflection and rewrite loop
# Author: ChatGPT
# Requirements: langgraph, langchain-openai, deepagents
import os import os
import asyncio from typing import TypedDict, Dict, Any
from typing import TypedDict from langgraph.graph import StateGraph, END
from langchain_openai import ChatOpenAI from langchain_openai import ChatOpenAI
from langgraph.graph import StateGraph, START, END
from deepagents import create_deep_agent
from deepagents.backends import CompositeBackend, LocalShellBackend
# LLM setup
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 = CompositeBackend(
default=LocalShellBackend(root_dir="./workspace", virtual_mode=True, inherit_env=True),
routes={},
)
agent = create_deep_agent(
model=llm,
tools=[],
backend=backend,
system_prompt="You are a helpful assistant.",
)
# 1. State definition
class ReflectState(TypedDict): class ReflectState(TypedDict):
question: str question: str
draft: str draft: str
critique: str critique: str
verdict: str verdict: str # "ok" | "needs_revision"
round: int round: int
max_rounds: int max_rounds: int
async def draft_answer(state: ReflectState) -> ReflectState: # 2. LLM instance
prompt = f"Write a concise answer (510 sentences) to the following question:\n\n{state['question']}" llm = ChatOpenAI(model="gpt-4o-mini", temperature=0.2)
response = await agent.ainvoke({"messages": ["Human: " + prompt]}, {"configurable": {"thread_id": "draft"}})
draft = response["messages"][-1].content
state["draft"] = draft
return state
async def reflect(state: ReflectState) -> ReflectState: # 3. Nodes
def draft_answer(state: ReflectState) -> Dict[str, Any]:
prompt = ( prompt = (
"You are a critical reviewer.\n" "Write a concise answer (510 sentences) to the following question:\n"
"Evaluate the following draft answer for completeness, specificity, and lack of filler.\n" f"Question: {state['question']}\n"
"Provide a verdict: 'ok' if the answer is satisfactory, otherwise 'needs_revision'.\n" "Answer:"
"If revision is needed, give 23 concrete points for improvement.\n"
"Respond in JSON with keys 'verdict' and 'critique'.\n"
f"Draft: {state['draft']}"
) )
response = await agent.ainvoke({"messages": ["Human: " + prompt]}, {"configurable": {"thread_id": "reflect"}}) response = llm.invoke(prompt)
import json state["draft"] = response.content.strip()
try: return {"draft": state["draft"]}
data = json.loads(response["messages"][-1].content)
verdict = data.get("verdict", "needs_revision")
critique = data.get("critique", "") def reflect(state: ReflectState) -> Dict[str, Any]:
except Exception: prompt = (
verdict = "needs_revision" "You are a critical reviewer of the draft answer.\n"
critique = "Could not parse critique." "Evaluate the draft for completeness, specificity, and lack of filler.\n"
"Provide a verdict: 'ok' if the answer is satisfactory, otherwise 'needs_revision'.\n"
"If revision is needed, give 23 concise points for improvement.\n"
f"Draft: {state['draft']}\n"
"Verdict and critique:"
)
response = llm.invoke(prompt)
text = response.content.strip()
lines = text.splitlines()
verdict_line = lines[0].lower().strip()
verdict = "ok" if "ok" in verdict_line else "needs_revision"
critique = "\n".join(lines[1:]).strip()
state["verdict"] = verdict state["verdict"] = verdict
state["critique"] = critique state["critique"] = critique
return state return {"verdict": verdict, "critique": critique}
async def rewrite(state: ReflectState) -> ReflectState:
def rewrite(state: ReflectState) -> Dict[str, Any]:
prompt = ( prompt = (
"You are revising the following draft answer based on the critique.\n" "Rewrite the draft answer incorporating the following critique points.\n"
"Make the answer clearer, more specific, and remove any filler.\n" "Keep the answer concise (510 sentences).\n"
"Do not add new information beyond what is already in the draft.\n" f"Critique: {state['critique']}\n"
f"Draft: {state['draft']}\n" f"Original Draft: {state['draft']}\n"
f"Critique: {state['critique']}" "Revised Answer:"
) )
response = await agent.ainvoke({"messages": ["Human: " + prompt]}, {"configurable": {"thread_id": "rewrite"}}) response = llm.invoke(prompt)
new_draft = response["messages"][-1].content state["draft"] = response.content.strip()
state["draft"] = new_draft
state["round"] += 1 state["round"] += 1
return {"draft": state["draft"], "round": state["round"]}
# 4. Graph construction
builder = StateGraph(ReflectState)
builder.add_node("draft_answer", draft_answer)
builder.add_node("reflect", reflect)
builder.add_node("rewrite", rewrite)
# Edges
builder.set_entry_point("draft_answer")
builder.add_edge("draft_answer", "reflect")
builder.add_conditional_edges(
"reflect",
lambda x: x["verdict"],
{
"ok": END,
"needs_revision": "rewrite"
}
)
builder.add_edge("rewrite", "reflect")
# Max rounds guard
@builder.before_node("rewrite")
def check_rounds(state: ReflectState) -> ReflectState:
if state["round"] >= state["max_rounds"]:
state["verdict"] = "ok"
return state return state
graph = builder.compile()
def build_graph() -> StateGraph[ReflectState]: # 5. Demo execution
graph = StateGraph(ReflectState) if __name__ == "__main__":
graph.add_node("draft_answer", draft_answer)
graph.add_node("reflect", reflect)
graph.add_node("rewrite", rewrite)
graph.add_edge(START, "draft_answer")
graph.add_edge("draft_answer", "reflect")
graph.add_conditional_edges(
"reflect",
lambda state: state["verdict"],
{"ok": END, "needs_revision": "rewrite"},
)
graph.add_conditional_edges(
"rewrite",
lambda state: "rewrite" if state["round"] < state["max_rounds"] else END,
{"rewrite": "reflect", END: END},
)
return graph
async def main():
question = "Объясни студенту разницу между tool и resource в MCP" question = "Объясни студенту разницу между tool и resource в MCP"
initial_state: ReflectState = { initial_state: ReflectState = {
"question": question, "question": question,
@@ -111,15 +98,12 @@ async def main():
"critique": "", "critique": "",
"verdict": "", "verdict": "",
"round": 0, "round": 0,
"max_rounds": 2, "max_rounds": 2
} }
graph = build_graph() result = graph.invoke(initial_state)
result = await graph.ainvoke(initial_state) print("\n--- Final Draft ---\n")
print("\n--- Final Answer ---")
print(result["draft"]) print(result["draft"])
print("\n--- Final Critique ---") print("\n--- Critique ---\n")
print(result["critique"]) print(result["critique"])
print("\n--- Verdict ---\n")
if __name__ == "__main__": print(result["verdict"])
asyncio.run(main())
"""