Building an AI Agent for Dolibarr ERP
ERP systems contain everything from customers and invoices to products, orders, and inventory. But interacting with all of that usually means navigating dashboards, forms, filters, and tables.
I wanted to see what would happen if I put an LLM in front of an ERP and gave it the ability to actually interact with the system.
The result is a simple AI agent for Dolibarr ERP.
Instead of navigating through Dolibarr manually, you can ask:
"Show me all customers"
"List all invoices"
"Get details for customer ID 1"
"What products do we have?"
The agent figures out which API operation is needed, calls Dolibarr, and presents the result conversationally.
How It Works
The architecture is pretty straightforward:
User
↓
LLM
↓
Tool Call
↓
Python API Interface
↓
Dolibarr REST API
↓
API Result
↓
LLM
↓
User
The important part is tool calling.
The LLM doesn't directly access Dolibarr. Instead, it has access to a function called dolibarr_api:
{
"name": "dolibarr_api",
"description": "Execute API calls to the Dolibarr ERP system",
"parameters": {
"type": "object",
"properties": {
"method": {
"type": "string",
"enum": ["GET", "POST", "PUT", "DELETE"]
},
"endpoint": {
"type": "string"
},
"payload": {
"type": "string"
}
},
"required": ["method", "endpoint"]
}
}
So if I ask:
"Show me all customers"
the model can decide that it needs:
{
"method": "GET",
"endpoint": "/thirdparties"
}
Python receives that tool call and executes the corresponding request against Dolibarr.
Connecting to Dolibarr
Dolibarr already exposes a REST API, so the actual integration is fairly small.
class DolibarrAPI:
def __init__(self, api_key):
self.api_key = api_key
self.headers = {
"DOLAPIKEY": api_key,
"Content-Type": "application/json",
"Accept": "application/json"
}
def _request(self, method, endpoint, data=None, params=None):
url = f"{self.base_url}{endpoint}"
response = requests.request(
method,
url,
headers=self.headers,
json=data,
params=params
)
response.raise_for_status()
return response.json()
The agent can use this interface to perform GET, POST, PUT, and DELETE operations.
That means the LLM isn't limited to answering questions about the ERP. It can potentially take actions inside it.
The Agent Loop
When a message comes in, the application first sends the conversation and available tool definition to the LLM.
response = self.client.chat.completions.create(
model="gpt-3.5-turbo",
messages=messages,
functions=self.functions,
function_call="auto"
)
If the model decides that it needs ERP data, it generates a function call.
The application executes it:
api_result = self.execute_dolibarr_call(
method=function_args.get("method", "GET"),
endpoint=function_args.get("endpoint", ""),
payload=function_args.get("payload", "")
)
The result from Dolibarr is then sent back to the LLM.
The second model call converts the raw ERP response into something useful for the user.
So a request essentially becomes:
"Get customer ID 5"
↓
GET /thirdparties/5
↓
Dolibarr JSON response
↓
Readable customer information
This is what makes it more than a normal chatbot.
The model is being given a tool that lets it interact with an external system and use the result to complete the user's request.
What Can It Do?
The current implementation understands several Dolibarr areas:
/thirdparties
/invoices
/products
/contacts
/users
/proposals
/orders
/bills
/projects
/stocks
This allows queries such as:
"Show me all customers"
"List all invoices"
"What products do we have?"
"Get customer ID 12"
"Show me recent proposals"
The same interface also supports write operations, allowing the agent to create or modify records where the corresponding Dolibarr endpoint supports it.
System Prompt
The system prompt gives the model basic knowledge about the available Dolibarr endpoints and how it should present ERP data.
For example:
/thirdparties - customers and suppliers
/invoices - invoices
/products - products
/contacts - contacts
/orders - sales orders
/projects - projects
/stocks - inventory
The model combines this information with the user's request to determine which endpoint and HTTP method it should use.
For a prototype this keeps the architecture extremely simple: the LLM handles the natural-language interpretation while Dolibarr remains the actual source of truth.
Interface
I used Gradio for the frontend:
demo = gr.ChatInterface(
fn=agent.chat,
title="OpenAI-Powered Dolibarr ERP Assistant"
)
That gives the whole system a simple conversational interface.
Instead of learning where every operation lives inside an ERP, the user can describe what they want in normal language and let the agent translate that intent into API operations.
Final Architecture
The entire project can basically be summarized as:
Natural Language
↓
LLM
↓
Function / Tool Calling
↓
Dolibarr REST API
↓
Structured ERP Data
↓
LLM
↓
Natural Language
It's a relatively small implementation, but that's also what makes the experiment interesting.
Dolibarr continues doing what an ERP should do: storing and managing the actual business data.
The LLM simply becomes another interface for interacting with it.
Instead of teaching users how to navigate every part of an ERP, we can increasingly let them describe what they want done and have an agent translate that intent into controlled operations against the underlying system.