Callbacks, Caching, Rate Limits & LangSmith
Your app works in Colab. Now make it ready for real users: see what happens inside every call, stop paying twice for the same question, stay inside Groq’s free-tier limits, answer many users at once, and trace everything in LangSmith. These five extras are small code changes with a big effect on cost and reliability.
In this session
- Callbacks: hooks on LLM start, end, error and new tokens
- LLM cache: same question → no new API call
- Rate limiter: stay inside requests-per-minute limits
- Async gather and batch(max_concurrency)
- LangSmith tracing with 3 environment variables
- Production checklist
01The big idea: a dashboard, a memory and a speed limit
Think of a car on the Mumbai–Pune expressway. Callbacks are the dashboard: speed, fuel, warnings. The cache is remembering the route, so you don’t ask Google Maps again. The rate limiter is the speed limit, so you don’t get fined (HTTP 429). LangSmith is the dashcam: a full recording of every trip, for when something goes wrong.
Ask the same question twice with the cache on: the second answer is instant and free. Switch the cache off and the API is called every time. (Cost per call is an example value.)
02The five extras
| Extra | Code | Saves you from |
|---|---|---|
| Callback | llm.invoke(q, config={"callbacks": [MyLogger()]}) | not knowing time and tokens per call |
| Cache | set_llm_cache(InMemoryCache()) or SQLiteCache("cache.db") | paying twice for the same prompt |
| Rate limiter | ChatGroq(..., rate_limiter=InMemoryRateLimiter(requests_per_second=0.5)) | 429 “too many requests” errors |
| Async / batch | await asyncio.gather(*[llm.ainvoke(q) …]), llm.batch(qs, config={"max_concurrency": 2}) | slow one-by-one loops |
| LangSmith | LANGSMITH_TRACING=true + API key + project | debugging blind |
Times for 5 questions from the hands-on run below (each fake call takes 0.8 s). Real Groq calls behave the same way.
os.environ["LANGSMITH_TRACING"] = "true"
os.environ["LANGSMITH_API_KEY"] = userdata.get("langsmith_key") # free at smith.langchain.com
os.environ["LANGSMITH_PROJECT"] = "langchain-handson"
chain.invoke({"topic": "vector database"},
config={"run_name": "explain-topic", "tags": ["demo"], "metadata": {"user": "ajeet"}})
# open smith.langchain.com → every step with inputs, outputs, tokens, latency and errors03Hands-on: run it here
Callbacks, token streaming, cache, rate limiter and concurrency, timed for real. The stand-in waits 0.8 s per call, like a network round trip, so you can see the cache and concurrency effects.
import time, asyncio
from standins import RuleModel
from langchain_core.callbacks import BaseCallbackHandler
from langchain_core.caches import InMemoryCache
from langchain_core.globals import set_llm_cache
from langchain_core.rate_limiters import InMemoryRateLimiter
# a stand-in with 0.8 s of fake network delay, so timings look like a real API
llm = RuleModel(default="India has 22 official languages listed in the Constitution.", delay=0.8)
class MyLogger(BaseCallbackHandler):
def on_chat_model_start(self, serialized, messages, **kwargs):
self.start = time.time(); print("🚀 LLM started")
def on_llm_end(self, response, **kwargs):
usage = response.generations[0][0].message.usage_metadata
print(f"✅ LLM finished in {time.time() - self.start:.1f}s | tokens: {usage}")
llm.invoke("Tell me a fun fact about India", config={"callbacks": [MyLogger()]})
class PrintTokens(BaseCallbackHandler):
def on_llm_new_token(self, token, **kwargs): print(token, end="|")
print("tokens: ", end="")
for _ in RuleModel(default="Snow peaks touch the sky.").stream("Himalayas", config={"callbacks": [PrintTokens()]}): pass
print()
set_llm_cache(InMemoryCache())
for i in (1, 2):
t = time.time(); llm.invoke("What is the capital of Japan?")
print(f"call {i}: {time.time() - t:.1f} s" + (" (from cache, no API cost)" if i == 2 else ""))
set_llm_cache(None)
limited = RuleModel(default="hi", rate_limiter=InMemoryRateLimiter(requests_per_second=0.5, check_every_n_seconds=0.1, max_bucket_size=1))
start = time.time()
for i in range(3):
limited.invoke(f"hi {i}"); print(f"rate-limited call {i + 1} finished at {time.time() - start:.0f} s")
questions = [f"Give one fact about number {i}" for i in range(1, 6)]
t = time.time(); [llm.invoke(q) for q in questions]; seq = time.time() - t
async def main(): return await asyncio.gather(*[llm.ainvoke(q) for q in questions])
t = time.time(); asyncio.run(main()); par = time.time() - t
t = time.time(); llm.batch(questions, config={"max_concurrency": 2}); b2 = time.time() - t
print(f"5 questions: one by one {seq:.0f} s | async gather {par:.0f} s | batch(max_concurrency=2) {b2:.0f} s")RuleModel or ScriptedModel from standins.py), so every output below is from a real run of this code. In Colab you swap one line: llm = ChatGroq(model="openai/gpt-oss-20b").04Hands-on: Ajeetkumar’s Colab notebook
groq_key with your free key from console.groq.com, switch on Notebook access, then run the cells from the top.05Production checklist
| ✅ | How |
|---|---|
| API keys in secrets, never in code | Colab Secrets, .env, a cloud secret manager |
| timeout, max_retries, fallbacks | ChatGroq(timeout=30, max_retries=2), .with_fallbacks() |
| cache repeated calls, rate-limit free tiers | this lab |
| trace with LangSmith, log tokens and cost | this lab |
| test prompts on a fixed question set before changing them | LangSmith datasets and evaluations |
06Summary
- Callbacks hook into start, end, error and new-token events.
set_llm_cachemakes repeated identical calls instant and free.InMemoryRateLimiterspaces out calls to respect provider limits.- Async gather and
batch(max_concurrency=…)serve many requests at once. - LangSmith tracing needs only environment variables.
07Quick quiz
1. Same question asked twice with InMemoryCache on. Second call?
2. requests_per_second=0.5 means…
3. Which callback method prints streamed tokens?
4. How do you switch on LangSmith tracing?
5. When should you NOT cache?
08References
- LangChain: Callbacks
- LangChain: Chat model caching
- LangSmith
- Ajeetkumar’s notebook: LC_15_Callbacks_Caching_RateLimit_LangSmith.ipynb (download)