Technical Guide

How to Integrate LLMsinto Applications

From the first API call to streaming, function calling, RAG, and cost management in production. A complete technical guide for 2026, with examples in Python and JavaScript.

IntermediatePythonJavaScriptREST APIRAGProduction

Prerequisites

✓Basic knowledge of Python or JavaScript
✓Account at OpenAI, Anthropic, or Google (API key)
✓Familiarity with HTTP and REST APIs
01

Choose Your Model and Provider

The first decision affects cost, quality, and latency. For most cases: start with each provider's mini/Haiku/Flash tier and move up to frontier only if quality is insufficient.

CriterionChoice
Maximum qualityEach provider's frontier model — see /ranking
Cost-efficiencyEach provider's mini/Haiku/Flash tier
Speed (real-time chatbot)Groq (Llama), Claude Haiku, Gemini Flash
Privacy / on-premisesLlama, Mistral, Qwen via Ollama
Long context (200K+)Claude Sonnet, Gemini Pro
Code & programmingClaude, ChatGPT, DeepSeek Coder

Production tip: use LiteLLM as an abstraction layer. Write the code once and swap models by changing a single environment variable. Saves rewrites when pricing or quality shifts.

02

Basic API Call

Every integration starts with a simple call. The pattern is the same across all major providers: messages with roles (system, user, assistant). The model string in the examples below is illustrative — check each provider's current model list before shipping.

Python
from openai import OpenAI

client = OpenAI(api_key="your-api-key-here")

response = client.chat.completions.create(
    model="gpt-4o-mini",
    messages=[
        {
            "role": "system",
            "content": "You are a helpful assistant."
        },
        {
            "role": "user",
            "content": "Explain what RAG is in 2 paragraphs."
        }
    ],
    max_tokens=500,
    temperature=0.7,
)

print(response.choices[0].message.content)
print(f"Tokens used: {response.usage.total_tokens}")
JavaScript / Node.js
import OpenAI from "openai";

const client = new OpenAI({
  apiKey: process.env.OPENAI_API_KEY,
});

const response = await client.chat.completions.create({
  model: "gpt-4o-mini",
  messages: [
    {
      role: "system",
      content: "You are a helpful assistant.",
    },
    {
      role: "user",
      content: "Explain what RAG is in 2 paragraphs.",
    },
  ],
  max_tokens: 500,
  temperature: 0.7,
});

console.log(response.choices[0].message.content);
console.log(`Tokens: ${response.usage.total_tokens}`);
03

Streaming for Responsive UX

With streaming, tokens appear as they are generated — just like ChatGPT. Perceived speed improves dramatically without reducing total processing time.

Python — Streaming
with client.chat.completions.stream(
    model="gpt-4o-mini",
    messages=[{"role": "user", "content": "Write a poem about AI"}],
) as stream:
    for text in stream.text_stream:
        print(text, end="", flush=True)  # prints token by token
Next.js — API Route with Streaming (Edge Runtime)
// app/api/chat/route.ts
import { OpenAIStream, StreamingTextResponse } from "ai"; // Vercel AI SDK
import OpenAI from "openai";

export const runtime = "edge";

const openai = new OpenAI();

export async function POST(req: Request) {
  const { messages } = await req.json();

  const response = await openai.chat.completions.create({
    model: "gpt-4o-mini",
    stream: true,
    messages,
  });

  const stream = OpenAIStream(response);
  return new StreamingTextResponse(stream);
}
04

Function Calling

Function calling lets the LLM decide when and how to call functions you define. It is the foundation of agents — the model can query external data, call APIs, and execute actions.

Python — Function Calling
import json

tools = [
    {
        "type": "function",
        "function": {
            "name": "get_model_price",
            "description": "Retrieves the price of an LLM model by name",
            "parameters": {
                "type": "object",
                "properties": {
                    "model_name": {
                        "type": "string",
                        "description": "Model name, e.g. gpt-4o-mini"
                    }
                },
                "required": ["model_name"]
            }
        }
    }
]

response = client.chat.completions.create(
    model="gpt-4o-mini",
    messages=[{"role": "user", "content": "How much does this model cost?"}],
    tools=tools,
    tool_choice="auto",
)

if response.choices[0].finish_reason == "tool_calls":
    tool_call = response.choices[0].message.tool_calls[0]
    args = json.loads(tool_call.function.arguments)
    # Execute the real function here
    result = get_model_price(args["model_name"])
    print(f"Model called: {tool_call.function.name}({args})")
05

RAG — Context from Your Own Data

RAG (Retrieval-Augmented Generation) lets the LLM answer based on your data without fine-tuning. You retrieve relevant chunks from a vector store and inject them into the request context.

Python — Simple RAG with OpenAI Embeddings
import numpy as np

# 1. Generate embedding for the user's question
def get_embedding(text: str) -> list[float]:
    response = client.embeddings.create(
        model="text-embedding-3-small",
        input=text
    )
    return response.data[0].embedding

