""" Main entry point for the Planning Agent assignment. The program demonstrates a LangGraph agent that: 1. Plans a task into discrete steps using an LLM. 2. Executes each step sequentially, collecting results. 3. Returns a final summary of all results. Three example tasks are executed when run as a script. """ import os from typing import TypedDict, List from langgraph.graph import StateGraph, START, END from langchain_openai import ChatOpenAI from langchain_core.messages import HumanMessage, SystemMessage from rich.console import Console # --------------------------------------------------------------------------- # 1. State definition # --------------------------------------------------------------------------- class PlanningState(TypedDict): task: str plan: List[str] | None current_step: int results: List[str] # --------------------------------------------------------------------------- # 2. LLM configuration – BroJS provider # --------------------------------------------------------------------------- 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 the task into steps # --------------------------------------------------------------------------- def planning(state: PlanningState) -> PlanningState: """Ask LLM to produce a numbered list of steps. The prompt asks for JSON output with a single key ``steps`` containing an array of strings. This guarantees deterministic parsing. """ task = state["task"] system_prompt = ( "You are a helpful assistant that breaks down a user request into a list of concrete, actionable steps." ) user_prompt = ( f"Task: {task}\n\nReturn a JSON object with a single key 'steps' containing an array of strings. Each string should be a concise step. Provide between 3 and 6 steps.") response = llm.invoke([SystemMessage(content=system_prompt), HumanMessage(content=user_prompt)]) # Parse JSON safely import json, re try: data = json.loads(response.content) steps = data.get("steps", []) except Exception: # Fallback: extract numbered list via regex pattern = r"\d+\.\s*(.+)" steps = [m.group(1).strip() for m in re.finditer(pattern, response.content)] return { "task": task, "plan": steps, "current_step": 0, "results": [], } # --------------------------------------------------------------------------- # 4. Execution node – run one step and record result # --------------------------------------------------------------------------- def execution(state: PlanningState) -> PlanningState: idx = state["current_step"] plan = state["plan"] or [] if idx >= len(plan): return state step_text = plan[idx] # For demonstration, we simply echo the step as result. # In a real scenario this could invoke tools or perform computation. result = f"Result of step {idx+1}: {step_text}" new_results = state["results"] + [result] return { "task": state["task"], "plan": plan, "current_step": idx + 1, "results": new_results, } # --------------------------------------------------------------------------- # 5. Decision node – continue or finish # --------------------------------------------------------------------------- def should_continue(state: PlanningState) -> str: if state["current_step"] >= len(state.get("plan", [])): return "finish" return "execute" # --------------------------------------------------------------------------- # 6. Build graph # --------------------------------------------------------------------------- graph = StateGraph(PlanningState) graph.add_node("planning", planning) graph.add_node("execution", execution) graph.add_conditional_edges( "planning", lambda _: "execute" if _["plan"] else "finish", ) graph.add_edge("execution", "should_continue") graph.add_conditional_edges( "should_continue", should_continue, {"execute": "execution", "finish": END}, ) # Start from planning graph.set_entry_point("planning") agent = graph.compile() # --------------------------------------------------------------------------- # 7. Helper to run a task and print results # --------------------------------------------------------------------------- def run_task(task: str) -> None: console = Console() console.print(f"\n[bold cyan]Running task:[/bold cyan] {task}") result_state = agent.invoke({"task": task}) plan = result_state.get("plan", []) results = result_state.get("results", []) console.print("\n[green]Plan:\n[/green]") for i, step in enumerate(plan, 1): console.print(f"{i}. {step}") console.print("\n[blue]Execution results:\n[/blue]") for r in results: console.print(r) console.print("\n[bold magenta]Final summary:[/bold magenta]\n") # Final LLM summarization summary_prompt = ( "You have executed the following steps: \n" + "\n".join(results) + "\nProvide a concise final answer.") summary_response = llm.invoke([HumanMessage(content=summary_prompt)]) console.print(summary_response.content) # --------------------------------------------------------------------------- # 8. Main – three example tasks # --------------------------------------------------------------------------- if __name__ == "__main__": examples = [ "Compare Python and JavaScript in terms of performance, syntax simplicity, and ecosystem support.", "Explain how to set up a basic Flask application with a single route.", "Outline the steps required to deploy a Dockerized Node.js app to AWS Elastic Beanstalk.", ] for ex in examples: run_task(ex) """