Files
task-6a1867fa8a94f887e50d52bd/main.py
T
2026-05-28 17:01:28 +00:00

174 lines
6.1 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.
"""
Main entry point for the "Planning Agent" assignment.
The project demonstrates a LangGraph agent that first plans a task into discrete steps and then executes those steps one by one.
Examples are provided in the ``__main__`` section run the script with different tasks to see how the planner works.
Requirements:
- langgraph>=0.2.0
- langchain-openai>=0.3.0
- python-dotenv>=1.0.0
- rich>=13.0.0
"""
from __future__ import annotations
import os
from typing import TypedDict, List, Optional
from langgraph.graph import StateGraph, START, END
from langchain_core.messages import HumanMessage, SystemMessage
from langchain_openai import ChatOpenAI
from rich.console import Console
from rich.table import Table
# ---------------------------------------------------------------------------
# 1. State definition
# ---------------------------------------------------------------------------
class PlanningState(TypedDict):
task: str
plan: List[str] | None
current_step: int
results: List[str]
# ---------------------------------------------------------------------------
# 2. 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.2,
)
# ---------------------------------------------------------------------------
# 3. Planning node split task into steps
# ---------------------------------------------------------------------------
def planning(state: PlanningState) -> PlanningState:
"""Ask the LLM to produce a numbered list of steps.
The prompt forces JSON output for reliable parsing.
"""
system = SystemMessage(
content="You are a helpful assistant that splits a task into clear, actionable steps. Return a JSON array of strings under the key `plan`."
)
user = HumanMessage(content=f"Plan the following task: {state['task']}")
response = llm.invoke([system, user])
text = response.content.strip()
# Try to parse JSON; if fails, fall back to simple split by lines.
try:
import json
data = json.loads(text)
plan: List[str] = data.get("plan", [])
except Exception:
# Fallback split on newlines and strip numbering
plan = []
for line in text.splitlines():
line = line.strip()
if not line:
continue
# Remove leading numbers like "1. " or "- "
if line[0].isdigit() and (len(line) > 2 and line[1] in ".-"):
line = line.split("", 1)[1]
plan.append(line)
return {
**state,
"plan": plan,
"current_step": 0,
"results": [],
}
# ---------------------------------------------------------------------------
# 4. Execution node perform one step (here we just echo the step)
# ---------------------------------------------------------------------------
def execution(state: PlanningState) -> PlanningState:
"""Execute a single step.
In a real assignment you would replace this with calls to tools or other logic.
For demonstration, we simply record the step text as the result.
"""
idx = state["current_step"]
if idx >= len(state["plan"]):
return state
step_text = state["plan"][idx]
# Simulate execution in practice you might call a tool here.
result = f"Executed: {step_text}"
new_results = state["results"].copy()
new_results.append(result)
return {
**state,
"current_step": idx + 1,
"results": new_results,
}
# ---------------------------------------------------------------------------
# 5. Decision node should we continue?
# ---------------------------------------------------------------------------
def should_continue(state: PlanningState) -> str:
if state["current_step"] >= len(state.get("plan", [])):
return "finish"
return "execute"
# ---------------------------------------------------------------------------
# 6. Build the graph
# ---------------------------------------------------------------------------
graph = StateGraph(PlanningState)
graph.add_node("planning", planning)
graph.add_node("execution", execution)
graph.add_conditional_edges(
"planning",
lambda _: "execute" if _.get("plan") else "finish",
)
graph.add_edge("execution", "should_continue")
graph.add_conditional_edges(
"should_continue",
should_continue,
{"execute": "execution", "finish": END},
)
agent = graph.compile()
# ---------------------------------------------------------------------------
# 7. Helper to run the agent and prettyprint results
# ---------------------------------------------------------------------------
def run_agent(task: str) -> None:
console = Console()
state: PlanningState = {"task": task, "plan": None, "current_step": 0, "results": []}
config = {"configurable": {"thread_id": f"{task[:8]}"}}
# Run the agent we capture intermediate states via a callback.
results: List[PlanningState] = []
for event in agent.stream(state, config):
if isinstance(event, dict) and "messages" in event:
continue # ignore final message
results.append(event)
# Print plan
console.print("\n[bold underline]Task:[/]", task)
if state.get("plan"):
table = Table(title="Plan", show_header=False, box=None)
for i, step in enumerate(state["plan"], 1):
table.add_row(f"{i}. {step}")
console.print(table)
# Print execution results
console.print("\n[bold underline]Execution Results:[/]")
for r in state.get("results", []):
console.print(r)
console.print("\n[green]Finished.[/]\n")
# ---------------------------------------------------------------------------
# 8. Demo examples
# ---------------------------------------------------------------------------
if __name__ == "__main__":
examples = [
"Compare Python and JavaScript in web development.",
"Plan a weekend trip to the mountains.",
"Explain how a neural network learns.",
]
for ex in examples:
run_agent(ex)