Back to .md Directory

app/config.py

Outlines a six-phase learning roadmap to build an enterprise chatbot with OpenAI Responses API, RAG, tools, Chainlit UI, and FastAPI.

May 2, 2026
0 downloads
1 views
ai rag prompt openai
View source

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

PythonOpenAI Responses APIFastAPIChainlitChromaDB/FAISSSpring Boot (future)

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.

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.

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

  1. 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
  1. 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
  1. 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 / .pdf into 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:

  1. Loader

    • Walk data/docs/, read files into {id, text, metadata}.
  2. Splitter

    • Simple rule: chunk by paragraphs or by N characters with overlap.
  3. 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]
    
  4. Vector store

    • Use a small library (e.g., chromadb) or FAISS.
  5. Retriever

    • Given query, embed, search, return top-k chunks.
  6. 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:

  1. Interface Layer

    • Chainlit app
    • FastAPI HTTP API (for Java and others)
  2. Application Layer

    • Chat orchestration (sessions, routing)
    • Tool manager (decides which tools available)
    • RAG orchestrator (query โ†’ retrieve โ†’ augment)
  3. Domain / Services Layer

    • Tools: DB query service, Unanet-like analytics, HR Resume search, etc.
    • RAG services (vector store, doc ingestion)
  4. 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 /chat endpoint.

    • 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

  1. Phase 1:

    • Project: 01-basic-chat-cli
    • Focus: Responses API, CLI chat.
  2. Phase 2:

    • Project: 02-chat-core-lib
    • Focus: reusable client, sessions, structured output.
  3. Phase 3:

    • Project: 03-tool-calling-basics
    • Focus: function calling, local tools.
  4. Phase 4:

    • Project: 04-rag-local
    • Focus: homegrown RAG pipeline (ingest โ†’ embed โ†’ search โ†’ answer).
  5. Phase 5:

    • Project: 05-chainlit-rag-assistant
    • Focus: Python-only UI, RAG + chat combined.
  6. Phase 6:

    • Project: enterprise-bot
    • Focus: layered architecture, FastAPI API, ready for Java integration.

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
    • README content
    • step-by-step commands you run in your terminal.

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-mini with 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