This project provides a modular, standalone RAG (Retrieval-Augmented Generation) hosting service designed to integrate with the OTOBO ticket system. It enables easy experimentation and deployment of multiple custom RAG pipelines. Each RAG module defines its own retrieval/generation logic and API schema, loaded dynamically at runtime.
The system uses LangGraph, LangChain, and ChromaDB to support flexible embedding and LLM-powered generation. It exposes a REST API via FastAPI for ingesting content and interacting with configured RAGs.
You may find documentation on how to use this with OTOBO here: https://doc.otobo.org
Clone the repository and set up the environment using Docker Compose:
git clone git@github.com:RotherOSS/otobo-ai.git
cd otobo-aiExample RAG definitions are provided under rag_examples.
If you use this setup with OTOBO, choose the default.
It supports Tickets, FAQ and Documentation.
Copy the RAG description to your RAG definition folder.
cp -r rags_examples/default ragsAll RAG definitions placed here are exposed at the web service. You may tune it to your liking, or create a new one!
Create a .env file in the root directory to configure environment variables:
cp .docker_compose_env_ai .envEdit the .env file to set your desired configuration options. For independent setup use
COMPOSE_FILE=docker-compose/otobo-ai_base.yml:docker-compose/otobo-ai_standalone.yml
Important
For usage with an OTOBO docker setups, docker compose v2 is required.
Use Docker Compose to build and run the server:
docker compose up --build --detachThe docker compose.yaml mounts the local ./rags directory into the container as src/rags, enabling external customization.
All endpoints are mounted under /otobo-ai/, secured with API key authentication via the get_api_key dependency.
Ingest a single data item for embedding.
Input:
{
"type": "documentation",
"content": [[{ "type": "text", "text": "your content here" }]],
"embed_content_types": ["text"],
"store_fulltext": false
}Response: 200 OK or 500 Internal Server Error
Ingest a batch of data items for embedding.
Input:
{
"type": "faq",
"content": [
[
{ "type": "question", "text": "What is OTOBO?" },
{ "type": "answer", "text": "A ticketing system." }
],
[
{ "type": "question", "text": "In what language is OTOBO written?" },
{ "type": "answer", "text": "In Perl." }
]
],
"embed_content_types": ["question"],
"store_fulltext": true,
"fulltext_types": ["answer"]
}Response: 200 OK or 500 Internal Server Error
Each registered RAG module is exposed under:
Run the full RAG process (retrieve and generate).
Input:
{
"input": {
"question": "What is OTOBO?",
"do_scoring": true
},
"config": {},
"kwargs": {}
}Output:
{
"output": {
"question": "What is OTOBO?",
"generation": "OTOBO is an open-source ticketing system.",
"score": 0.87
},
"metadata": {
"run_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"feedback_tokens": [
{
"key": "score-feedback",
"token_url": "https://feedback.example.com/token/3fa85f64",
"expires_at": "2025-04-06T08:05:36.868Z"
}
]
}
}Each RAG can define its own input/output models, but this is the expected default format.
Note that only the input and output fields are relevant for usage within OTOBO, the other fields are automatically provided by LangServe
Each RAG lives in its own folder under rags/, structured as follows:
rags/
└── my_custom_rag/
├── graph.py
├── chains.py # optional
├── io_models.py
└── prompts/
└── prompt.txt
-
graph.pyMust define agraphobject usingStateGraph, compiled withgraph = workflow.compile(). -
io_models.pyMust define:class RAGInput(BaseModel): ... class RAGOutput(BaseModel): ...
These models define the request and response schema for your RAG.
-
chains.pyUse this to separate LangChain logic or define chains used in your workflow. -
prompts/Store custom prompt templates here. Load them withPath(__file__).parent / "prompts" / "prompt.txt".
workflow = StateGraph(GraphState)
workflow.add_node("retrieve", retrieve)
workflow.add_node("generate", generate)
workflow.set_entry_point("retrieve")
workflow.add_edge("retrieve", "generate")
workflow.add_edge("generate", END)
graph = workflow.compile()All RAGs in rags/ are auto-registered by register_rags(app) on startup.
If files or required types are missing, the module will be skipped with a warning.
This project supports dynamic loading of RAG modules at runtime via the register_rags(app: FastAPI) function.
At startup, the server scans the src/rags/ directory for subdirectories containing a graph.py file.
Each graph.py must define a graph object with a .with_config(config) method.
These are registered as individual FastAPI routes under /otobo-ai/{rag_name}, protected by API key.
Each RAG module must follow this structure:
- A
graph.pydefining the LangGraph workflow (graph) - An
io_models.pydefining the input/output types (RAGInputandRAGOutput) - Optionally,
chains.py, prompt templates, and other helpers
Only rag_examples/ is version-controlled. The src/rags/ directory is .gitignored so users can safely define custom modules without affecting the repo.
src/
├── rags/ ← not version controlled
│ ├── simple_rag/ ← copy or create your RAG modules here
│ │ ├── graph.py
│ │ ├── chains.py
│ │ ├── io_models.py
│ │ └── prompts/
│ └── ...
└── ...
rag_examples/ ← reference implementations
├── simple_rag/
└── ...To update the dependencies in a controlled way:
In requirements.txt, remove version pins from most packages.
Keep pins only for critical compatibility fixes.
Before:
uvicorn==0.25.0
fastapi==0.108.0
# critical compatibility fixes
numpy==1.26.4After:
uvicorn
fastapi
# critical compatibility fixes
numpy==1.26.4docker compose builddocker compose upEnsure it runs without version issues.
Verify ingestion and RAG endpoints still work correctly.
After confirming stability, inspect the installed versions:
docker compose run --rm otobo-ai pip listUse the output to update requirements.txt with the final versions used.
Example:
uvicorn==0.34.0
fastapi==0.115.0
# critical compatibility fixes
numpy==1.26.4git add requirements.txt
git commit -m "Update pinned dependencies"