app/config.py
Outlines a six-phase learning roadmap to build an enterprise chatbot with OpenAI Responses API, RAG, tools, Chainlit UI, and FastAPI.
What this file does
Outlines a six-phase learning roadmap to build an enterprise chatbot with OpenAI Responses API, RAG, tools, Chainlit UI, and FastAPI.
When to use it
- You want a structured path from basic OpenAI calls to a production-ready chatbot
- You need to integrate Python chatbot with Java via REST
- You are building a RAG pipeline from scratch with minimal dependencies
- You want to add tool calling and structured outputs to your chatbot
Assumes this stack
Go ๐
Letโs design this so you can actually build as you learn, not just read.
0. North Star: What Youโre Ultimately Building
Target system (end of roadmap):
A Python-first, API-exposed Enterprise Chatbot Service that:
- Uses OpenAI Responses API
- Has RAG over your docs
- Supports file search / DB search
- Can call web/tools
- Exposes a clean HTTP API (FastAPI)
- Has a Chainlit UI on top
- Can be consumed later by Java (Spring Boot, etc.) via REST or messaging
Weโll climb there in phases.
Phase 1 โ โHello OpenAIโ (CLI + Notebook)
Goal: Get fully comfortable with the Responses API and basic prompting in Python.
Milestones
-
โ Can call
client.responses.create()from a Python script. -
โ Can set system / user messages intentionally.
-
โ Can run this from both:
- a simple CLI (
python chat.py) - a Jupyter/VS Code Notebook.
- a simple CLI (
Mini-project: 01-basic-chat-cli
Folder skeleton:
openai-learning/
phase1_basic_chat/
.env # OPENAI_API_KEY=...
requirements.txt
basic_chat.py
README.md
requirements.txt (minimal):
openai
python-dotenv
First code steps โ basic_chat.py:
import os
from dotenv import load_dotenv
from openai import OpenAI
load_dotenv()
client = OpenAI()
def basic_chat():
messages = [
{"role": "system", "content": "You are a helpful assistant."},
{"role": "user", "content": "Explain RAG in 3 bullet points."}
]
resp = client.responses.create(
model="gpt-4.1-mini",
input=messages,
)
print(resp.output[0].content[0].text)
if __name__ == "__main__":
basic_chat()
What to do in this phase
-
Play with:
- different models
- different system prompts
- short vs. long user messages.
-
Add a simple loop:
- input from
stdin - append to
messages - re-call the model.
- input from
Phase 2 โ Reusable Client, Structured Outputs & Prompt Patterns
Goal: Move from โtoy scriptโ to reusable building blocks.
Milestones
- โ Centralized client + config (no copy-paste).
- โ Simple chat history manager.
- โ Generate structured JSON outputs (e.g., parsing fields).
Mini-project: 02-chat-core-lib
Folder skeleton:
openai-learning/
phase2_chat_core/
app/
__init__.py
config.py # env loading, model names
client.py # OpenAI client factory
chat_session.py # class ChatSession(...)
schemas.py # Pydantic models for structured outputs
examples/
classify_intent.py
summarize_doc.py
requirements.txt
README.md
Key components
- Config + client
# app/config.py
import os
from dotenv import load_dotenv
load_dotenv()
OPENAI_MODEL_DEFAULT = os.getenv("OPENAI_MODEL_DEFAULT", "gpt-4.1-mini")
# app/client.py
from openai import OpenAI
from .config import OPENAI_MODEL_DEFAULT
_client = None
def get_client():
global _client
if _client is None:
_client = OpenAI()
return _client
def call_model(messages, model=None):
client = get_client()
resp = client.responses.create(
model=model or OPENAI_MODEL_DEFAULT,
input=messages,
response_format={"type": "text"},
)
return resp.output[0].content[0].text
- ChatSession (stateful chat)
# app/chat_session.py
from .client import call_model
class ChatSession:
def __init__(self, system_prompt: str):
self.messages = [{"role": "system", "content": system_prompt}]
def ask(self, user_msg: str) -> str:
self.messages.append({"role": "user", "content": user_msg})
reply = call_model(self.messages)
self.messages.append({"role": "assistant", "content": reply})
return reply
- Structured output example
# examples/classify_intent.py
from app.client import get_client
from app.config import OPENAI_MODEL_DEFAULT
client = get_client()
messages = [
{"role": "system", "content": "You output JSON only."},
{"role": "user", "content": "Book me a flight to Boston tomorrow morning."}
]
resp = client.responses.create(
model=OPENAI_MODEL_DEFAULT,
input=messages,
response_format={
"type": "json_schema",
"json_schema": {
"name": "intent_schema",
"schema": {
"type": "object",
"properties": {
"intent": {"type": "string"},
"entities": {"type": "object"},
},
"required": ["intent", "entities"],
"additionalProperties": False,
},
"strict": True,
},
},
)
print(resp.output[0].content[0].text) # parse with json.loads
Outcome: You now have a tiny โSDKโ you control, not scattered scripts.
Phase 3 โ Tool Calling / Function Calling (Local Tools Only)
Goal: Learn tool calling using your own Python functions, before RAG.
Milestones
- โ Define tools as Python functions + JSON schemas.
- โ Let the model choose which function to call.
- โ Execute the function and return results to the user.
Mini-project: 03-tool-calling-basics
Example tools:
get_time_in_timezone(tz)search_local_index(query)(stub)calculate_discount(price, percentage)
Folder skeleton:
openai-learning/
phase3_tools/
app/
tools.py # python functions
tool_schema.py # JSON schema definitions
tool_runner.py # glue: call model, parse tool calls, execute
examples/
chat_with_tools.py
Tool schema + call example (simplified):
# app/tools.py
def get_time_in_timezone(timezone: str) -> str:
from datetime import datetime
import pytz
tz = pytz.timezone(timezone)
return datetime.now(tz).isoformat()
# app/tool_schema.py
tool_defs = [
{
"type": "function",
"function": {
"name": "get_time_in_timezone",
"description": "Get current time ISO8601 in a given timezone name.",
"parameters": {
"type": "object",
"properties": {
"timezone": {"type": "string"}
},
"required": ["timezone"],
},
},
}
]
# examples/chat_with_tools.py
import json
from app.client import get_client
from app.tool_schema import tool_defs
from app.tools import get_time_in_timezone
client = get_client()
TOOLS_MAP = {
"get_time_in_timezone": get_time_in_timezone,
}
def chat_with_tools(user_msg: str):
messages = [
{"role": "system", "content": "You may call tools when needed."},
{"role": "user", "content": user_msg},
]
resp = client.responses.create(
model="gpt-4.1-mini",
input=messages,
tools=tool_defs,
)
output = resp.output[0]
# check if it requested a tool call
for item in output.content:
if item.type == "tool_call":
fn_name = item.tool_call.function.name
args = json.loads(item.tool_call.function.arguments)
result = TOOLS_MAP[fn_name](**args)
# send result back to model
tool_msg = {
"role": "tool",
"tool_call_id": item.tool_call.id,
"name": fn_name,
"content": json.dumps({"result": result}),
}
messages.append(tool_msg)
final = client.responses.create(
model="gpt-4.1-mini",
input=messages,
)
return final.output[0].content[0].text
# no tool needed:
return output.content[0].text
if __name__ == "__main__":
print(chat_with_tools("What time is it in America/New_York?"))
Phase 4 โ RAG Foundations (Local, Simple)
Goal: Build your own RAG pipeline with minimal dependencies.
Milestones
-
โ Can ingest a folder of
.md/.txt/.pdfinto a vector store (e.g., ChromaDB or simple FAISS). -
โ Can perform:
query โ retrieve top-k chunks โ send as context to model.
-
โ Keep RAG decoupled from UI (pure Python module).
Mini-project: 04-rag-local
Folder skeleton:
openai-learning/
phase4_rag_local/
data/
docs/ # your markdown / text docs
rag/
__init__.py
loader.py # read files, clean
splitter.py # chunking logic
embeddings.py # call OpenAI embeddings
vectorstore.py # store + search (Chroma/FAISS)
retriever.py # high-level retrieve(query) -> [chunks]
examples/
build_index.py
ask_rag.py
Core steps you implement:
-
Loader
- Walk
data/docs/, read files into{id, text, metadata}.
- Walk
-
Splitter
- Simple rule: chunk by paragraphs or by
Ncharacters with overlap.
- Simple rule: chunk by paragraphs or by
-
Embeddings (OpenAI):
# rag/embeddings.py from openai import OpenAI client = OpenAI() def embed_texts(texts: list[str]) -> list[list[float]]: resp = client.embeddings.create( model="text-embedding-3-small", input=texts, ) return [d.embedding for d in resp.data] -
Vector store
- Use a small library (e.g., chromadb) or FAISS.
-
Retriever
- Given
query, embed, search, return top-k chunks.
- Given
-
RAG ask script (
examples/ask_rag.py)from rag.retriever import retrieve from app.client import get_client client = get_client() def ask(query: str): chunks = retrieve(query, k=5) context = "\n\n".join([c["text"] for c in chunks]) messages = [ {"role": "system", "content": "You answer using given context."}, {"role": "user", "content": f"Context:\n{context}\n\nQuestion: {query}"}, ] resp = client.responses.create( model="gpt-4.1-mini", input=messages, ) return resp.output[0].content[0].text
Phase 5 โ Chainlit Frontend (Python-First UI)
Goal: Wrap your chat & RAG into a nice UI without JS heavy lifting.
Milestones
-
โ Simple Chat UI backed by Phase 2 chat core.
-
โ Button or toggle to:
- use RAG (Phase 4) or
- use plain LLM.
-
โ Display retrieved context snippets.
Mini-project: 05-chainlit-rag-assistant
Folder skeleton:
openai-learning/
phase5_chainlit_rag/
app/
__init__.py
chat_core/ # copy or import from phase2
rag_core/ # copy or import from phase4
chainlit_app.py
chainlit.toml
requirements.txt
chainlit_app.py (conceptual):
import chainlit as cl
from app.chat_core.chat_session import ChatSession
from app.rag_core.retriever import retrieve
SYSTEM_PROMPT = "You are an enterprise assistant that uses context when available."
@cl.on_chat_start
async def start_chat():
session = ChatSession(SYSTEM_PROMPT)
cl.user_session.set("session", session)
@cl.on_message
async def on_message(message: cl.Message):
session = cl.user_session.get("session")
query = message.content
use_rag = True # later: toggle based on UI or metadata
if use_rag:
chunks = retrieve(query, k=4)
context = "\n\n".join([c["text"] for c in chunks])
user_msg = f"Context:\n{context}\n\nQuestion: {query}"
else:
chunks = []
user_msg = query
reply = session.ask(user_msg)
await cl.Message(content=reply).send()
# optionally show context in separate element
if chunks:
snips = "\n\n".join([f"- {c['source']}: {c['text'][:200]}..." for c in chunks])
await cl.Message(content=f"**Context used:**\n{snips}").send()
Run with:
chainlit run chainlit_app.py
Outcome: You now have a Python-only UI for a RAG assistant.
Phase 6 โ Enterprise Blueprint + Java Integration Hook
Now we turn your learning into an architecture.
Target Architecture (Python side)
Core layers:
-
Interface Layer
- Chainlit app
- FastAPI HTTP API (for Java and others)
-
Application Layer
- Chat orchestration (sessions, routing)
- Tool manager (decides which tools available)
- RAG orchestrator (query โ retrieve โ augment)
-
Domain / Services Layer
- Tools: DB query service, Unanet-like analytics, HR Resume search, etc.
- RAG services (vector store, doc ingestion)
-
Infrastructure Layer
- OpenAI client wrapper
- Vector DB client
- Logging, metrics, configuration
Folder blueprint:
enterprise-bot/
app/
__init__.py
config/
settings.py
interfaces/
api/ # FastAPI routers
__init__.py
chat.py
ui/
chainlit_app.py
core/
chat/
session.py
router.py # decide LLM-only vs RAG vs tools
rag/
loader.py
retriever.py
tools/
registry.py
finance_tools.py
hr_tools.py
infra/
openai_client.py
vector_store.py
logging.py
tests/
README.md
FastAPI API (for Java to consume later)
Example: app/interfaces/api/chat.py
from fastapi import APIRouter
from pydantic import BaseModel
from app.core.chat.router import handle_chat
router = APIRouter()
class ChatRequest(BaseModel):
session_id: str | None = None
message: str
use_rag: bool = True
class ChatResponse(BaseModel):
session_id: str
reply: str
@router.post("/chat", response_model=ChatResponse)
async def chat_endpoint(req: ChatRequest):
session_id, reply = await handle_chat(
session_id=req.session_id,
message=req.message,
use_rag=req.use_rag,
)
return ChatResponse(session_id=session_id, reply=reply)
Then create a main.py with FastAPI app and mount this router.
Java Integration (later phase, not now)
When you reach โmid-levelโ:
-
Build a Spring Boot microservice that:
-
Reads its config (Python chatbot URL, auth).
-
Calls
/chatendpoint. -
Wraps this as:
- A REST endpoint for other internal services.
- Or a scheduled/batch consumer (e.g., process tickets, proposals, resumes).
-
Patterns to use:
-
Circuit breaker / retry (Resilience4j) around Python API.
-
DTO mapping (Java models โ ChatRequest/ChatResponse).
-
Security:
- Python service protected by API key / JWT.
- Java side injects credentials from Vault or config.
Summary of Phases & Projects
-
Phase 1:
- Project:
01-basic-chat-cli - Focus: Responses API, CLI chat.
- Project:
-
Phase 2:
- Project:
02-chat-core-lib - Focus: reusable client, sessions, structured output.
- Project:
-
Phase 3:
- Project:
03-tool-calling-basics - Focus: function calling, local tools.
- Project:
-
Phase 4:
- Project:
04-rag-local - Focus: homegrown RAG pipeline (ingest โ embed โ search โ answer).
- Project:
-
Phase 5:
- Project:
05-chainlit-rag-assistant - Focus: Python-only UI, RAG + chat combined.
- Project:
-
Phase 6:
- Project:
enterprise-bot - Focus: layered architecture, FastAPI API, ready for Java integration.
- Project:
If you want, in the next step I can:
-
Pick Phase 1 or 2
-
Flesh it out as a mini-tutorial with:
- exact
requirements.txt READMEcontent- step-by-step commands you run in your terminal.
- exact
What's inside
6 phases, each with milestones, folder skeletons, and code examples; plus a target architecture blueprint
Change this for your project
- Replace
openai-learning/with your own project root directory name - Replace
gpt-4.1-miniwith the actual OpenAI model you intend to use - Replace
data/docs/with the path to your own document folder for RAG
Where it goes
Save in docs/ or the repository root. Gives agents and new contributors a map of the codebase.
Worth borrowing
- Phase-based learning with mini-projects that build on each other
- Separating reusable core library (config, client, session) from examples
- Decoupling RAG pipeline into loader, splitter, embeddings, vectorstore, retriever
Related Documents
Design Document: BharatSeva AI
Describes a 10-agent AWS system that helps India's informal workers access government schemes via voice-first, serverless architecture.
OpenClaw Enterprise Transformation Plan
Transforms a single-user AI agent into a dual-mode platform supporting both viral open-source and Fortune 500 enterprise deployments through phased security, IAM, audit, multi-tenancy, and Kubernetes features.
Qwen Image and Edit: Open-sourcing and Local GGUF Generations with Lightning
Documents the Qwen-Image and Qwen-Image-Edit models, covering architecture, training, benchmarks, ComfyUI setup, and prompting techniques for local GGUF deployment.
University of Guelph Rocketry Club - Complete Tech Stack
Documents the full tech stack of a university rocketry club website with AI chatbot, member management, and project showcases.