# 2. Retrieve relevant chunks (simplified — use pgvector/Pinecone in prod)
def search_context(question: str, documents: list[dict]) -> str:
    q_emb = get_embedding(question)

    # Compute cosine similarity with each document
    scores = []
    for doc in documents:
        score = np.dot(q_emb, doc["embedding"]) / (
            np.linalg.norm(q_emb) * np.linalg.norm(doc["embedding"])
        )
        scores.append((score, doc["text"]))

    # Return top-3 most relevant
    top3 = sorted(scores, reverse=True)[:3]
    return "\n---\n".join([t for _, t in top3])

# 3. Inject context into the prompt
question = "How much does this model cost?"
context = search_context(question, documents)

response = client.chat.completions.create(
    model="gpt-4o-mini",
    messages=[
        {
            "role": "system",
            "content": f"""Answer ONLY based on the context below.
If the answer is not in the context, say you don't know.

CONTEXT:
{context}"""
        },
        {"role": "user", "content": question}
    ]
)
print(response.choices[0].message.content)
Deep dive: RAG vs Fine-Tuning — when to use each →
06

Caching and Cost Control

In production, caching is the most effective tool for cutting costs. Identical or very similar requests do not need to reach the LLM.

Python — Simple cache with Redis
import hashlib
import redis
import json

r = redis.Redis(host="localhost", port=6379, db=0)

def llm_with_cache(messages: list, model: str = "gpt-4o-mini", ttl: int = 3600) -> str:
    # Generate deterministic cache key
    cache_key = hashlib.sha256(
        json.dumps({"model": model, "messages": messages}, sort_keys=True).encode()
    ).hexdigest()

    # Check cache
    cached = r.get(cache_key)
    if cached:
        return json.loads(cached)["content"]

    # Call LLM
    response = client.chat.completions.create(model=model, messages=messages)
    content = response.choices[0].message.content

    # Save to cache
    r.setex(cache_key, ttl, json.dumps({"content": content}))
    return content

Cost Reduction Strategies

  • →Cache frequent responses (Redis, Upstash)
  • →Native prompt caching (Anthropic, OpenAI) for long system prompts
  • →Smaller model for triage, larger only for complex cases
  • →Limit max_tokens to the minimum needed
  • →Compress long conversation histories

Production Monitoring

  • →Log input/output tokens per request
  • →Per-user daily budget cap (auto-block when hit)
  • →Alerts when daily cost exceeds threshold
  • →LangFuse or Helicone for detailed observability
  • →Cost dashboard per feature/endpoint

Production Checklist

□API key in environment variables (never hardcoded)
□Per-user rate limiting implemented
□Timeout configured (prevent hanging requests)
□Retry with exponential backoff for 429/500 errors
□Daily per-user budget cap
□Logging of input/output tokens per request
□Output validation before displaying to user
□Model version pinned (avoid breaking changes)
□Fallback to an alternative model if primary is unavailable
□Sensitive data never sent to an LLM without a DPA

Frequently Asked Questions

What is the difference between calling the API directly and using a framework like LangChain?▾
The direct API gives you more control, lower overhead, and fewer dependencies — ideal for simple use cases. LangChain and similar frameworks abstract common patterns (RAG, agents, memory) and speed up development of complex systems. Recommendation: start with the direct API. If you need complex agents or elaborate RAG pipelines, evaluate frameworks. Avoid premature abstraction.
How do I calculate the cost of an LLM in production?▾
Cost = (input_tokens × input_price/1M) + (output_tokens × output_price/1M). Use tiktoken (Python) or js-tiktoken to estimate tokens before sending. Prices vary a lot between the flagship and economy tier of each provider — see current per-model rates at /benchmark/custo-api. Multiply by your expected request volume and add a 20% buffer for system tokens and formatting.
How do I prevent users from abusing my LLM integration?▾
Implement: (1) mandatory authentication before any call; (2) per-user rate limiting (e.g., 10 requests/minute); (3) per-request token limits; (4) daily per-user budget cap — block automatically when reached; (5) real-time cost monitoring with alerts. Never expose your API key in the frontend.
Is it worth fine-tuning or should I use RAG?▾
RAG first, always. Fine-tuning is expensive (time + compute + labeled data), hard to keep current, and does not solve hallucination. RAG dynamically injects retrieved context — easier to update and debug. Use fine-tuning only for: adapting the model's style/tone, improving performance on a specific output format (structured JSON, niche code), or reducing latency with smaller specialized models.
How do I choose between streaming and a full response?▾
Use streaming for user interfaces (chatbots, text editors) — perceived speed improves dramatically even without reducing total latency. Use a full response for: batch processing (no user waiting), when you need to parse the entire JSON response before acting, or when total latency is lower than streaming overhead.

Related content: