add main.py

This commit is contained in:
2026-06-04 15:57:56 +00:00
parent 4e2195b36b
commit 1288fa770e
+133
View File
@@ -0,0 +1,133 @@
"""
LangGraph Reflective Agent
=========================
This repository contains a small demo of a LangGraph agent that:
* Generates an initial answer to a question.
* Critiques the answer using a separate node.
* If the critique indicates "needs_revision", rewrites the answer up to ``max_rounds`` times.
The implementation follows the specification from the assignment and is fully runnable with
``pip install -r requirements.txt``.
"""
import os
from typing import TypedDict, Annotated
from langchain_openai import ChatOpenAI
from langgraph.graph import StateGraph, END
from langgraph.checkpoint.memory import MemorySaver
from dotenv import load_dotenv
# Load environment variables (JOURNAL_MCP_PAT must be set)
load_dotenv()
# LLM configuration BroJS endpoint
llm = ChatOpenAI(
model="openai/gpt-oss-20b:free",
base_url="https://platform.brojs.ru/jrnl-bh/api/inference/v1",
api_key=os.getenv("JOURNAL_MCP_PAT"),
temperature=0.0,
)
# ---------- State definition --------------------------------------------
class ReflectState(TypedDict):
question: str
draft: str
critique: str
verdict: str # "ok" or "needs_revision"
round: int
max_rounds: int
# ---------- Node implementations ---------------------------------------
async def draft_answer(state: ReflectState) -> dict:
"""Generate a concise answer (510 sentences)."""
prompt = (
f"Write a short answer (5-10 sentences) to the following question:\n\n{state['question']}"
)
response = await llm.ainvoke([{"role": "user", "content": prompt}])
state["draft"] = response.content.strip()
return {"draft": state["draft"]}
async def reflect(state: ReflectState) -> dict:
"""Critique the draft and decide if revision is needed."""
critique_prompt = (
f"You are a critical reviewer. Evaluate the following answer for completeness, specificity, and lack of filler.\n\nAnswer:\n{state['draft']}\n\nProvide verdict (ok / needs_revision) followed by 2-3 bullet points of feedback."
)
response = await llm.ainvoke([{"role": "user", "content": critique_prompt}])
# Parse verdict and critique
text = response.content.strip()
if "needs_revision" in text.lower():
state["verdict"] = "needs_revision"
else:
state["verdict"] = "ok"
state["critique"] = text
return {"critique": state["critique"], "verdict": state["verdict"]}
async def rewrite(state: ReflectState) -> dict:
"""Rewrite the draft incorporating critique feedback."""
rewrite_prompt = (
f"You are revising an answer based on the following critique. Update the answer to improve it, keeping it concise (5-10 sentences).\n\nCritique:\n{state['critique']}\n\nOriginal Answer:\n{state['draft']}"
)
response = await llm.ainvoke([{"role": "user", "content": rewrite_prompt}])
state["draft"] = response.content.strip()
state["round"] += 1
return {"draft": state["draft"], "round": state["round"]}
# ---------- Graph construction ------------------------------------------
builder = StateGraph(ReflectState)
builder.add_node("draft_answer", draft_answer)
builder.add_node("reflect", reflect)
builder.add_node("rewrite", rewrite)
# Define transitions
builder.set_entry_point("draft_answer")
builder.add_edge("draft_answer", "reflect")
# From reflect: if ok -> END, else if needs_revision and round < max_rounds -> rewrite
builder.add_conditional_edges(
"reflect",
lambda x: x["verdict"] == "ok",
{"ok": END},
)
builder.add_conditional_edges(
"reflect",
lambda x: x["verdict"] == "needs_revision" and x["round"] < x["max_rounds"],
{"needs_revision": "rewrite"},
)
# If needs_revision but round >= max_rounds -> END
builder.add_edge("reflect", END, condition=lambda _: True) # fallback
# Add rewrite to reflect loop
builder.add_edge("rewrite", "reflect")
graph = builder.compile(checkpointer=MemorySaver())
# ---------- Demo execution ----------------------------------------------
async def run_demo(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)
# Extract final answer
final_answer = result.get("draft", "")
print("\n=== Final Answer ===")
print(final_answer)
return final_answer
if __name__ == "__main__":
import asyncio
examples = [
"Explain the difference between a tool and a resource in MCP.",
"What is the capital of France?",
"Describe how to set up a virtual environment in Python 3.10.",
]
for q in examples:
print("\nQuestion:", q)
asyncio.run(run_demo(q))
"""