This project is a backend prototype built with NestJS to explore LLM-based application architectures.
It evolves from a simple tool-calling system into a planner-based execution model capable of:
- multi-turn conversations
- tool calling
- multi-step reasoning
- sequential execution of multiple tools within a single user request
The system persists conversations and maintains context across requests using:
x-user-id(simulated authentication)conversationId(multi-turn continuity)
This project is developed using a spec-driven workflow.
Each feature is defined as a structured specification and then implemented step-by-step using Codex.
Key principles:
- break features into explicit tasks
- define acceptance criteria before coding
- implement in small, verifiable steps
- avoid premature abstraction
- keep architecture aligned with real problems
Specs live under the spec/ directory and guide the evolution of the system.
- Node.js
- NestJS
- TypeScript
- PostgreSQL
- TypeORM
- Docker
- Anthropic (Claude) API
- Jest (testing)
- OpenAPI (Swagger)
# install dependencies
npm install
# run database (docker)
docker-compose up -d
# run migrations (if needed)
npm run migration:run
# start app
npm run start:devOnce the app is running, access:
http://localhost:3000/docsSwagger UI allows you to:
- explore the /ask endpoint
- test requests interactively
- understand request/response contracts
At a high level:
- Receives a user prompt via /ask
- Persists the message in a conversation
- Builds full conversation context
- Delegates execution to a planner-based runtime
Execution flow:
AskService
-> TurnRunnerService
-> Planner (LLM-driven)
-> decides next action
-> ToolExecutorService
-> executes tools
-> loop until final answerThe system can:
- respond directly
- call one tool
- call multiple tools sequentially
- combine results into a final response
POST /ask
Request:
{
"prompt": "string",
"conversationId": "string (optional)"
}Headers:
x-user-id: string
Response:
{
"content": "string",
"conversationId": "string"
}📬 API Examples Direct answer
{
"prompt": "Explain agents in one sentence"
}Single tool call
{
"prompt": "What is the status of order 123?"
}Multi-tool execution (core feature)
{
"prompt": "What is the status and items of order 123?"
}Expected behavior:
- system calls getOrderStatus
- system calls getOrderItems
- system returns a combined answer
Multi-turn conversation First request:
{
"prompt": "What is the status of my order?"
}Response:
{
"content": "Which order?",
"conversationId": "abc-123"
}Follow-up:
{
"prompt": "Order 123",
"conversationId": "abc-123"
}This project builds on:
👉 https://github.com/artur-cesar/tool-caller-prototype
The previous system:
- supported tool calling
- but only allowed:
- 0 tool calls (direct answer)
- 1 tool call per turn
Limitation:
User: "status AND items of order 123"
→ system could not execute both tools in the same turnThis version introduces a planner-based execution loop that enables:
- multiple tool calls in the same turn
- step-by-step reasoning
- result composition across tools
In short:
Before:
LLM → 0 or 1 tool → response
Now:
LLM → tool → LLM → tool → LLM → responseThis is the foundation for:
- planner-based agents
- autonomous workflows
- real-world LLM orchestration systems
The architecture is intentionally simple, explicit, and extensible.