Table of Contents
- Course Overview
- Key Technologies
- Prerequisites and Installation
- Module 1 — Measuring Agent Quality
- Module 2 — From Custom Metrics to Production Dashboards
- Overall Pipeline Architecture
- Best Practices
- Common Challenges and Solutions
Course Overview
This course covers essential techniques for evaluating and optimizing LLM (Large Language Model) agents in production environments, focusing on performance, reliability, and cost-effectiveness.
What You Will Learn
- Evaluate LLM agents with industry-standard tools (G-Eval, DeepEval, LangSmith)
- Apply comprehensive metrics for quality, cost, and latency evaluation
- Build custom evaluation tests and benchmarks
- Optimize agent performance for real production deployment
- Set up monitoring and observability for LLM agents
Course Structure
Module 1: Measuring Agent Quality (23m 7s)
├── Clip 1: Why and What to Measure
└── Clip 2: Advanced Evaluation with G-Eval and DeepEval
Module 2: From Custom Metrics to Production Dashboards (19m 49s)
├── Clip 1: Task-specific Testing and Open-RAG-Eval
└── Clip 2: Holistic Tuning + LangSmith Observability
Key Technologies
| Tool | Role |
|---|---|
| DeepEval | Real-time evaluation framework (relevance, hallucination) |
| G-Eval | LLM-as-a-Judge method (GPT-4o as judge) |
| Open-RAG-Eval | Custom evaluation for RAG pipelines |
| LangSmith | Observability, traces, costs, and production metrics |
| LangChain | Framework for LLM applications |
| Streamlit | Web interface for interactive demos |
| Chroma | Vector database for embeddings |
| OpenAI API | GPT models and embeddings |
Prerequisites and Installation
git clone https://github.com/example/Evaluate-Optimize-LLM-Agents.git
cd Evaluate-Optimize-LLM-Agents
python -m venv venv
source venv/bin/activate # Windows: venv\Scripts\activate
pip install -r requirements.txt
API Keys Configuration
OPENAI_API_KEY=your_openai_api_key_here
LANGCHAIN_API_KEY=your_langsmith_api_key_here
LANGCHAIN_TRACING_V2=true
LANGCHAIN_PROJECT=your_project_name
Never commit API keys to version control. Always use environment variables.
Module 1 — Measuring Agent Quality
1.1 Why and What to Measure
5 essential reasons to evaluate:
- Trust, quality, and adoption are directly linked — no trust → no usage → no value
- LLMs are probabilistic by nature — results vary even with excellent prompts
- “You can’t optimize what you don’t measure” — without metrics, you guess instead of guide
- Hallucinations and off-topic responses quickly erode credibility
- Evaluation creates a feedback loop to detect regressions and continuously improve
Key Metrics to Measure
| Metric | Description |
|---|---|
| Relevance | Does the response directly address the question? |
| Correctness | Is the content factually correct? |
| Hallucination rate | How often does the agent invent facts? |
| Contextual fit | Is the response supported by the retrieved context? |
Evaluation Approach Comparison
graph LR
subgraph Traditional Methods
A[BLEU] --> D[Token overlap comparison]
B[ROUGE] --> D
end
subgraph Semantic Methods
E[Vector Embeddings] --> F[Cosine Similarity]
end
subgraph LLM-as-a-Judge
G[G-Eval / GPT-4o] --> H[Score 1-10 + Explainable rationale]
end
| Approach | Strengths | Weaknesses |
|---|---|---|
| BLEU/ROUGE/F1 | Fast, reproducible, no LLM needed | Doesn’t capture LLM creative variability |
| Semantic embeddings | Compares meaning, not just tokens | Not always aligned with human judgment |
| LLM-as-a-Judge (G-Eval) | Scalable, explainable, no reference labels needed | Depends on another LLM, additional cost |
1.2 Demo — DeepEval Integration
pip install deepeval
from deepeval import evaluate
from deepeval.test_case import LLMTestCase
from deepeval.metrics import AnswerRelevancyMetric, HallucinationMetric
def evaluate_response(prompt, answer, retriever):
context_docs = retriever.invoke(prompt)
context_chunks = [doc.page_content for doc in context_docs]
test_case = LLMTestCase(
input=prompt,
actual_output=answer,
retrieval_context=context_chunks
)
metrics = [
AnswerRelevancyMetric(),
HallucinationMetric()
]
result = evaluate(
test_cases=[test_case],
metrics=metrics
)
return result
DeepEval Data Flow
sequenceDiagram
participant U as User
participant S as Streamlit App
participant R as Retriever (Chroma)
participant L as LangChain LLM
participant D as DeepEval (GPT-4o)
U->>S: Asks a question
S->>R: retriever.invoke(prompt)
R-->>S: context_docs
S->>L: llm_chain.invoke(question)
L-->>S: answer
S->>D: evaluate(LLMTestCase)
D-->>S: scores + statuses
S-->>U: Response + displayed metrics
1.3 G-Eval: LLM-as-a-Judge
Instead of relying on semantic metrics like BLEU, we ask another LLM (GPT-4o) to act as an impartial judge. It examines the original question, the retrieved context, and the generated response, then assigns a score from 1 to 10 with an explanation.
G-Eval Prompt Structure:
1. ROLE: "You are an impartial evaluator..."
2. INPUTS:
• Original user question
• Context retrieved from knowledge base
• Response generated by LLM agent
3. CRITERIA: Relevance, Accuracy, Groundedness
4. OUTPUT FORMAT:
• Numeric score from 1 to 10
• Rationale in one sentence
5. temperature = 0 ← CRITICAL for reproducibility
Single Judge vs. Multi-Judge Ensemble
graph TB
subgraph Single Judge
SJ[GPT-4o] --> SR[Single score + Rationale]
end
subgraph Multi-Judge Ensemble
MJ1[GPT-4o] --> MA[Average score or vote]
MJ2[Claude] --> MA
MJ3[Gemini] --> MA
end
For critical domains (compliance, finance, healthcare), prefer a multi-judge ensemble.
1.4 Demo — G-Eval Implementation
import os
from openai import OpenAI
client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
def judge(question: str, context_docs: list, answer: str) -> str:
context_texts = [doc.page_content for doc in context_docs]
context_str = " ".join(context_texts)[:1500]
prompt = f"""You are an impartial evaluator. Rate the following response
on a scale from 1 to 10 based on:
- Relevance: Does the response directly address the question?
- Accuracy: Are the facts correct?
- Groundedness: Is the response anchored in the provided context?
Question: {question}
Retrieved context:
{context_str}
Agent response:
{answer}
Provide ONLY: "Score: X/10 - Rationale: [one sentence explanation]"
"""
response = client.chat.completions.create(
model="gpt-4o",
messages=[{"role": "user", "content": prompt}],
temperature=0 # CRITICAL: same prompt = same score every time
)
return response.choices[0].message.content
Module 2 — From Custom Metrics to Production Dashboards
2.1 Task-Specific Tests and Stress Tests
Limitations of generic metrics:
Generic metrics like accuracy and relevance scores are useful as a baseline but insufficient for enterprise use cases (regulatory compliance, citation fidelity, PII risks).
Open-RAG-Eval Use Cases:
| Use Case | Description |
|---|---|
| Robustness | Systematic tests on diverse and challenging situations |
| Domain specificity | Citation accuracy and PII compliance evaluation |
| Stress testing | Behavior under load (ambiguous queries, multi-step, edge cases) |
| CI/CD | Integration into continuous deployment pipelines |
2.2 Demo — Open-RAG-Eval
Configuration file my_rag.yaml:
evaluator:
type: TRECEvaluator
model:
type: OpenAIModel
name: gpt-4o
api_key: ${env:OPENAI_API_KEY}
results_folder: scored_results
generated_answers: "rag_results.csv"
eval_results_file: eval_results.csv
metrics:
- answer_relevancy
- hallucination
- citation_accuracy
Results generation script:
import os
import pandas as pd
from dotenv import load_dotenv
import time
load_dotenv()
DELAY_BETWEEN_QUERIES = 4 # max ~15 requests/minute
# Load documents
docs = load_txt_files("data")
retriever = ensemble_retriever_from_docs(docs, embeddings=embeddings)
chain, _ = create_full_chain(retriever, openai_api_key=os.getenv("OPENAI_API_KEY"))
df = pd.read_csv("data/queries.csv")
rows = []
for idx, q in enumerate(df["query"]):
response = chain.invoke({"question": q, "chat_history": []})
answer = response.content
context_docs = retriever.invoke(q)
context = " ".join([doc.page_content for doc in context_docs])[:1500]
time.sleep(2)
g_eval_score = judge(q, context_docs, answer)
rows.append({
"query": q,
"generated_answer": answer,
"passage_id": idx,
"passage": context_docs[0].page_content if context_docs else "",
"context": context,
"g_eval": g_eval_score
})
time.sleep(DELAY_BETWEEN_QUERIES)
pd.DataFrame(rows).to_csv("rag_results.csv", index=False)
CI/CD Integration:
name: Evaluate RAG System
on: [push, pull_request]
jobs:
evaluate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Install dependencies
run: pip install -r requirements.txt
- name: Generate RAG results
run: python generate_rag_results.py
env:
OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
- name: Check metrics (quality gates)
run: python check_metrics.py
2.3 Holistic Optimization: Cost, Latency, and Quality
The Observability Triad
graph TD
CENTER((Observability\nTriad))
CENTER --> Q[QUALITY\nRelevant, correct\nand useful responses]
CENTER --> C[COST\nToken tracking\nper response]
CENTER --> L[LATENCY\nSystem speed\nSmooth experience]
Q -.->|tradeoff| C
C -.->|tradeoff| L
L -.->|tradeoff| Q
| Dimension | What to Measure | Key Question |
|---|---|---|
| Quality | Relevance, correctness, actionability | Is the response truly useful? |
| Cost | Tokens per response, cost per request | Is the approach cost-effective at scale? |
| Latency | Average response time, P95 | Does the experience feel responsive? |
2.4 Demo — Monitoring with LangSmith
3-step integration:
# Step 1: Get a LangSmith API key (free registration at smith.langchain.com)
# Step 2: Install dependency
pip install langsmith
# Step 3: Configure in .env
LANGCHAIN_TRACING_V2=true
LANGCHAIN_API_KEY=your_langsmith_api_key
LANGCHAIN_PROJECT=rag_system
Automatic tracing — Zero additional code:
import os
os.environ["LANGCHAIN_TRACING_V2"] = "true"
os.environ["LANGCHAIN_PROJECT"] = "rag_system"
# All LangChain operations are automatically traced
chain = create_full_chain(retriever, openai_api_key=openai_api_key)
result = chain.invoke({"question": "Is it healthy to eat broccoli every day?"})
# → The full trace appears automatically in LangSmith
Custom evaluation with LangSmith client:
from langsmith import Client
from langsmith.evaluation import evaluate
client = Client()
def custom_quality_evaluator(run, example):
output = run.outputs.get("answer", "")
input_text = example.inputs.get("question", "")
score = judge(input_text, [], output)
return {
"key": "custom_quality",
"score": int(score.split("/")[0].split(": ")[1]) / 10,
"comment": score
}
results = evaluate(
lambda inputs: chain.invoke(inputs),
data="my-test-dataset",
evaluators=[custom_quality_evaluator],
experiment_prefix="rag-evaluation"
)
Overall Pipeline Architecture
flowchart LR
A[User Question] --> B[LangChain RAG Pipeline]
B --> C[Chroma Vector Store]
B --> D[OpenAI GPT-4o]
C --> E[Generated Response]
D --> E
E --> F[DeepEval\nReal-time scoring]
E --> G[G-Eval\nLLM-as-a-Judge]
E --> H[LangSmith\nObservability]
F --> I[Quality Dashboard]
G --> I
H --> I
Best Practices
| Aspect | Recommendation |
|---|---|
| Clear criteria | Explicitly define evaluation criteria |
| Regular updates | Keep evaluations current with business needs |
| Multiple perspectives | Combine automated metrics, LLM-judge, and human feedback |
| Continuous improvement | Iterate on evaluation design |
| Security | Never commit API keys; always use environment variables |
Common Challenges and Solutions
| Challenge | Solution | Best Practice |
|---|---|---|
| High trace volume | Implement sampling and filtering | Smart sampling strategies |
| Evaluation bias | Use diverse evaluation methods | Regular bias audits |
| Monitoring impact | Asynchronous monitoring | Balance depth vs performance |
| Ground truth quality | Expert annotations and consensus | Regular review and updates |
| Metric selection | Align metrics with business objectives | Start simple, add gradually |
| Context window limits | Context compression, sliding window | Semantic chunking |
Search Terms
evaluating · optimizing · llm · agents · llmops · model · governance · artificial · intelligence · generative · ai · deepeval · g-eval · measure · metrics · quality