This repository is the "killer demo" for the Lár Engine — an open-source, "glass box" framework for building auditable, multi-agent AI systems.
This app is a live, interactive multi-agent customer support bot. It's not one "mega-agent" trying to do everything. It's a team of specialized agents, and you can watch them work together like a transparent "assembly line."
You will see the agent:
-
Triage your question (e.g., "Billing" or "Tech Support").
-
Plan a search query for its knowledge base.
-
Retrieve the correct facts.
-
Route the facts to the correct "Specialist" agent.
-
Write a final, auditable answer.
The "magic" of AI agents isn't magic at all—it's just a graph. Other frameworks try to sell you a "magic" AgentExecutor that does everything at once. It's a "black box," and when it breaks, it's impossible to fix.
Lár is the opposite. It's a simple, "dumb" GraphExecutor loop. It runs one node, stops, and logs what happened.
This is the "glass box" advantage: the "intelligence" doesn't live in the engine; it lives in your nodes. This makes your agents 100% auditable and deterministic.
How does lar "know" whether to use a Tech Support or a Billing agent? It doesn't. You tell it how to decide, in two simple, auditable steps:
-
Step 1: The "Triage" (
LLMNode) AnLLMNoderuns with a specific prompt. Its only job is to classify the user's task and create a label (e.g.,"TECH_SUPPORT"). It writes this label to theGraphState -
Step 2: The "Manager" (
RouterNode) ARouterNode(which is pure, "dumb" Python) reads that label from theGraphState. It then uses a simpleifstatement or adictlookup to choose which "specialist" node to run next.
| Other Frameworks (“Black Box”) | Lár (“Glass Box”) |
|---|---|
| "Chaotic Chat Room" model. Agents talk to each other and you hope for a good result. | "Deterministic "Assembly Line." You define the exact path of collaboration with RouterNode |
| No Control. The "Tech Support" agent might try to answer a "Billing" question. | "Total Control." The RouterNode guarantees only the correct specialist agent ever sees the task. |
| Implicit Data Flow. Agents can pollute each other's context. | "Explicit Data Flow." The history log proves what data was given to each agent. |
| Wasteful. If one step fails, other agents may still run, wasting API calls. | "Efficient." If a step fails, the graph stops. The TechAgent never runs if the TriageAgent fails. |
This demo uses a local FAISS vector store, built from the document_text.txt FAQ.
build_index.py— builds a FAISS vector storesupport_app.py— launches the interactive Streamlit GlassBox Support app
- Python 3.10+
- Poetry
- Google Gemini API Key
- macOS/Linux/WSL/Windows
git clone https://github.com/snath-ai/customer-support-demo.git
cd customer-support-demo
# Install all dependencies (including lar-engine from PyPI)
poetry installCreate a .env file:
GOOGLE_API_KEY="YOUR_API_KEY_HERE"poetry run python build_index.pyThis creates your FAISS index in vector_store/.
poetry run streamlit run support_app.pyOpen in browser: http://localhost:8501 and try asking:
How do I reset my password?(This will route toTECH_AGENT)Can I get a refund?(This will route toBILLING_AGENT)
Ask:
This is the lar assembly line in action.
graph TD
A[Start] --> B(LLMNode<br/>'Agent 1: Triage');
B --> C(LLMNode<br/>'Agent 2: Planner');
C --> D(ToolNode<br/>'Retriever');
%% This is the "hub" node
D --> E{RouterNode<br/>'Manager: Route By Category'};
%% Define the three parallel paths
E -- "BILLING_AGENT" --> F;
E -- "TECH_AGENT" --> G;
E -- "GENERAL_AGENT" --> H;
%% Define what's INSIDE the subgraphs
subgraph "Billing Department"
F(LLMNode<br/>'Agent 3: Billing Specialist');
end
subgraph "Tech Support Department"
G(LLMNode<br/>'Agent 4: Tech Specialist');
end
subgraph "General"
H(LLMNode<br/>'Agent 5: Generalist');
end
%% Define the "join" point
F --> I[AddValueNode<br/>'Final Answer'];
G --> I;
H --> I;
I --> J[END];
The core of this application is a Multi-Agent Orchestration Graph. Lár forces you to define the assembly line, which guarantees predictable, auditable results.
The agent executes in a fixed, 6-step sequence. The graph is defined backwards in the code, but the execution runs forwards:
| Step | Node Name | Lár Primitive | Action | State Output |
|---|---|---|---|---|
| 0 (Start) | triage_node | LLMNode | Classifies the user's input ({task}) into a service category (BILLING, TECH, etc.). |
category |
| 1 | planner_node | LLMNode | Converts the task into a concise, high-quality search query. | search_query |
| 2 | retrieve_node | ToolNode | Executes the local FAISS vector search and retrieves the relevant context. | retrieved_context |
| 3 | specialist_router | RouterNode | Decision point. Reads the category and routes the flow to the appropriate specialist. | (No change; routing) |
| 4 | billing/tech_agent | LLMNode | The chosen specialist synthesizes the final answer using the retrieved context. | agent_answer |
| 5 (End) | final_node | AddValueNode | Saves the synthesized answer as final_response and terminates the graph. |
final_response |
This demo relies on the core Lár primitives to function:
-
LLMNode: Used 5 times (Triage, Plan, and the 3 Specialists) for all reasoning and synthesis steps. -
RouterNode: Used once (specialist_router) for the deterministic if/else branching logic. -
ToolNode: Used once (retrieve_node) to securely execute the local RAG database lookup. -
GraphExecutor: The engine that runs this entire sequence and produces the complete audit log
'''
====================================================================
ARCHITECTURE NOTE: Defining the Graph Backwards
The Lár Engine uses a "define-by-run" philosophy. Because a node
references the *next_node* object (e.g., next_node=planner_node),
the nodes MUST be defined in Python in the REVERSE order of execution
to ensure the next object already exists in memory.
Execution runs: START (Triage) -> END (Final)
Definition runs: END (Final) -> START (Triage)
====================================================================
'''
from lar import *
from lar.utils import compute_state_diff # (Used by executor)
# 1. Define the "choice" logic for our Router
def triage_router_function(state: GraphState) -> str:
"""Reads the 'category' from the state and returns a route key."""
category = state.get("category", "GENERAL").strip().upper()
if "BILLING" in category:
return "BILLING_AGENT"
elif "TECH_SUPPORT" in category:
return "TECH_AGENT"
else:
return "GENERAL_AGENT"
# 2. Define the agent's nodes (the "bricks")
# We build from the end to the start.
# --- The End Nodes (the destinations) ---
final_node = AddValueNode(key="final_response", value="{agent_answer}", next_node=None)
critical_fail_node = AddValueNode(key="final_status", value="CRITICAL_FAILURE", next_node=None)
# --- The "Specialist" Agents ---
billing_agent = LLMNode(
model_name="gemini/gemini-2.5-pro",
prompt_template="You are a BILLING expert. Answer '{task}' using ONLY this context: {retrieved_context}",
output_key="agent_answer",
next_node=final_node
)
tech_agent = LLMNode(
model_name="gemini/gemini-2.5-pro",
prompt_template="You are a TECH SUPPORT expert. Answer '{task}' using ONLY this context: {retrieved_context}",
output_key="agent_answer",
next_node=final_node
)
general_agent = LLMNode(
model_name="gemini/gemini-2.5-pro",
prompt_template="You are a GENERAL assistant. Answer '{task}' using ONLY this context: {retrieved_context}",
output_key="agent_answer",
next_node=final_node
)
# --- The "Manager" (Router) ---
specialist_router = RouterNode(
decision_function=triage_router_function,
path_map={
"BILLING_AGENT": billing_agent,
"TECH_AGENT": tech_agent,
"GENERAL_AGENT": general_agent
},
default_node=general_agent
)
# --- The "Retriever" (Tool) ---
retrieve_node = ToolNode(
tool_function=retrieve_relevant_chunks, # This is our local FAISS search
input_keys=["search_query"],
output_key="retrieved_context",
next_node=specialist_router,
error_node=critical_fail_node
)
# --- The "Planner" (LLM) ---
planner_node = LLMNode(
model_name="gemini/gemini-2.5-pro",
prompt_template="You are a search query machine. Convert this task to a search query: {task}. Respond with ONLY the query.",
output_key="search_query",
next_node=retrieve_node
)
# --- The "Triage" Node (The *real* start) ---
triage_node = LLMNode(
model_name="gemini/gemini-2.5-pro",
prompt_template="You are a triage bot. Classify this task: \"{task}\". Respond ONLY with: BILLING, TECH_SUPPORT, or GENERAL.",
output_key="category",
next_node=planner_node
)
# 3. Run the Agent
executor = GraphExecutor()
initial_state = {"task": "How do I reset my password?"}
result_log = list(executor.run_step_by_step(
start_node=triage_node,
initial_state=initial_state
))
'''
The "glass box" log for Step 0 will show:
"state_diff": {"added": {"category": "TECH_SUPPORT"}}
The log for Step 1 will show:
"Routing to LLMNode" (the tech_support_agent)
'''If you build an agent using the Lár Engine, you are building a dependable, verifiable system. Help us spread the philosophy of the "Glass Box" by displaying the badge below in your project's README.
By adopting this badge, you signal to users and collaborators that your agent is built for production reliability and auditability.
Show an Auditable Badge to your project:
Badge Markdown:
[](https://docs.snath.ai)Lár is designed for Agentic IDEs (Cursor, Windsurf, Antigravity) and strict code generation.
We provide a 3-Step Workflow to make your IDE an expert Lár Architect.
Instead of pasting massive prompts, simply reference the master files in the lar/ directory (or download them from the main repo).
- Context (The Brain): In your IDE chat, reference
@lar/IDE_MASTER_PROMPT.md. This loads the strict typing rules and "Code-as-Graph" philosophy. - Integrations (The Hands): Reference
@lar/IDE_INTEGRATION_PROMPT.mdto generate production-ready API wrappers in seconds. - Scaffold (The Ask): Open
@lar/IDE_PROMPT_TEMPLATE.md, fill in your agent's goal, and ask the IDE to "Implement this."
Example Prompt to Cursor/Windsurf: "Using the rules in @lar/IDE_MASTER_PROMPT.md, implement the agent described in @lar/IDE_PROMPT_TEMPLATE.md."
Lár is an open-source agent framework built to be clear, debuggable, and developer-friendly. If this project helps you, consider supporting its development through GitHub Sponsors.
Become a sponsor → Sponsor on GitHub
Your support helps me continue improving the framework and building new tools for the community.
- Lár Engine by Aadithya Vishnu Sajeev — glass-box agent framework
- FAISS — vector search
- SentenceTransformers — embeddings
- Streamlit — UI
This Project is licensed under the Apache License 2.0
This means:
- You are free to use Làr in personal, academic, or commercial projects.
- You may modify and distribute the code.
- You MUST retain the
LICENSEand theNOTICEfile. - If you distribute a modified version, you must document what you changed.
Apache 2.0 protects the original author (Aadithya Vishnu Sajeev)
while encouraging broad adoption and community collaboration.
For developers building on Làr:
Please ensure that the LICENSE and NOTICE files remain intact
to preserve full legal compatibility with the Apache 2.0 terms.