diff --git a/.github/workflows/skills-drift.yml b/.github/workflows/skills-drift.yml new file mode 100644 index 0000000..2f986c1 --- /dev/null +++ b/.github/workflows/skills-drift.yml @@ -0,0 +1,27 @@ +name: skills-drift + +# Fails if the vendored shared skills (skills/opik, skills/agent-ops) have drifted +# from the source of truth (comet-ml/opik-mcp) at the pinned CANON_REF. +# No-ops while CANON_REF is UNPINNED (OPIK-7471, until skills land in opik-mcp). + +on: + pull_request: + paths: ["skills/**", "scripts/sync-shared-skills.sh", ".github/workflows/skills-drift.yml"] + push: + branches: [main] + +jobs: + drift: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Re-vendor and check for drift + run: | + bash scripts/sync-shared-skills.sh + if ! git diff --quiet -- skills; then + echo "::error::Vendored shared skills differ from source (comet-ml/opik-mcp)." + echo "Run 'CANON_REF= bash scripts/sync-shared-skills.sh' and commit the result." + git --no-pager diff --stat -- skills + exit 1 + fi + echo "no drift" diff --git a/scripts/sync-shared-skills.sh b/scripts/sync-shared-skills.sh new file mode 100755 index 0000000..01e27c2 --- /dev/null +++ b/scripts/sync-shared-skills.sh @@ -0,0 +1,38 @@ +#!/usr/bin/env bash +# Vendor the shared Opik skills from the canonical source (OPIK-7471). +# +# Source of truth: comet-ml/opik-mcp (src/opik_mcp/skills). The skills listed in +# SHARED are OWNED THERE — do not hand-edit them in this repo; edit the source +# and re-sync. This repo keeps its own non-shared assets (commands/, agents/, +# hooks/, the logger). +# +# Usage: CANON_REF= bash scripts/sync-shared-skills.sh +set -euo pipefail + +CANON_REPO="${CANON_REPO:-https://github.com/comet-ml/opik-mcp.git}" +CANON_REF="${CANON_REF:-UNPINNED}" # bump to a canonical commit/tag to actually sync +SRC="src/opik_mcp/skills" # skills path within opik-mcp +DEST="skills" +SHARED=(opik evaluate) + +if [ "$CANON_REF" = "UNPINNED" ]; then + echo "CANON_REF is UNPINNED — set it to an opik-mcp ref once the skills have landed" + echo "there (OPIK-7471, opik-mcp#154). Nothing to sync yet; exiting cleanly." + exit 0 +fi + +tmp="$(mktemp -d)" +trap 'rm -rf "$tmp"' EXIT +git clone --quiet "$CANON_REPO" "$tmp" +git -C "$tmp" checkout --quiet "$CANON_REF" + +for s in "${SHARED[@]}"; do + if [ ! -d "$tmp/$SRC/$s" ]; then + echo "skip '$s' — not yet in opik-mcp@$CANON_REF" + continue + fi + rm -rf "${DEST:?}/$s" + cp -R "$tmp/$SRC/$s" "$DEST/$s" + echo "synced $s" +done +echo "Done. Vendored ${SHARED[*]} from opik-mcp@$CANON_REF" diff --git a/skills/SHARED.md b/skills/SHARED.md new file mode 100644 index 0000000..ab356ca --- /dev/null +++ b/skills/SHARED.md @@ -0,0 +1,13 @@ +# Shared skills are vendored — don't hand-edit them + +`skills/opik/` and `skills/agent-ops/` are **vendored from the source of truth**, +`comet-ml/opik-mcp` (`src/opik_mcp/skills`, OPIK-7471). Do not edit them here — +edit them in `opik-mcp` and re-sync: + +```bash +CANON_REF= bash scripts/sync-shared-skills.sh +``` + +A CI drift check (`.github/workflows/skills-drift.yml`) fails if these directories +diverge from the pinned source ref. This repo still owns everything else: +`commands/`, `agents/`, `hooks/`, and the session logger. diff --git a/skills/agent-ops/SKILL.md b/skills/agent-ops/SKILL.md deleted file mode 100644 index a88cde6..0000000 --- a/skills/agent-ops/SKILL.md +++ /dev/null @@ -1,106 +0,0 @@ ---- -name: agent-ops -description: This skill should be used when the user asks about agent architecture, evaluation, metrics, production monitoring, debugging agents, or best practices for building reliable AI agents. Use for questions like "evaluate my agent", "set up production monitoring", "add guardrails", "detect hallucinations", "agent anti-patterns", "compare experiments", "create evaluation dataset". ---- - -# Agent Operations: Build, Evaluate, and Monitor AI Agents - -This skill covers the agent lifecycle beyond basic tracing: architecture patterns, evaluation, metrics, and production monitoring. All examples use Opik for observability — for SDK details (tracing, integrations, span types), load the `opik` skill. - -## The Agent Lifecycle - -1. **Instrument** — Add Opik tracing to make your agent's behavior visible (see `opik` skill) -2. **Evaluate** — Measure performance with datasets, metrics, and experiments -3. **Monitor** — Track quality, cost, and reliability in production -4. **Optimize** — Improve based on data from evaluation and production traces - -## Agent Architecture Patterns - -Trace every component of your agent with appropriate span types: - -```python -import opik - -@opik.track(name="research_agent") -def agent(query: str) -> str: - plan = plan_action(query) # general span - results = execute_tool(plan) # tool span - return generate_response(results) # llm span - -@opik.track(type="tool") -def execute_tool(action: dict) -> str: - return search_web(action["query"]) - -@opik.track(type="llm") -def generate_response(context: str) -> str: - return llm_call(context) -``` - -### What to Trace - -| Component | Span Type | Key Data | -|-----------|-----------|----------| -| Planning | `general` | Reasoning steps, decisions | -| Tool calls | `tool` | Tool name, parameters, results | -| LLM calls | `llm` | Prompt, response, tokens | -| Retrieval | `tool` | Query, documents | -| Validation | `guardrail` | Check results, pass/fail | - -## Evaluation - -Evaluate agents at multiple levels — end-to-end and per-component: - -```python -from opik.evaluation import evaluate -from opik.evaluation.metrics import AnswerRelevance, Hallucination, AgentTaskCompletion - -results = evaluate( - experiment_name="agent-v2", - dataset=dataset, - task=lambda item: {"output": agent(item["input"])}, - scoring_metrics=[ - AnswerRelevance(), - Hallucination(), - AgentTaskCompletion(), - ] -) -``` - -### Built-in Agent Metrics - -| Metric | What It Measures | -|--------|-----------------| -| `AgentTaskCompletion` | Did the agent fulfill its task? | -| `AgentToolCorrectness` | Were tools used correctly? | -| `TrajectoryAccuracy` | Did actions match expected sequence? | -| `AnswerRelevance` | Does the answer address the question? | -| `Hallucination` | Are there unsupported claims? | - -### 41 Total Built-in Metrics - -Heuristic (Equals, Contains, BLEU, ROUGE, BERTScore, IsJson, etc.), LLM-as-Judge (AnswerRelevance, Hallucination, Usefulness, GEval, etc.), RAG (ContextPrecision, ContextRecall, Faithfulness), and conversation metrics. See `references/evaluation.md` for the full list. - -## Production Monitoring - -- **Dashboards** — Visualize quality, cost, latency, and error trends -- **Online evaluation** — Automatically score production traces with LLM-as-Judge -- **Alerts** — Get notified when metrics deviate (quality drops, cost spikes, error rates) -- **Guardrails** — PII detection, topic validation, custom safety checks -- **Opik Assist** — AI-powered root cause analysis for failed traces - -## Common Anti-Patterns - -| Category | Anti-Pattern | -|----------|-------------| -| Reliability | Unbounded loops, retry storms, silent failures | -| Security | Prompt injection, privilege escalation, data leakage | -| Observability | Late tracing (missing input), orphaned spans | -| Tools | Tool loops, hallucinated tools, parameter errors | - -## Detailed References - -| Topic | Reference File | -|-------|----------------| -| Agent architecture, reliability, security patterns | `references/agent-patterns.md` | -| Evaluation datasets, experiments, all 41 metrics | `references/evaluation.md` | -| Production dashboards, alerts, guardrails, cost tracking | `references/production.md` | diff --git a/skills/agent-ops/references/agent-patterns.md b/skills/agent-ops/references/agent-patterns.md deleted file mode 100644 index e60acdc..0000000 --- a/skills/agent-ops/references/agent-patterns.md +++ /dev/null @@ -1,630 +0,0 @@ -# Agent Architecture Patterns - -Best practices for building, evaluating, and optimizing AI agents with Opik. - -## The Agent Lifecycle - -Building production-grade agents requires: -1. **Observability** - Understand what your agent is doing -2. **Evaluation** - Measure performance systematically -3. **Optimization** - Improve based on data - -## Start with Observability - -Before evaluating, make your agent's behavior transparent. - -### Critical: Trace from Input - -**Tracing must start at the agent entry point, before any processing begins.** This ensures: -- The exact input is captured as received -- Traces can be replayed for debugging -- Full execution context is preserved - -```python -import opik - -# ✅ CORRECT: @track on the entry point captures the original input -@opik.track(name="research_agent") -def agent(query: str) -> str: - """Entry point - trace starts here with exact input""" - plan = plan_action(query) - results = execute_tool(plan) - return generate_response(query, results) - -# ❌ WRONG: Starting trace after preprocessing loses original input -def agent(query: str) -> str: - processed = preprocess(query) # Input transformation lost! - return _traced_agent(processed) # Trace misses original query -``` - -### Enabling Replay - -For traces to support replay (re-running from a trace for debugging): - -```python -@opik.track(name="research_agent") -def agent(query: str, config: dict = None) -> str: - # Capture config/env state that affects execution - opik.opik_context.update_current_trace( - metadata={ - "config": config, - "feature_flags": get_feature_flags(), - "model_version": MODEL_VERSION - } - ) - # Now the trace has everything needed for replay - return execute_agent(query, config) -``` - -### Basic Agent Tracing - -```python -import opik - -@opik.track -def plan_action(query: str) -> dict: - """Agent planning step""" - return {"action": "search", "params": {"query": query}} - -@opik.track(type="tool") -def execute_tool(action: dict) -> str: - """Tool execution""" - if action["action"] == "search": - return search_web(action["params"]["query"]) - -@opik.track -def generate_response(query: str, tool_results: str) -> str: - """Final response generation""" - return llm_call(f"Query: {query}\nResults: {tool_results}") - -@opik.track(name="research_agent") -def agent(query: str) -> str: - plan = plan_action(query) - results = execute_tool(plan) - return generate_response(query, results) -``` - -### What to Trace - -| Component | Span Type | Key Data | -|-----------|-----------|----------| -| Planning | `general` | Reasoning steps, decisions | -| Tool calls | `tool` | Tool name, parameters, results | -| LLM calls | `llm` | Prompt, response, tokens | -| Retrieval | `tool` | Query, documents | -| Validation | `guardrail` | Check results, pass/fail | - -## Evaluating Agents - -Agent evaluation goes beyond final outputs—you need to assess the journey. - -### End-to-End Evaluation - -Evaluate the final response quality: - -```python -from opik.evaluation import evaluate -from opik.evaluation.metrics import AnswerRelevance, Hallucination - -def agent_task(dataset_item): - response = agent(dataset_item["input"]) - return {"output": response} - -results = evaluate( - experiment_name="agent-e2e-v1", - dataset=dataset, - task=agent_task, - scoring_metrics=[ - AnswerRelevance(), - Hallucination() - ] -) -``` - -### Step-Level Evaluation - -Evaluate individual agent decisions: - -#### Tool Selection Evaluation - -```python -from opik.evaluation.metrics import BaseMetric, ScoreResult - -class ToolSelectionQuality(BaseMetric): - def __init__(self): - self.name = "tool_selection_quality" - - def score(self, tool_calls, expected_tool_calls, **kwargs): - actual = tool_calls[0]["function_name"] if tool_calls else None - expected = expected_tool_calls[0]["function_name"] if expected_tool_calls else None - - if actual == expected: - return ScoreResult( - name=self.name, - value=1.0, - reason=f"Correct tool: {actual}" - ) - return ScoreResult( - name=self.name, - value=0.0, - reason=f"Expected {expected}, got {actual}" - ) -``` - -#### Trajectory Evaluation - -Use `task_span` parameter for trajectory access: - -```python -from opik.evaluation.metrics import BaseMetric, ScoreResult -from opik.message_processing.emulation.models import SpanModel - -class StrictToolAdherenceMetric(BaseMetric): - def __init__(self): - self.name = "strict_tool_adherence" - - def find_tools(self, task_span: SpanModel) -> list: - """Extract tool names from span hierarchy""" - tools = [] - - def extract(spans): - for span in spans: - if span.type == "tool": - tools.append(span.name) - if span.spans: - extract(span.spans) - - if task_span.spans: - extract(task_span.spans) - return tools - - def score(self, task_span: SpanModel, expected_tool: list, **kwargs): - actual = self.find_tools(task_span) - - if actual == expected_tool: - return ScoreResult( - name=self.name, - value=1.0, - reason=f"Correct trajectory: {actual}" - ) - return ScoreResult( - name=self.name, - value=0.0, - reason=f"Expected {expected_tool}, got {actual}" - ) -``` - -### Built-in Agent Metrics - -| Metric | Description | -|--------|-------------| -| `AgentTaskCompletion` | Did the agent fulfill its task? | -| `AgentToolCorrectness` | Were tools used with correct parameters? | -| `TrajectoryAccuracy` | Did actions match expected sequence? | - -## Evaluation Dataset Design - -### Tool Selection Dataset - -```python -dataset.insert([ - { - "input": "What is 25 * 17?", - "expected_tool": ["calculator"] - }, - { - "input": "What's the weather in Paris?", - "expected_tool": ["weather_api"] - }, - { - "input": "Tell me a joke", - "expected_tool": [] # No tool needed - } -]) -``` - -### Multi-Step Dataset - -```python -dataset.insert([ - { - "input": "Book a flight and hotel for NYC", - "expected_trajectory": [ - {"tool": "search_flights", "params": {"destination": "NYC"}}, - {"tool": "search_hotels", "params": {"city": "NYC"}}, - {"tool": "book_flight"}, - {"tool": "book_hotel"} - ] - } -]) -``` - -## Evaluating Different Components - -### What to Evaluate - -| Component | Metrics | Dataset Fields | -|-----------|---------|----------------| -| Router/Planner | Tool selection, plan quality | Expected tools/plan | -| Tools | Output accuracy, error rate | Expected tool output | -| Memory/RAG | Relevance, recall | Expected context | -| Response | Quality, hallucination | Expected response | - -### Component-Specific Evaluation - -```python -# Router evaluation -router_results = evaluate( - experiment_name="router-v1", - dataset=router_dataset, - task=router_task, - scoring_metrics=[ToolSelectionQuality()] -) - -# Tool evaluation -tool_results = evaluate( - experiment_name="tools-v1", - dataset=tool_dataset, - task=tool_task, - scoring_metrics=[ExactMatch(), ErrorRate()] -) - -# End-to-end evaluation -e2e_results = evaluate( - experiment_name="agent-v1", - dataset=e2e_dataset, - task=agent_task, - scoring_metrics=[ - AnswerRelevance(), - AgentTaskCompletion(), - TrajectoryAccuracy() - ] -) -``` - -## Multi-Agent Systems - -### Tracing Multi-Agent Workflows - -```python -import opik - -@opik.track(name="orchestrator") -def orchestrator(query: str) -> str: - # Decide which agent to use - agent_type = classify_query(query) - - if agent_type == "research": - return research_agent(query) - elif agent_type == "code": - return code_agent(query) - else: - return general_agent(query) - -@opik.track(name="research_agent") -def research_agent(query: str) -> str: - # Research-specific logic - pass - -@opik.track(name="code_agent") -def code_agent(query: str) -> str: - # Code-specific logic - pass -``` - -### Evaluating Agent Routing - -```python -class RoutingAccuracy(BaseMetric): - def __init__(self): - self.name = "routing_accuracy" - - def score(self, selected_agent, expected_agent, **kwargs): - if selected_agent == expected_agent: - return ScoreResult( - name=self.name, - value=1.0, - reason=f"Correctly routed to {selected_agent}" - ) - return ScoreResult( - name=self.name, - value=0.0, - reason=f"Routed to {selected_agent}, expected {expected_agent}" - ) -``` - -## Reliability Patterns - -### Idempotency - -Ensure operations can be safely retried: - -```python -@opik.track(type="tool") -def create_order(order_data: dict, idempotency_key: str) -> dict: - """Tool with idempotency key for safe retries""" - # Check if already processed - existing = db.get_order_by_idempotency_key(idempotency_key) - if existing: - return existing - - # Process and store with key - order = process_order(order_data) - order["idempotency_key"] = idempotency_key - db.save_order(order) - return order -``` - -### Retry with Backoff - -```python -import time -import random - -def retry_with_backoff(func, max_attempts=3, base_delay=1.0): - """Exponential backoff with jitter""" - for attempt in range(max_attempts): - try: - return func() - except TransientError as e: - if attempt == max_attempts - 1: - raise - delay = base_delay * (2 ** attempt) + random.uniform(0, 1) - time.sleep(delay) -``` - -### Circuit Breaker - -```python -class CircuitBreaker: - def __init__(self, failure_threshold=5, recovery_time=60): - self.failures = 0 - self.threshold = failure_threshold - self.recovery_time = recovery_time - self.last_failure = None - self.state = "closed" # closed, open, half-open - - def call(self, func): - if self.state == "open": - if time.time() - self.last_failure > self.recovery_time: - self.state = "half-open" - else: - raise CircuitOpenError("Circuit breaker is open") - - try: - result = func() - if self.state == "half-open": - self.state = "closed" - self.failures = 0 - return result - except Exception as e: - self.failures += 1 - self.last_failure = time.time() - if self.failures >= self.threshold: - self.state = "open" - raise -``` - -## Security Patterns - -### Input Validation - -```python -@opik.track(name="secure_agent") -def agent(query: str) -> str: - # Validate input before processing - if not is_safe_input(query): - return "I cannot process that request." - - # Sanitize external content - context = retrieve_context(query) - sanitized_context = sanitize_external_content(context) - - return generate_response(query, sanitized_context) - -def sanitize_external_content(content: str) -> str: - """Remove potential prompt injection from retrieved content""" - # Strip instruction-like patterns from external data - patterns = [r"ignore previous", r"system:", r"<\|.*\|>"] - for pattern in patterns: - content = re.sub(pattern, "", content, flags=re.IGNORECASE) - return content -``` - -### Tool Permission Boundaries - -```python -# Define allowed tools per context -TOOL_PERMISSIONS = { - "read_only": ["search", "get_info", "list_items"], - "write": ["search", "get_info", "list_items", "create", "update"], - "admin": ["search", "get_info", "list_items", "create", "update", "delete"] -} - -@opik.track(name="permission_aware_agent") -def agent(query: str, permission_level: str = "read_only") -> str: - allowed_tools = TOOL_PERMISSIONS[permission_level] - - # Pass allowed tools to agent, reject others - return execute_with_tools(query, allowed_tools) -``` - -## Resource Management - -### Token Budgets - -```python -@opik.track(name="budget_aware_agent") -def agent(query: str, max_tokens: int = 10000) -> str: - tokens_used = 0 - - while not done and tokens_used < max_tokens: - response, tokens = llm_call_with_count(prompt) - tokens_used += tokens - - if tokens_used > max_tokens * 0.9: - # Approaching limit, wrap up - return generate_summary(partial_results) - - opik.opik_context.update_current_trace( - metadata={"tokens_used": tokens_used} - ) - return final_response -``` - -### Execution Limits - -```python -MAX_STEPS = 20 -MAX_TOOL_CALLS = 10 - -@opik.track(name="bounded_agent") -def agent(query: str) -> str: - steps = 0 - tool_calls = 0 - - while not done: - steps += 1 - if steps > MAX_STEPS: - return "Reached maximum steps, returning partial result." - - action = plan_next_action() - if action["type"] == "tool": - tool_calls += 1 - if tool_calls > MAX_TOOL_CALLS: - return "Reached tool call limit." - execute_tool(action) -``` - -## Common Anti-Patterns - -### Reliability Anti-Patterns - -1. **Unbounded loops**: No maximum steps or circuit breaker -2. **Tool loops**: Agent repeatedly calls same tool without progress -3. **Retry storms**: Cascading failures causing exponential retries -4. **No backoff**: Immediate retries overwhelming services -5. **Silent failures**: Errors swallowed without logging - -### Security Anti-Patterns - -6. **Prompt injection**: User input directly in system prompts -7. **Indirect injection**: External content (web, docs) unsanitized -8. **Privilege escalation**: Agent accessing tools beyond scope -9. **Data leakage**: Sensitive info in logs or responses - -### Observability Anti-Patterns - -10. **Late tracing**: Trace starts after agent begins, missing input -11. **Input mismatch**: Trace input differs from actual input (breaks replay) -12. **Orphaned spans**: Spans without parent trace - -### Tool Anti-Patterns - -13. **Tool loops**: Agent repeatedly calls same tool -14. **Hallucinated tools**: Agent invents non-existent tools -15. **Parameter errors**: Wrong types or missing required params -16. **Inefficient paths**: Taking more steps than necessary -17. **Context loss**: Forgetting information across turns - -### Detection Metrics - -```python -class LoopDetection(BaseMetric): - def __init__(self, max_repeats: int = 3): - self.name = "loop_detection" - self.max_repeats = max_repeats - - def score(self, task_span, **kwargs): - tools = self.find_tools(task_span) - - # Check for repeated consecutive tools - for i in range(len(tools) - self.max_repeats + 1): - window = tools[i:i + self.max_repeats] - if len(set(window)) == 1: # All same tool - return ScoreResult( - name=self.name, - value=0.0, - reason=f"Detected loop: {window[0]} repeated {self.max_repeats} times" - ) - - return ScoreResult( - name=self.name, - value=1.0, - reason="No loops detected" - ) -``` - -## Iterative Improvement - -### The Evaluation Loop - -1. **Run baseline evaluation** -2. **Analyze failures** - Filter to low-scoring items -3. **Identify patterns** - What's causing failures? -4. **Make improvements**: - - Refine system prompt - - Improve tool descriptions - - Add/remove tools - - Adjust parameters -5. **Re-evaluate** - Measure impact -6. **Compare experiments** - Verify improvement -7. **Repeat** - -### Comparing Experiments - -In the Opik UI: -1. Go to dataset experiments -2. Select experiments to compare -3. View metric differences -4. Drill into specific failures -5. Document what changed - -## Best Practices - -### Observability - -- **Trace from input**: Start tracing at agent entry point, not after processing -- **Capture for replay**: Include config, feature flags, model versions in trace metadata -- Trace all agent components with appropriate span types -- Add metadata for filtering and debugging -- Include tool parameters and results - -### Evaluation - -- Start with end-to-end metrics -- Add component-level as needed -- Build datasets from production failures -- Run evaluations before deploying changes -- Detect anti-patterns (loops, hallucinations) with custom metrics - -### Reliability - -- **Idempotency**: Use idempotency keys for all mutating operations -- **Retries**: Exponential backoff with jitter, capped attempts -- **Circuit breakers**: Prevent cascade failures to downstream services -- **Timeouts**: Set per-call and total execution timeouts -- **Graceful degradation**: Return partial results when limits reached - -### Security - -- Validate and sanitize all inputs at agent boundary -- Sanitize external content (retrieved docs, web pages) before processing -- Apply least-privilege tool access based on context -- Never log sensitive data (PII, credentials) -- Protect against indirect prompt injection - -### Resource Management - -- Set token budgets per request and session -- Limit maximum steps and tool calls -- Cache repeated queries -- Use appropriate model size for task complexity -- Monitor costs and set alerts - -### Optimization - -- Change one variable at a time -- Track experiment configurations -- Use data to guide decisions -- Monitor production performance diff --git a/skills/agent-ops/references/evaluation.md b/skills/agent-ops/references/evaluation.md deleted file mode 100644 index 57818fb..0000000 --- a/skills/agent-ops/references/evaluation.md +++ /dev/null @@ -1,595 +0,0 @@ -# Evaluation & Metrics Guide - -Comprehensive guide to evaluating LLM applications with Opik's evaluation platform. - -## Why Evaluation Matters - -Manual review of LLM outputs doesn't scale. Opik's evaluation platform automates quality assessment with: -- **Reproducible experiments** across datasets -- **Quantitative metrics** for objective comparison -- **Historical tracking** to measure improvement -- **Side-by-side comparison** of different approaches - -## Core Concepts - -### Datasets - -A **dataset** is a collection of test cases for evaluating your LLM application. - -Each dataset item contains: -- **Input**: The query/prompt to send to your application -- **Expected output** (optional): The ground truth or reference answer -- **Custom fields**: Any additional context needed for evaluation - -### Experiments - -An **experiment** is a single evaluation run that: -1. Processes each dataset item through your LLM application -2. Computes the actual output -3. Scores the output using one or more metrics -4. Logs results for analysis - -## Creating Datasets - -### Via Python SDK - -```python -from opik import Opik - -client = Opik() -dataset = client.get_or_create_dataset(name="my-evaluation-dataset") - -# Insert items -dataset.insert([ - { - "input": "What is the capital of France?", - "expected_output": "Paris" - }, - { - "input": "Explain quantum computing in simple terms", - "expected_output": "Quantum computing uses quantum mechanics..." - } -]) -``` - -### From Production Traces - -In the Opik UI: -1. Go to your project's traces -2. Select traces you want to use -3. Click "Add to dataset" in Actions dropdown - -### From CSV/JSON - -Upload files directly through the Opik UI or API. - -### From Pandas DataFrame - -```python -import pandas as pd -from opik import Opik - -client = Opik() -dataset = client.get_or_create_dataset(name="from-pandas") - -df = pd.DataFrame({ - "input": ["What is ML?", "Explain AI"], - "expected_output": ["Machine learning is...", "AI is..."] -}) - -dataset.insert_from_pandas(df) -``` - -### From JSONL Files - -```python -dataset.insert_from_jsonl("path/to/data.jsonl") -``` - -## Dataset Versioning - -Opik supports immutable dataset versions for reproducible evaluations. - -### Creating Versions - -```python -from opik import Opik - -client = Opik() -dataset = client.get_dataset(name="my-dataset") - -# Create a named version (immutable snapshot) -version = dataset.create_version(name="v1.0") - -# List all versions -versions = dataset.list_versions() -for v in versions: - print(f"{v.name}: {v.item_count} items, created {v.created_at}") -``` - -### Using Specific Versions - -```python -# Run evaluation on a specific version -results = evaluate( - experiment_name="test-v1", - dataset=dataset, - dataset_version="v1.0", # Pin to specific version - task=evaluation_task, - scoring_metrics=[AnswerRelevance()] -) -``` - -### Version History - -In the UI: -1. Go to dataset details -2. Click "Versions" tab -3. View version history with timestamps -4. Compare versions side-by-side -5. Restore or duplicate from any version - -## AI Expansion (Synthetic Data) - -Generate synthetic test data to expand your datasets. - -### Using AI Expansion - -In the Opik UI: -1. Go to your dataset -2. Click "AI Expansion" -3. Select seed examples (optional) -4. Configure expansion parameters: - - Number of new items - - Diversity settings - - Topic constraints -5. Review and approve generated items - -### Programmatic Expansion - -```python -from opik import Opik - -client = Opik() -dataset = client.get_dataset(name="my-dataset") - -# Generate synthetic variations -dataset.expand( - num_items=50, - seed_items=dataset.get_items()[:5], # Use 5 examples as seeds - diversity="high", - model="gpt-4" -) -``` - -## OQL: Opik Query Language - -Filter datasets and traces using OQL syntax. - -### Basic Syntax - -``` -field_name operator value -``` - -### Operators - -| Operator | Description | Example | -|----------|-------------|---------| -| `=` | Equals | `status = "success"` | -| `!=` | Not equals | `model != "gpt-3.5"` | -| `>`, `<`, `>=`, `<=` | Comparison | `score > 0.8` | -| `contains` | Substring match | `input contains "error"` | -| `in` | List membership | `tag in ["prod", "staging"]` | -| `exists` | Field exists | `metadata.user_id exists` | - -### Combining Filters - -``` -# AND (implicit) -score > 0.8 model = "gpt-4" - -# OR (explicit) -score > 0.9 OR model = "gpt-4" - -# Parentheses for grouping -(score > 0.8 AND model = "gpt-4") OR tag = "important" -``` - -### Examples - -```python -# Filter traces -traces = client.search_traces( - project_name="production", - filter='score > 0.7 AND metadata.user_type = "premium"' -) - -# Filter dataset items -items = dataset.get_items( - filter='input contains "error" AND expected_output exists' -) -``` - -## Annotation Queues - -Create review workflows for expert human evaluation. - -### Creating a Queue - -In the Opik UI: -1. Go to Project > Annotation Queues -2. Click "Create Queue" -3. Configure: - - Queue name - - Sampling rules (all traces, percentage, or filtered) - - Annotation schema (scores, labels, free text) - - Assignees - -### Annotation Schema - -Define what reviewers evaluate: - -```python -from opik import Opik - -client = Opik() - -# Create annotation queue -queue = client.create_annotation_queue( - name="quality-review", - project_name="production", - schema={ - "scores": [ - {"name": "accuracy", "type": "numeric", "min": 0, "max": 5}, - {"name": "helpfulness", "type": "numeric", "min": 0, "max": 5} - ], - "labels": [ - {"name": "category", "options": ["good", "needs_work", "bad"]} - ], - "free_text": ["comments"] - }, - sampling_rate=0.1 # Sample 10% of traces -) -``` - -### Reviewing Items - -1. Go to your annotation queue -2. Items appear based on sampling rules -3. For each item: - - View trace details - - Apply scores and labels - - Add comments - - Submit annotation -4. Progress is tracked per reviewer - -### Using Annotations - -```python -# Get annotated traces -annotated = client.search_traces( - project_name="production", - filter='annotation.queue = "quality-review" AND annotation.completed = true' -) - -# Export annotations for training -annotations = client.export_annotations( - queue_name="quality-review", - format="jsonl" -) -``` - -## Running Evaluations - -### Basic Evaluation - -```python -from opik import Opik -from opik.evaluation import evaluate -from opik.evaluation.metrics import Equals, AnswerRelevance - -client = Opik() -dataset = client.get_dataset(name="my-dataset") - -# Define the task (how to process each item) -def evaluation_task(dataset_item): - # Your LLM application logic - response = my_llm_call(dataset_item["input"]) - return {"output": response} - -# Run evaluation -results = evaluate( - experiment_name="baseline-v1", - dataset=dataset, - task=evaluation_task, - scoring_metrics=[ - Equals(), # Exact match - AnswerRelevance() # LLM-as-Judge - ] -) -``` - -### Evaluation with Context - -For RAG applications: - -```python -def evaluation_task(dataset_item): - query = dataset_item["input"] - - # Retrieve context - context = retrieve_documents(query) - - # Generate response - response = generate_with_context(query, context) - - return { - "output": response, - "context": context # Pass to metrics - } - -results = evaluate( - experiment_name="rag-v1", - dataset=dataset, - task=evaluation_task, - scoring_metrics=[ - ContextPrecision(), - ContextRecall(), - Hallucination() - ] -) -``` - -## Built-in Metrics (41 Total) - -Opik provides 41 built-in metrics organized into categories. - -### Heuristic Metrics - -Deterministic, rule-based checks that don't require LLM calls: - -**Text Similarity:** -- `Equals` - Exact string match -- `Contains` - Substring presence -- `RegexMatch` - Pattern matching -- `Levenshtein` - Edit distance -- `BLEU` - Translation quality (n-gram overlap) -- `ROUGE` - Summarization quality (recall-oriented) -- `BERTScore` - Semantic similarity using embeddings - -**Validation:** -- `IsJson` - Valid JSON check -- `JsonSchemaMatch` - Validates against JSON schema -- `Sentiment` - Sentiment analysis (-1 to 1) - -### Conversation Heuristic Metrics - -For multi-turn conversation analysis: - -- `ConversationCoherence` - Flow between turns -- `TopicDrift` - Measures topic consistency -- `ResponseLatency` - Tracks response timing patterns - -### LLM-as-Judge Metrics - -Use an LLM to evaluate semantic quality: - -**Quality Assessment:** -- `AnswerRelevance` - Does the answer address the question? -- `Hallucination` - Are there unsupported claims? -- `Usefulness` - How useful is the response? -- `MeaningMatch` - Semantic equivalence -- `Moderation` - Safety and policy violations -- `GEval` - Configurable custom criteria - -**RAG-Specific:** -- `ContextPrecision` - Is only relevant context used? -- `ContextRecall` - Is all relevant context used? -- `Faithfulness` - Does output align with provided context? - -### Conversation LLM Metrics - -For evaluating chat and dialogue quality: - -- `ConversationQuality` - Overall conversation effectiveness -- `ResponseAppropriate` - Is the response fitting for the context? -- `TurnCoherence` - Logical connection between turns - -### Agent-Specific Metrics - -For evaluating agentic behavior: - -- `AgentTaskCompletion` - Did the agent complete its task? -- `AgentToolCorrectness` - Were tools used correctly? -- `TrajectoryAccuracy` - Did the agent follow expected steps? -- `PlanningQuality` - Quality of agent's planning -- `ToolSelectionAccuracy` - Did agent pick appropriate tools? - -## Using Metrics - -### Simple Scoring - -```python -from opik.evaluation.metrics import Hallucination - -metric = Hallucination() - -result = metric.score( - input="What is the capital of France?", - output="The capital of France is Paris. It has the Eiffel Tower.", - context=["Paris is the capital of France."] -) - -print(result.value) # 0.0 (no hallucination) -print(result.reason) # Explanation -``` - -### Custom Model for LLM Metrics - -```python -from opik.evaluation.metrics import Hallucination - -# Use a different LLM as judge -metric = Hallucination(model="bedrock/anthropic.claude-3-sonnet-20240229-v1:0") -``` - -### G-Eval: Custom Criteria - -```python -from opik.evaluation.metrics import GEval - -# Define custom evaluation criteria -metric = GEval( - name="technical_accuracy", - criteria=""" - Evaluate the technical accuracy of the response: - 1. Are technical terms used correctly? - 2. Are explanations factually accurate? - 3. Is the complexity appropriate for the audience? - """, - model="gpt-4" -) -``` - -## Custom Metrics - -Create your own metrics: - -```python -from opik.evaluation.metrics import BaseMetric, ScoreResult - -class ResponseLengthMetric(BaseMetric): - def __init__(self, min_length: int = 50, max_length: int = 500): - self.name = "response_length" - self.min_length = min_length - self.max_length = max_length - - def score(self, output: str, **kwargs) -> ScoreResult: - length = len(output) - - if self.min_length <= length <= self.max_length: - return ScoreResult( - name=self.name, - value=1.0, - reason=f"Length {length} is within acceptable range" - ) - else: - return ScoreResult( - name=self.name, - value=0.0, - reason=f"Length {length} outside range [{self.min_length}, {self.max_length}]" - ) -``` - -## Experiment-Level Metrics - -Compute aggregate metrics across all test results: - -```python -def compute_experiment_scores(test_results): - scores = [r.scores.get("accuracy", 0) for r in test_results] - return { - "mean_accuracy": sum(scores) / len(scores), - "min_accuracy": min(scores), - "pass_rate": sum(1 for s in scores if s > 0.8) / len(scores) - } - -results = evaluate( - experiment_name="with-aggregates", - dataset=dataset, - task=evaluation_task, - scoring_metrics=[AnswerRelevance()], - experiment_scoring_functions=[compute_experiment_scores] -) -``` - -## Comparing Experiments - -In the Opik UI: -1. Go to your dataset's experiments -2. Select experiments to compare -3. View side-by-side metrics -4. Analyze per-item differences - -## Evaluation Best Practices - -### Dataset Design - -1. **Representative samples**: Cover edge cases and typical usage -2. **Clear expected outputs**: When possible, include ground truth -3. **Version your datasets**: Track changes over time -4. **Balance coverage**: Include examples across all use cases - -### Metric Selection - -1. **Start simple**: Begin with heuristic metrics -2. **Add LLM judges**: For semantic quality -3. **Custom metrics**: For domain-specific requirements -4. **Multiple metrics**: Capture different quality dimensions - -### Experiment Workflow - -1. **Baseline first**: Establish current performance -2. **Change one variable**: Isolate impact of changes -3. **Document config**: Track model, prompt, parameters -4. **Iterate systematically**: Use data to guide improvements - -## Online Evaluation - -Run metrics automatically on production traces: - -### Setting Up Rules - -In the Opik UI: -1. Go to Project Settings > Evaluation Rules -2. Create a new rule -3. Select the metric -4. Configure sampling (all traces or percentage) -5. Activate the rule - -### Supported Online Metrics - -- Answer Relevance -- Hallucination -- Moderation -- Custom LLM-as-Judge rules - -## TypeScript Evaluation - -```typescript -import { Opik, evaluate, Hallucination } from "opik"; - -const client = new Opik(); -const dataset = await client.getDataset("my-dataset"); - -const results = await evaluate({ - experimentName: "ts-evaluation", - dataset, - task: async (item) => { - const response = await myLLM(item.input); - return { output: response }; - }, - scoringMetrics: [new Hallucination({ model: "gpt-4o" })] -}); -``` - -## Troubleshooting - -### Metrics returning unexpected scores - -- Check input/output field names match metric expectations -- Verify context is passed for RAG metrics -- Review the `reason` field for explanation - -### Slow evaluations - -- Use batch APIs when possible -- Consider sampling large datasets -- Choose faster models for LLM-as-Judge metrics - -### Inconsistent LLM-as-Judge scores - -- Set `temperature=0` for deterministic results -- Use `seed` parameter when available -- Run multiple trials and average diff --git a/skills/agent-ops/references/production.md b/skills/agent-ops/references/production.md deleted file mode 100644 index 2ce3c51..0000000 --- a/skills/agent-ops/references/production.md +++ /dev/null @@ -1,456 +0,0 @@ -# Production Monitoring Guide - -Guide to monitoring, alerting, and protecting your LLM applications in production with Opik. - -## Overview - -Opik provides comprehensive production monitoring: -- **Dashboards** - Visualize metrics over time -- **Online evaluation** - Automatically score production traces -- **Alerts** - Get notified when metrics deviate -- **Guardrails** - Protect against risks in real-time -- **Opik Assist** - AI-powered debugging for traces - -## Opik Assist (AI Debugging) - -Opik Assist uses AI to help debug and understand your traces. - -### What Opik Assist Does - -- **Root Cause Analysis**: Automatically identifies why a trace failed or produced poor results -- **Anomaly Detection**: Flags unusual patterns in trace behavior -- **Improvement Suggestions**: Recommends prompt or configuration changes -- **Comparison Analysis**: Explains differences between successful and failed traces - -### Using Opik Assist - -In the Opik UI: -1. Navigate to any trace -2. Click "Opik Assist" button -3. Ask questions about the trace: - - "Why did this trace fail?" - - "What caused the hallucination in span X?" - - "How can I improve this response?" - - "Compare this to similar successful traces" - -### Example Questions - -| Question Type | Example | -|---------------|---------| -| Debugging | "Why did the agent stop early?" | -| Quality | "What made the response unhelpful?" | -| Performance | "Why was this trace slow?" | -| Comparison | "How does this differ from trace X?" | -| Improvement | "Suggest prompt changes to fix this" | - -### Opik Assist for Evaluation Results - -When viewing experiment results: -1. Select traces with low scores -2. Click "Analyze with Opik Assist" -3. Get AI-generated insights on: - - Common failure patterns - - Suggested prompt improvements - - Dataset gaps to address - -### Programmatic Access - -```python -from opik import Opik - -client = Opik() - -# Get AI analysis for a trace -analysis = client.assist( - trace_id="abc-123", - question="Why did this trace produce a hallucination?" -) - -print(analysis.explanation) -print(analysis.suggestions) -``` - -## Dashboards - -Create custom views to monitor your LLM applications. - -### Dashboard Types - -| Type | Location | Purpose | -|------|----------|---------| -| Standalone | Dashboards page | Cross-project monitoring | -| Project | Project > Dashboards tab | Project-specific metrics | -| Experiment | Experiment comparison | Compare evaluation results | - -### Available Widgets - -#### Project Metrics Widget - -Time-series visualization of: -- Trace/thread feedback scores -- Trace/thread counts -- Token usage -- Estimated cost -- Failed guardrails -- Duration metrics - -#### Project Statistics Widget - -Single-value cards showing: -- Total trace/span counts -- P50/P90/P99 duration -- Average costs -- Error counts -- Token averages - -#### Experiment Metrics Widget - -Compare experiments with: -- Line charts for trends -- Bar charts for distributions -- Radar charts for multi-metric comparison - -### Creating Dashboards - -Via UI: -1. Navigate to Dashboards page -2. Click **Create new dashboard** -3. Choose a template or start blank -4. Add sections and widgets -5. Configure filters and metrics - -### Dashboard Templates - -- **Performance Overview**: Traces, quality, latency, cost summary -- **Project Metrics**: Token usage, costs, feedback, guardrails -- **Experiment Insights**: Radar/bar charts for experiment comparison - -## Online Evaluation - -Automatically score production traces with LLM-as-Judge metrics. - -### Setting Up Evaluation Rules - -1. Go to Project Settings > Evaluation Rules -2. Create a new rule -3. Configure: - - **Metric**: AnswerRelevance, Hallucination, Moderation, etc. - - **Sampling**: All traces or percentage - - **Filters**: Apply to specific trace types - -### Supported Online Metrics - -| Metric | Description | -|--------|-------------| -| Answer Relevance | Does response address the query? | -| Hallucination | Are there unsupported claims? | -| Moderation | Safety and policy violations | -| Custom | Define your own LLM-as-Judge | - -### Tracking Scores Over Time - -Once rules are active: -- Scores appear on each trace -- Dashboard shows score trends -- Filter traces by score ranges -- Set alerts on score thresholds - -## Feedback Scores - -### Logging Inline with Traces - -```python -import opik - -@opik.track -def my_agent(query: str): - # Your logic - response = generate_response(query) - - # Log feedback score - opik.opik_context.update_current_trace( - feedback_scores=[ - { - "name": "user_feedback", - "value": 1.0, - "reason": "User clicked thumbs up" - } - ] - ) - return response -``` - -### Updating Scores Later - -```python -from opik import Opik - -client = Opik() - -# Search for traces to annotate -traces = client.search_traces( - project_name="production", - filters={"tags": ["needs_review"]} -) - -# Add feedback scores -for trace in traces: - client.log_traces_feedback_scores( - scores=[{ - "id": trace.id, - "name": "quality_score", - "value": 0.85, - "reason": "Reviewed by team", - "project_name": "production" - }] - ) -``` - -## Alerts - -Get notified when metrics exceed thresholds. - -### Setting Up Alerts - -1. Go to Project Settings > Alerts -2. Create alert with: - - **Metric**: What to monitor - - **Condition**: Threshold and comparison - - **Window**: Time period to evaluate - - **Notification**: Slack, email, webhook - -### Common Alert Patterns - -| Alert | Metric | Condition | -|-------|--------|-----------| -| Quality drop | AnswerRelevance | Average < 0.7 over 1 hour | -| High errors | Error count | > 10 in 15 minutes | -| Cost spike | Estimated cost | > $100 in 1 hour | -| Hallucination rate | Hallucination | Average > 0.3 over 1 hour | -| Latency regression | P95 duration | > 5s over 30 minutes | - -## Guardrails - -Protect your application from LLM risks in real-time. - -### Types of Guardrails - -| Type | Method | Use Case | -|------|--------|----------| -| PII | NLP models | Detect personal information | -| Topic | Zero-shot classifier | Ensure on-topic responses | -| Custom | Your logic | Brand mentions, business rules | - -### Using Guardrails (Self-hosted) - -```bash -# Start guardrails backend -./opik.sh --guardrails -``` - -```python -from opik.guardrails import Guardrail, PII, Topic -from opik import exceptions - -guardrail = Guardrail( - guards=[ - Topic( - restricted_topics=["finance", "health"], - threshold=0.9 - ), - PII(blocked_entities=["CREDIT_CARD", "SSN", "PERSON"]) - ] -) - -llm_response = "Your account balance is $5,000" - -try: - guardrail.validate(llm_response) -except exceptions.GuardrailValidationFailed as e: - print(f"Guardrail failed: {e}") - # Handle the failure - return safe response -``` - -### Custom Guardrails - -```python -import opik - -competitor_brands = ["OpenAI", "Anthropic", "Google AI"] - -def custom_guardrail(generation: str, trace_id: str) -> str: - client = opik.Opik() - - # Start guardrail span - span = client.span( - name="brand_check", - input={"generation": generation}, - type="guardrail", - trace_id=trace_id - ) - - # Check for competitor mentions - found = [b for b in competitor_brands if b.lower() in generation.lower()] - - if found: - result = "failed" - output = {"guardrail_result": result, "found_brands": found} - else: - result = "passed" - output = {"guardrail_result": result} - - span.end(output=output) - return generation if result == "passed" else "I cannot recommend competitors." -``` - -### Streaming Guardrails - -Validate chunks in streaming responses: - -```python -for chunk in llm_stream: - try: - guardrail.validate(chunk) - yield chunk - except exceptions.GuardrailValidationFailed: - yield "[Content filtered]" - break -``` - -## Data Privacy - -### Anonymizers - -Automatically mask sensitive data in traces: - -```python -from opik import configure - -configure( - anonymizers=[ - {"type": "pii", "action": "mask"}, # Replace PII with [MASKED] - {"type": "email", "action": "hash"}, # Hash email addresses - ] -) -``` - -### Selective Logging - -Control what gets logged: - -```python -import opik - -@opik.track( - capture_input=True, # Log function inputs - capture_output=False, # Don't log outputs (sensitive) -) -def sensitive_function(user_data: dict) -> str: - pass -``` - -### Data Retention - -Configure retention policies: -- Set trace TTL per project -- Automatic deletion of old traces -- Export before deletion for compliance - -## Cost Tracking - -### Automatic Cost Estimation - -Opik automatically estimates costs based on: -- Model pricing tables -- Input/output token counts -- Provider-specific rates - -### Cost Analysis - -In dashboards: -- Total cost over time -- Cost per trace/operation -- Cost by model/project -- Budget alerts - -### Cost Optimization - -Use traces to identify: -- Expensive operations -- Unnecessary LLM calls -- Opportunities for smaller models -- Caching opportunities - -## Performance Monitoring - -### Key Metrics - -| Metric | What It Shows | -|--------|---------------| -| P50/P90/P99 Latency | Response time distribution | -| Throughput | Traces per minute | -| Error Rate | Failed traces percentage | -| Token Usage | Input/output token trends | - -### Latency Analysis - -Use traces to identify: -- Slow spans (bottlenecks) -- External API latency -- Model inference time -- Processing overhead - -## Error Tracking - -### Automatic Error Capture - -Traces automatically capture: -- Exception type and message -- Stack trace -- Span where error occurred -- Input that caused error - -### Error Analysis - -Dashboard features: -- Error rate over time -- Error type breakdown -- Filter to error traces -- Drill into root causes - -### Error Alerting - -Set alerts for: -- Error rate exceeds threshold -- Specific error types -- Error spikes - -## Best Practices - -### Dashboard Design - -1. **Start with templates**: Customize from there -2. **Group related metrics**: One section per concern -3. **Add context**: Use markdown widgets to explain -4. **Set appropriate date ranges**: Match your monitoring cadence - -### Alerting Strategy - -1. **Alert on symptoms**: User-facing issues -2. **Avoid alert fatigue**: Tune thresholds carefully -3. **Escalation paths**: Different severity levels -4. **Include context**: Link to relevant dashboards - -### Guardrail Deployment - -1. **Start permissive**: Log, don't block initially -2. **Analyze violations**: Understand patterns -3. **Tune thresholds**: Balance safety and usability -4. **Monitor guardrail latency**: Keep it fast - -### Data Governance - -1. **Define retention policy**: Before going to production -2. **Classify data sensitivity**: Per project/trace type -3. **Configure anonymizers**: For PII protection -4. **Regular audits**: Review what's being logged