was the UI for complicated price range planning, with an AI agent dealing with the optimisation behind the scenes?
If I may simply ship ‘Run 2026 price range with XXX caps and Y% on Sustainability’ and get a choice again, that will change our price range evaluation.
That’s how a logistics VP framed the issue after we mentioned automating portfolio choice for his or her annual CAPEX approval cycle.
Instance of CAPEX Utility – (Picture by Samir Saci)
As a result of firms put money into costly logistics gear, administrators obtain CAPEX functions from operations groups for brief and long-term initiatives.
This can be a complicated train as it’s essential to steadiness between return on funding and long-term technique.
As an information scientist, how can we assist this decision-making course of?
Utilizing linear programming, we may help determine which initiatives to fund to maximise ROI whereas respecting multi-year price range constraints.
On this article, we’ll construct an AI Agent for Price range Planning that turns electronic mail requests into an optimised CAPEX portfolio.
This AI workflow is constructed utilizing n8n for orchestration and LangGraph to create a reasoning agent related to a FastAPI microservice that executes a linear programming mannequin.
Goal of the Workflow – (Picture by Samir Saci)
We are going to evaluation the structure and discover the outcomes utilizing an actual request for price range optimisation with a constraint of a minimal 20% allocation for sustainability initiatives.
Price range Planning Optimisation with Python
Drawback Assertion: Operational Price range Planning
We’re supporting the APAC director of a giant Third-Celebration Logistics Service Supplier (3PL) based mostly in Singapore.
Their job is to handle warehousing and transportation operations for different firms in 4 nations within the Asia Pacific.
We’re speaking about operations supporting 48 clients grouped in over eight market verticals (Luxurious, Cosmetics …).
As an example, they handle a ten,000 sqm warehouse for a big fast-fashion retailer in Beijing, delivering to 50 shops throughout North China.
Warehouse Supervisor: We want 150k euros for a brand new conveyor belt that may enhance our receiving productiveness by 20%.
Our director receives an inventory of initiatives that require capital expenditure (CAPEX) from his 17 warehouse managers throughout the APAC area.
Examples of CAPEX Utility – (Picture by Samir Saci)
For every challenge, the CAPEX software features a temporary description (e.g., renting 500 sqm), a three-year value profile (Yr 1: €115k; Yr 2: €120k; Yr 3: €150k), and the anticipated Return On Funding (e.g., +€50k).
These initiatives may convey further advantages:
Enterprise Improvement: unlock new income (e.g., capability for a brand new buyer or product line)
Sustainability (CO₂ Discount): decrease emissions through energy-efficient gear or format modifications
Digital Transformation: enhance knowledge visibility, automate repetitive duties, and allow AI-driven selections
Operational Excellence: enhance throughput, scale back defects and rework, shorten changeovers, and stabilise processes.
HSE (Well being, Security & Setting): scale back incident threat and insurance coverage publicity with safer gear
CSR (Company Social Duty): strengthen neighborhood and workforce initiatives (e.g., coaching, accessibility)
As an example, a warehouse automation challenge can scale back packaging use (sustainability), lower operator pressure (CSR), and speed up digital transformation.
Among the further advantages are linked to the highest administration tips that our director must comply with.
APAC Director: “How ought to I allocate my price range of XX M€ to maximise the ROI whereas respecting my high administration tips?“
Because the director receives over 50 initiatives every session, we proposed to construct a linear programming module to find out the optimum choice, bearing in mind exterior constraints.
Goal of the answer – (Picture by Samir Saci)
As inputs, we get the obtainable price range and the administration goals, together with the spreadsheet of CAPEX functions.
Instance of CAPEX Utility Spreadsheet – (Picture by Samir Saci)
This will likely be a part of the inputs of our answer.
FastAPI Microservice: 0–1 Combined-Integer Optimiser for CAPEX Price range Planning
To handle this drawback, we utilised the modelling framework for Linear Programming (LP) and Integer Programming (IP) issues offered by the PuLP library of Python.
The answer is packaged in a FastAPI microservice deployed on the cloud.
Resolution Variables
For every challenge i, we outline a binary worth to tell if we allocate a price range or not:
Resolution Variables – (Picture by Samir Saci)
Goal Operate
The target is to maximise the whole return on funding of the portfolio of initiatives we chosen:
Goal Operate – (Picture by Samir Saci)
Constraints
As we can’t spend greater than what we’ve got allotted per yr, we should take into account the price range constraints for the subsequent three years.
Constraints – (Picture by Samir Saci)
APAC Director: Our CEO need us to speculate 20% of our price range on initiatives supporting our sustainability roadmap.
Furthermore, we may additionally have constraints on the minimal price range for particular administration goals.
Instance of Constraint for Sustainability – (Picture by Samir Saci)
Within the instance above, we make sure that the whole funding for sustainability initiatives is the same as or better than S_min.
Now that we’ve got outlined our mannequin, we will package deal it as a FastAPI microservice utilizing the code shared in this article.
This total workflow will settle for a selected schema outlined utilizing Pydantic for validation and defaults.
from pydantic import BaseModel
from typing import Elective
class LaunchParamsBudget(BaseModel):
budget_year1: int = 1250000
budget_year2: int = 1500000
budget_year3: int = 1750000
set_min_budget: bool = False
min_budget_objective: Elective[str] = 'Sustainability'
min_budget_perc: float = 20
class EmailRequest(BaseModel):
email_text: str
LaunchParamsBudget captures the optimisation inputs:
The three annual price range caps (budget_year1/2/3 in euros)
The elective minimum-allocation rule toggled by set_min_budget with its goal administration goal min_budget_objective and the required share min_budget_perc in %.
We have to make sure that the Agent respects this schema; if not, will probably be unable to question the API.
The agent will obtain the small print of the allotted price range, together with the annual break up, together with data on the chosen initiatives.
With these outcomes and a correct system immediate, our agent can present a concise abstract of the price range allocation to the VP of Logistics.
Constructing an Agentic Workflow with LangGraph and n8
Finish-to-Finish Workflow Description
Allow us to assume that our Asia Pacific Director acquired a spreadsheet with all of the CAPEX functions.
Instance of CAPEX Utility Spreadsheet – (Picture by Samir Saci)
The thought is to obtain the hypotheses relating to the price range quantity and constraints in plain English through electronic mail, together with the spreadsheet hooked up.
E-mail request with 205 on sustainability – (Picture by Samir Saci)
Primarily based on this electronic mail, the workflow ought to mechanically choose initiatives for optimum price range allocation.
Undertaking Chosen or not – (Picture by Samir Saci)
But additionally, the director expects a concise clarification of the alternatives made to construct this optimum “funding portfolio”.
Instance of Abstract Generated by the Agent – (Picture by Samir Saci)
We are able to construct an automatic workflow utilizing LangGraph and n8n for orchestration to carry out these duties.
Goal of the workflow – (Picture by Samir Saci)
This will likely be a workflow in 4 steps:
Step 1: Utilizing the n8n Gmail node, we ship the e-mail physique and the spreadsheet hooked up to the FastAPI Backend
Step 2: An AI Agent Parser will accumulate the parameters from the e-mail and name the opposite API for Price range Planning
Step 3: The outputs will likely be despatched to an AI Agent Summarizer that may use them to generate the abstract
Step 4: The consumer receives a reply by electronic mail with the abstract through a Gmail node in n8n
The core LangGraph agentic workflow will likely be applied inside a FastAPI microservice, which will likely be wrapped in an n8n workflow for orchestration.
n8n orchestration workflow – (Picture by Samir Saci)
The primary two nodes on the highest will extract the content material of the spreadsheet and add it to the API.
Then, we extract the e-mail content material within the Extract E-mail Physique node and ship it to the agent endpoint utilizing the node Question AI Agent.
The outputs of the API embrace:
An in depth clarification of the optimum price range allocation which can be despatched by electronic mail utilizing the Reply node
A JSON with the allocation by challenge utilized by the node Replace Allocation so as to add the ✅ and ❌ within the spreadsheet
In the end, our director receives a complete evaluation of the optimum portfolio, which incorporates three distinct sections.
Price range Abstract – (Picture by Samir Saci)
A Price range Abstract offers particulars on the allotted price range and the return on funding.
Portfolio Composition – (Picture by Samir Saci)
The Portfolio Composition particulars the variety of initiatives awarded per administration goal.
Recommandation – (Picture by Samir Saci)
Lastly, the agent concludes with a Advice based mostly on its understanding of the target.
Allow us to see how we constructed the core LangGraph Price range Agent, which parses an electronic mail to return a whole evaluation.
Price range Planning AI Agent with LangGraph
The code shared on this part has been tremendously simplified for concision.
Earlier than creating the graph, allow us to construct the totally different blocks:
EmailParser is used to parse the e-mail physique acquired from the n8n HTTP node to extract the parameters of the Price range API
BudgetPlanInterpreter will name the API, retrieve the outcomes and generate the abstract
To your data, I’ve used this strategy with standalone blocks as a result of we reuse them in different workflows that mix a number of brokers.
Block 1: AI Agent Parser
Allow us to construct the block for this agent that we’ll name AI Agent Parser:
We embrace the parameters that will likely be used for the Graph State variables.
This block EmailParser turns a plain-text electronic mail physique into typed, schema-valid parameters for our Price range Planning API utilizing an LLM.
On initialisation, we load the chat mannequin from the config and construct a LangChain chat mannequin.
The operate parse() takes the uncooked electronic mail content material despatched by n8n HTTP node and the system immediate to invoke the mannequin with structured outputs outlined with the LaunchParamsBudget Pydantic schema.
The EmailParser system_prompt saved within the YAML config file (minimal model for concision):
budget_agent:
system_prompt_parser: |
You're a price range planning analyst for LogiGreen.
Your activity is to extract structured enter parameters from emails
requesting price range optimization runs.
Fields to return (match precisely the schema):
- budget_year1: integer (annual cap for Yr 1)
- budget_year2: integer (annual cap for Yr 2)
- budget_year3: integer (annual cap for Yr 3)
- set_min_budget: boolean (true/false)
- min_budget_objective: string (e.g., "Sustainability")
- min_budget_perc: quantity (share between 0 and 100)
Output ONLY these fields; no further keys.
It features a checklist of fields to parse, together with concise explanations and strict format guidelines.
This will likely be despatched to the second agent for name tooling (our different FastAPI Microservice) and outcomes interpretation.
Block 2: Block for Software Calling and Interpretation
Two blocks will likely be used to name the Price range Planning API and to course of the API’s output.
As I will likely be utilizing this Price range Planning API for a number of brokers, I created a standalone operate to name it.
import os, logging, httpx
import logging
from app.fashions.budagent_models import LaunchParamsBudget
from app.utils.config_loader import load_config
logger = logging.getLogger(__name__)
API = os.getenv("API_URL")
LAUNCH = f"{API}/price range/launch_budget"
async def run_budget_api(params: LaunchParamsBudget,
session_id: str = "test_agent") -> dict:
payload = {
"goal": params.goal,
"budget_year1": params.budget_year1,
"budget_year2": params.budget_year2,
"budget_year3": params.budget_year3,
"set_min_budget": params.set_min_budget,
"min_budget_objective": params.min_budget_objective,
"min_budget_perc": params.min_budget_perc,
}
attempt:
async with httpx.AsyncClient(timeout=httpx.Timeout(5, learn=30)) as c:
r = await c.put up(LAUNCH,
json=payload,
headers={"session_id": session_id})
r.raise_for_status()
return r.json()
besides httpx.HTTPError as e:
logger.error("[BudgetAgent]: API name failed: %s", e)
code = getattr(e.response, "status_code", "")
return {"error": f"{code} {e}"}
We are able to now name this operate within the block BudgetPlanInterpreter outlined beneath.
import logging
import requests
from langchain.chat_models import init_chat_model
from app.utils.features.budagent_runner import run_budget_api
from app.fashions.budagent_models import LaunchParamsBudget
from app.utils.config_loader import load_config
logger = logging.getLogger(__name__)
config = load_config()
class BudgetPlanInterpreter:
def __init__(self,
model_name: str = "anthropic:claude-3-7-sonnet-latest",
session_id: str = "test_agent"):
self.llm = init_chat_model(model_name)
self.session_id = session_id
self.api_result: dict | None = None
self.html_summary: str | None = None
async def run_plan(self, params: dict) -> dict:
"""Run price range planning utilizing FastAPI Microservice API"""
attempt:
launch_params = LaunchParamsBudget(**params)
self.api_result = await run_budget_api(launch_params, self.session_id)
return self.api_result
besides Exception as e:
logger.error(f"[BudgetAgent] Direct API name failed: {e}")
return {"error": str(e)}
async def interpret(self, params: dict, api_output: dict | None = None) -> str:
"""Interpret API price range planning outcomes into HTML abstract for the Director"""
if api_output is None:
if not self.api_result:
elevate ValueError("No API outcome obtainable.")
api_output = self.api_result
messages = [
{
"role": "system",
"content": config["budget_agent"]["system_prompt_tool"]
},
{
"position": "consumer",
"content material": f"Enter parameters: {params}n
nModel outcomes: {api_output}"
}
]
reply = self.llm.invoke(messages)
self.html_summary = reply.content material
logger.information("[BudgetPlanAgent] Generated HTML abstract")
return self.html_summary
def get_summary(self) -> str:
if not self.html_summary:
elevate ValueError("No abstract obtainable.")
return self.html_summary
It runs the price range optimiser and turns its JSON into an “government abstract” in HTML format.
We use run_plan() to name the FastAPI microservice utilising the operate run_budget_api(s) to retrieve the outcomes
The operate interpret() generates the evaluation based mostly on the API’s outputs, following the directions of a system immediate.
We now have our three basis blocks that can be utilized to construct a graph with nodes and conditional edges.
LangGraph Builder with nodes and conditional edges
Now that we’ve got the three blocks, we will construct our graph.
LangGraph – (Picture by Samir Saci)
This can be a small experimental LangGraph state machine that features the three steps (parsing, working, interpretation) with error dealing with at each step.
from typing import TypedDict
from langgraph.graph import StateGraph, START, END
import logging
from app.utils.features.budagent_parser import EmailParser
from app.utils.features.budagent_functions import BudgetPlanInterpreter
from app.utils.config_loader import load_config
logger = logging.getLogger(__name__)
config = load_config()
class AgentState(TypedDict, whole=False):
email_text: str
params: dict
budget_results: dict
html_summary: str
error: str
session_id: str
This block is key because it defines the shared state handed between LangGraph nodes
session_id: included within the API name
email_text: uncooked electronic mail physique acquired from the n8n node
params: structured inputs parsed from the e-mail by EmailParser
budget_results: JSON output of the FastAPI microservice
html_summary: the evaluation (in HTML format) generated by interpret() based mostly on the output budget_results
error: storing an error message if wanted
We should always now outline the features of every node that may obtain the present AgentState and return a partial replace.
async def handle_error(state: AgentState) -> AgentState:
err = state.get("error", "Unknown error")
logger.error(f"[BudgetGraph] handle_error: {err}")
html = (
"<b>Price range Abstract</b><br>"
"<ul><li>There was an error whereas processing your request.</li></ul>"
f"<b>Particulars</b><br>{err}"
)
return {"html_summary": html}
Operate : handle_error(state)
Returns an error message in HTML format, which will likely be despatched to the Gmail node in n8n to inform the consumer
Instance of error message – (Picture by Samir Saci)
On this case, for the assist crew, it’s extra sensible because the consumer simply has to ahead the e-mail.
Word: Within the manufacturing model, we’ve got added a run_id to assist monitor the problems within the logs.
async def parse_email(state: AgentState) -> AgentState:
attempt:
parser = EmailParser(model_name=config["budget_agent"]["model_name"])
params = parser.parse(state["email_text"])
if not params or ("error" in params and params["error"]):
return {"error": f"Parse failed: {params.get('error', 'unknown')}"}
return {"params": params}
besides Exception as e:
logger.exception("[BudgetGraph] parse_email crashed")
return {"error": f"Parse exception: {e}"}
Operate 2: parse_email(state)
Makes use of EmailParser to rework the e-mail physique acquired into parameters for Price range Planning in JSON format
On success: returns {"params": …} used to name the FastAPI Microservice
async def run_budget(state: AgentState) -> AgentState:
if "error" in state:
return {}
attempt:
interpreter = BudgetPlanInterpreter(
model_name=config["budget_agent"]["model_name"],
session_id=state.get("session_id",
config["budget_agent"]["session_id"]))
outcomes = await interpreter.run_plan(state["params"])
if "error" in outcomes:
return {"error": f"Price range run failed: {outcomes['error']}"}
return {"budget_results": outcomes, "interpreter": interpreter}
besides Exception as e:
logger.exception("[BudgetGraph] run_budget crashed")
return {"error": f"Price range exception: {e}"}
Operate 3: run_budget(state)
Makes use of BudgetPlanInterpreter to name the operate run_plan that may execute the price range optimisation through the FastAPI microservice
On success: returns the output of the optimiser in JSON format as budget_results
This output can be utilized to generate the abstract of the price range allocation.
async def summarize(state: AgentState) -> AgentState:
if "error" in state:
return {}
attempt:
interpreter = state.get("interpreter") or BudgetPlanInterpreter(
model_name=config["budget_agent"]["model_name"],
session_id=state.get("session_id", config["budget_agent"]["session_id"]),
)
html = await interpreter.interpret(state["params"], state["budget_results"])
return {"html_summary": html}
besides Exception as e:
logger.exception("[BudgetGraph] summarize crashed")
return {"error": f"Summarization exception: {e}"}
Operate 4: summarize(state)
Reuses interpreter from state (or creates one), then calls interpret()
On success: returns a concise {and professional} abstract of the price range allocation in HTML format, able to be despatched by electronic mail {"html_summary": …}
This output html_summary is then returned by the API to the Gmail node on n8n to answer to the sender.
Now that we’ve got all of the features, we will create the nodes and “wire” the Graph utilizing the operate build_budagent_graph() outlined beneath:
route_after_parse will direct the circulation based mostly on the output of the e-mail parsing: if error in state → go to "error"; else → "run".
route_after_run will direct the circulation based mostly on the output of the FastAPI Microservice calling: if error in state → go to "error"; else → "summarize".
We’re almost achieved!
We simply have to package deal this in a FastAPI endpoint:
@router.put up("/graph_parse_and_run")
async def graph_parse_and_run(request: EmailRequest):
"""
Parse an electronic mail physique, run Price range Planning, and return an HTML abstract — orchestrated through a LangGraph StateGraph.
"""
attempt:
initial_state = {
"email_text": request.email_text,
"session_id": config.get("budget_agent", {}).get("session_id", "test_agent"),
}
final_state = await _graph.ainvoke(initial_state)
return {
"params": final_state.get("params"),
"budget_results": final_state.get("budget_results"),
"html_summary": final_state.get("html_summary"),
"error": final_state.get("error"),
}
besides Exception as e:
logger.exception("[BudAgent] Graph run failed")
elevate HTTPException(status_code=500, element=f"Graph run failed: {e}")
It is going to be queried by the Question Agent API node of our n8n workflow to return enter parameters in params, Price range Optimiser ends in budget_results and the abstract generated by the Agent Interpreter in html_summary.
A completely practical AI Workflow for Price range Planning
We are able to now activate the workflow on n8n and take a look at the device with totally different eventualities.
What if we don’t have a minimal price range for any administration goals?
I’ll attempt to adapt the e-mail to have set_min_budget at False.
Instance of electronic mail – (Picture by Saci)
The e-mail has been properly parsed with now set_min_budget on the worth False.
I’m nonetheless not happy with the contribution of the Agentic a part of the workflow.
Certainly, it’s good to have a device that may be triggered by an electronic mail and supply a concise abstract.
Nevertheless, I want to discover the concept of getting a number of brokers proposing totally different eventualities that will compete in opposition to one another.
What can be the impression on the ROI if we enhance the minimal price range of sustainability by 15%?
As an example, we will ask brokers to run a number of eventualities and supply a comparative examine.
We’re nonetheless experimenting with varied sorts of orchestration to find out essentially the most environment friendly strategy.
This would be the subject of the next articles.
Different examples of Agentic Workflows?
This isn’t the primary time I’m making an attempt to hyperlink an optimisation device (packaged in a FastAPI Microservice) with an Agentic Workflow.
My preliminary try was to create a Manufacturing Planning Optimisation Agent.
Manufacturing Planning Agent – (Picture by Samir Saci)
Like right here, I packaged an optimisation algorithm in a FastAPI Microservice that I wished to hook up with an electronic mail workflow.
Manufacturing Planning FastAPI Microservices – (Picture by Samir Saci)
Not like right here, the Agentic a part of the workflow was dealt with in n8n with two Agent nodes.
Workflow – (Picture by Samir Saci)
The outcomes had been fairly satisfying, as you may see within the brief video linked beneath.
The consumer expertise was very clean.
Nevertheless, the upkeep proved to be difficult for the crew.
That is why we wished to discover using Python and TypeScript frameworks for the agentic workflow, like right here.
What’s subsequent? Agentic Strategy of Enterprise Planning
At our startup, LogiGreen, we are trying (by experiments like this one) to increase past provide chain optimisation and embody enterprise decision-making.
On my roadmap, I’ve a device that I’ve developed to assist small and medium-sized firm optimise their money circulation.
Worth Chain of the enterprise mannequin of my pal – (Picture by Samir Saci)
In another article printed in In direction of Information Science, I’ve launched how I used Python to simulate the monetary flows of an organization promoting renewable paper cups to espresso outlets.
“We have now to refuse orders as we don’t have sufficient money to pay suppliers for inventory replenishment.”
A detailed pal, who owns a small enterprise, was complaining about money circulation points limiting the event of his firm.
I began by tackling the issue of stock administration with an optimised built-in answer utilizing Python.
Stock Administration Rule – (Picture by Samir Saci)
Then I enriched the mannequin contemplating gross sales channel technique, cost phrases and lots of different strategic enterprise parameters to assist him discover the optimum marketing strategy to maximise profitability.
Instance of eventualities generated – (Picture by Samir Saci)
This answer might be the subsequent candidate in our experimentation with utilizing agentic workflows to assist enterprise and operational decision-making.
For extra data, you may take a look at this brief presentation of the device
Presently, it’s packaged in a FastAPI microservice related to a React frontend (and streamlit for the general public demo).
UI of the device obtainable in LogiGreen Apps – (Picture by Samir Saci)
I want to implement an AI workflow that will
Take a number of eventualities (like those offered within the video)
Name the API for every situation
Accumulate and course of the outcomes
Present a comparative evaluation to suggest the very best choice
Mainly, I want to outsource to a single (or a number of brokers) the entire examine offered within the article and the video.
For that, we’re exploring a number of approaches to agent orchestration.
We are going to share our findings in a future article. Keep tuned!
About Me
Let’s join on Linkedin and Twitter. I’m a Provide Chain Engineer who makes use of knowledge analytics to enhance logistics operations and scale back prices.
For consulting or recommendation on analytics and sustainable provide chain transformation, be at liberty to contact me through Logigreen Consulting.
In case you are involved in Information Analytics and Provide Chain, have a look at my web site.