A multi-agent system built with AutoGen that automates the core backend workflows of an accounts-payable / procurement function β invoice processing, 3-way matching, payment release, exception handling, and vendor communication.
Built during an undergraduate internship at EY as a proof-of-concept for how drag-and-drop agent orchestration platforms (think UiPath, SAP, or custom low-code tools) can be backed by an LLM-driven agent layer.
The system runs as a group chat where a GroupChatManager routes each user instruction to the most appropriate specialist agent. Each agent owns exactly one responsibility, queries a local SQLite database, and hands off to the next agent if needed.
| Agent | Responsibility |
|---|---|
InvoiceAgent |
Parses invoice details and persists them via LLM-generated SQL |
MatcherAgent |
3-way quantity match across PO β GR β Invoice |
ExceptionHandlerAgent |
Logs mismatches and missing-data errors to the exceptions table |
PaymentProcessingAgent |
Releases payment once a Matched status is confirmed |
StatusAgent |
Looks up payment + match status for any invoice |
VendorCommunicationAgent |
Drafts a vendor-facing status update from live DB data |
ReportingAgent |
Runs canned SQL reports (billed per vendor, overdue, open exceptions) |
User instruction
β
βΌ
GroupChatManager (AutoGen, speaker_selection="auto")
β
βββΊ InvoiceAgent βββΊ SQLite (invoice, invoice_items)
βββΊ MatcherAgent βββΊ SQLite (match_status)
βββΊ ExceptionHandlerAgent βββΊ SQLite (exceptions)
βββΊ PaymentProcessingAgent βββΊ SQLite (payments, invoice)
βββΊ StatusAgent βββΊ SQLite (invoice, match_status)
βββΊ VendorCommunicationAgentβββΊ SQLite + LLM (vendor message)
βββΊ ReportingAgent βββΊ SQLite (aggregate queries)
Each agent overrides generate_reply() with a keyword-based relevance check before doing any work, which keeps the token spend low and the conversation from going in circles.
vendor purchase_order purchase_order_items
ββββββ ββββββββββββββ ββββββββββββββββββββ
vendor_id (PK) po_number (PK) id (PK)
name vendor_id (FK) po_number (FK)
email order_date item_code
phone quantity
unit_price
goods_receipt goods_receipt_items
βββββββββββββ βββββββββββββββββββ
gr_number (PK) id (PK)
po_number (FK) gr_number (FK)
receipt_date item_code
quantity_received
invoice invoice_items match_status
βββββββ βββββββββββββ ββββββββββββ
invoice_no (PK) id (PK) po_id (PK)
po_number (FK) invoice_no (FK) status
vendor_id (FK) item_code details
invoice_date quantity
total_amount unit_price
status
date_received payments exceptions
amount_paid ββββββββ ββββββββββ
payment_id (PK) exception_id (PK)
invoice_no (FK) po_number
payment_date issue_type
amount_paid details
status status
timestamp
Prerequisites: Python 3.10+, an Azure OpenAI (or standard OpenAI) API key.
# 1. Clone and install dependencies
git clone https://github.com/<your-username>/procurement-agent.git
cd procurement-agent
pip install -r requirements.txt
# 2. Add your LLM credentials
cp config_list.example.json config_list.json
# Edit config_list.json with your API key and endpoint
# 3. Create the database and load sample data
python schema.py
python seed_data.py
# 4. Run
python main_groupchat.pyπ₯ Instruction: process invoice INV4001 for PO1001, vendor V001,
date 2024-02-01, total 2000, item ITEM-A qty 100 price 10
π₯ Instruction: run 3-way match for PO1001
π₯ Instruction: process payment for invoice INV4001
π₯ Instruction: what is the status of invoice INV4001
π₯ Instruction: generate report total billed per vendor
π₯ Instruction: send vendor status update for vendor V001
config_list.json follows the standard AutoGen format:
[
{
"model": "gpt-4o-mini",
"api_type": "azure",
"api_key": "YOUR_KEY",
"base_url": "https://YOUR_ENDPOINT.openai.azure.com/",
"api_version": "2024-03-01-preview"
}
]The file is in .gitignore β never commit API keys.
procurement-agent/
βββ agents/
β βββ __init__.py
β βββ invoice_agent.py
β βββ matching_agent.py
β βββ exception_agent.py
β βββ payment_agent.py
β βββ status_agent.py
β βββ vendor_agent.py
β βββ reporting_agent.py
βββ db/ # created at runtime, gitignored
βββ config.py # loads and caches LLM config
βββ config_list.example.json # template β copy to config_list.json
βββ main_groupchat.py # entry point
βββ run_sql.py # thin SQLite wrapper
βββ schema.py # CREATE TABLE statements
βββ seed_data.py # sample data for local testing
βββ requirements.txt
- Parameterised queries everywhere β
run_sql(query, params)usescursor.execute(query, params)throughout, so there's no SQL injection surface even when agent-generated values are passed in. - No positional column access β agents query only the columns they need by name rather than indexing into raw result tuples, so schema changes don't silently break things.
- INSERT OR REPLACE on match_status β re-running a match always reflects the latest quantities rather than failing on a duplicate primary key.
- Agents stay in their lane β each
generate_replystarts with a keyword check and returns an empty string if the message isn't relevant to that agent. This keeps the LLM calls and the conversation rounds to a minimum. - LLM used only where language matters β invoice SQL generation and the vendor message are LLM tasks; everything else (matching arithmetic, status lookups, payment logic) is plain Python + SQL.
- Swap SQLite for PostgreSQL with minimal changes to
run_sql.py. - Add an approval step before payment (human-in-the-loop via
human_input_mode="ALWAYS"). - Expose the group chat as a REST API with FastAPI for integration with a front-end drag-and-drop builder.
- Add structured logging to the
logstable for a full audit trail.