What you’ll build
A conversational AI agent that can answer natural language questions like:“What’s our EBIT margin in the Optimistic scenario compared to Committed?” “Which distribution centers are below 95% service level this quarter?” “How does switching to the Optimistic scenario affect our CO2 footprint?”The agent calls SIMCEL Insights API to fetch real data, then uses an LLM to reason over it.
Prerequisites
- Python 3.10+
- SIMCEL API key
- OpenAI API key
pip install openai requests python-dotenv
Step 1: Set up the SIMCEL client
Createsimcel_client.py:
import requests
import time
import os
class SimcelClient:
BASE_URL = "https://api.simcel.io/insights/v1"
AUTH_URL = "https://api.simcel.io/auth/token"
def __init__(self):
self.api_key = os.environ["SIMCEL_API_KEY"]
self._token = None
self._token_expiry = 0
def _get_token(self) -> str:
if self._token and time.time() < self._token_expiry - 60:
return self._token
resp = requests.post(self.AUTH_URL, json={"apiKey": self.api_key})
resp.raise_for_status()
data = resp.json()
self._token = data["accessToken"]
self._token_expiry = time.time() + data["expiresIn"]
return self._token
def get(self, path: str, **params) -> dict:
resp = requests.get(
f"{self.BASE_URL}{path}",
headers={"Authorization": f"Bearer {self._get_token()}"},
params=params,
)
resp.raise_for_status()
return resp.json()
Step 2: Define the agent tools
import json
client = SimcelClient()
# These are the "tools" we'll expose to the LLM
TOOLS = [
{
"type": "function",
"function": {
"name": "get_pnl_summary",
"description": "Get Profit & Loss summary (EBIT, DPM, Net Sales, COGS) for one or more scenarios",
"parameters": {
"type": "object",
"properties": {
"plan_id": {"type": "string", "description": "The plan ID"},
"scenario_ids": {
"type": "array",
"items": {"type": "string"},
"description": "List of scenario IDs (max 3)",
},
"interval": {
"type": "string",
"enum": ["M", "FY"],
"default": "FY",
},
},
"required": ["plan_id", "scenario_ids"],
},
},
},
{
"type": "function",
"function": {
"name": "get_supply_performance",
"description": "Get supply chain KPIs: closing stock, service level, dispatch volume per DC or region",
"parameters": {
"type": "object",
"properties": {
"plan_id": {"type": "string"},
"scenario_ids": {"type": "array", "items": {"type": "string"}},
"group_by": {
"type": "string",
"enum": ["dc", "sku", "region"],
"default": "dc",
},
"interval": {"type": "string", "enum": ["D", "W", "M", "FY"], "default": "M"},
},
"required": ["plan_id", "scenario_ids"],
},
},
},
{
"type": "function",
"function": {
"name": "get_sustainability",
"description": "Get CO2 footprint data (inbound, storage, outbound) per scenario",
"parameters": {
"type": "object",
"properties": {
"plan_id": {"type": "string"},
"scenario_ids": {"type": "array", "items": {"type": "string"}},
"group_by": {
"type": "string",
"enum": ["dc", "region", "transport_mode"],
"default": "dc",
},
},
"required": ["plan_id", "scenario_ids"],
},
},
},
]
def call_tool(name: str, args: dict) -> str:
"""Dispatch a tool call to the SIMCEL API and return JSON string."""
if name == "get_pnl_summary":
data = client.get(
"/pnl-summary",
planId=args["plan_id"],
scenarioIds=",".join(args["scenario_ids"]),
interval=args.get("interval", "FY"),
)
elif name == "get_supply_performance":
data = client.get(
"/supply-performance",
planId=args["plan_id"],
scenarioIds=",".join(args["scenario_ids"]),
groupBy=args.get("group_by", "dc"),
interval=args.get("interval", "M"),
)
elif name == "get_sustainability":
data = client.get(
"/sustainability",
planId=args["plan_id"],
scenarioIds=",".join(args["scenario_ids"]),
groupBy=args.get("group_by", "dc"),
)
else:
return json.dumps({"error": f"Unknown tool: {name}"})
return json.dumps(data)
Step 3: The agent loop
from openai import OpenAI
openai_client = OpenAI()
SYSTEM_PROMPT = """
You are a supply chain analyst AI assistant for SIMCEL.
You help users understand their IBP data by querying the SIMCEL Insights API.
The user's active plan ID is: {plan_id}
Available scenarios:
{scenarios_list}
Always compare scenarios when relevant. Present numbers clearly:
- Format monetary values with currency symbols and thousands separators
- Format ratios as percentages (0.484 → 48.4%)
- Highlight significant variances between scenarios
"""
def run_agent(user_question: str, plan_id: str, scenarios: list[dict]):
system = SYSTEM_PROMPT.format(
plan_id=plan_id,
scenarios_list="\n".join(
f" - {s['name']} (id: {s['id']}, flags: {s.get('flags', [])})"
for s in scenarios
),
)
messages = [
{"role": "system", "content": system},
{"role": "user", "content": user_question},
]
while True:
response = openai_client.chat.completions.create(
model="gpt-4o",
messages=messages,
tools=TOOLS,
tool_choice="auto",
)
msg = response.choices[0].message
messages.append(msg)
if not msg.tool_calls:
return msg.content
# Execute all tool calls
for tool_call in msg.tool_calls:
args = json.loads(tool_call.function.arguments)
result = call_tool(tool_call.function.name, args)
messages.append({
"role": "tool",
"tool_call_id": tool_call.id,
"content": result,
})
Step 4: Try it out
if __name__ == "__main__":
PLAN_ID = "plan_9xKz2"
SCENARIOS = [
{"id": "scen_abc", "name": "Committed", "flags": ["committed"]},
{"id": "scen_def", "name": "Optimistic"},
{"id": "scen_ghi", "name": "Actual", "flags": ["actual"]},
]
questions = [
"What's the EBIT difference between Committed and Optimistic scenarios?",
"Which DCs have service levels below 95% in the Committed plan?",
"How does the Optimistic scenario affect our total CO2 footprint vs Committed?",
]
for q in questions:
print(f"\n🤔 {q}")
print(f"🤖 {run_agent(q, PLAN_ID, SCENARIOS)}\n")
print("─" * 60)
Next steps
- Add the
get_demand_forecasttool for demand trend analysis - Connect the agent to Slack using Bolt for Python
- Store conversation history in a database for multi-turn sessions
- Add a
list_planstool so the agent can discover plan/scenario IDs dynamically

