
Senior Prompt Engineer
- 195 installs
- 451 repo stars
- Updated July 21, 2026
- borghei/claude-skills
Craft production prompts, tool schemas, eval harnesses, and guardrails for LLM features, copilots, and multi-step agent workflows.
About
Senior prompt engineer skill for LLM products and agents: designs system prompts, structured outputs, tool definitions, evaluation suites, and iteration loops that improve accuracy, safety, and consistency across chat, RAG, and autonomous workflows.
- System and few-shot prompt design
- Tool schema and JSON-mode patterns
- Eval sets and regression checks
- Safety and refusal guardrails
- Cost-latency optimization tactics
Senior Prompt Engineer by the numbers
- 195 all-time installs (skills.sh)
- Ranked #2,920 of 16,546 AI & Agent Building skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/borghei/claude-skills --skill senior-prompt-engineerAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 195 |
|---|---|
| repo stars | ★ 451 |
| Last updated | July 21, 2026 |
| Repository | borghei/claude-skills ↗ |
What it does
Craft production prompts, tool schemas, eval harnesses, and guardrails for LLM features, copilots, and multi-step agent workflows.
Files
Senior Prompt Engineer
Prompt engineering patterns, LLM evaluation frameworks, and agentic system design. Provides static (deterministic) analysis tools to optimize prompts, evaluate RAG retrieval and generation quality, and validate/visualize agent workflows — plus deep reference libraries of prompt patterns, evaluation metrics, and agent architectures.
Core Capabilities
- Prompt optimization — token counting and cost estimation, clarity/structure scoring, ambiguity and redundancy detection, and generation of optimized prompt versions.
- Few-shot & structured output design — extract/manage few-shot examples, design diverse example sets (simple/edge/complex/negative), and enforce reliable JSON/XML schema outputs.
- RAG evaluation — context relevance, answer faithfulness, groundedness (ROUGE-L), and retrieval metrics (Precision@K, MRR, NDCG) over pre-retrieved contexts.
- Agentic system design — validate agent configs, visualize flows (ASCII/Mermaid), estimate token cost per run, and apply ReAct / Plan-Execute / Tool-Use / multi-agent patterns.
- Pattern library — 10 prompt patterns, evaluation frameworks (A/B testing, benchmarks, human eval), and agent architectures with pseudocode.
When to Use
- Optimizing an existing prompt's performance or reducing token costs.
- Designing prompt templates, few-shot examples, or structured-output workflows.
- Evaluating LLM outputs or RAG retrieval/generation quality.
- Building or validating agentic systems and tool-calling workflows.
Tools
| Tool | Purpose | Command |
|---|---|---|
prompt_optimizer.py | Analyze/optimize prompts: tokens, clarity, structure, few-shot extraction | python scripts/prompt_optimizer.py prompt.txt --analyze |
rag_evaluator.py | Evaluate RAG context relevance, faithfulness, retrieval metrics | python scripts/rag_evaluator.py --contexts ctx.json --questions q.json |
agent_orchestrator.py | Validate, visualize, and cost-estimate agent configs | python scripts/agent_orchestrator.py agent.yaml --validate |
References
Load the reference that matches the task — keep this file lean and pull detail on demand:
- [references/tools-and-workflows.md](references/tools-and-workflows.md) — full tool usage with sample outputs, the prompt-optimization / few-shot / structured-output workflows, common-patterns and command quick references, troubleshooting table, success criteria, and complete per-script parameter/output-format reference. Read when running any tool or executing a workflow.
- [references/prompt_engineering_patterns.md](references/prompt_engineering_patterns.md) — 10 prompt patterns (zero/few-shot, CoT, role, structured output, self-consistency, ReAct, tree-of-thoughts, RAG) with example inputs and expected outputs. Read when choosing or applying a prompt technique.
- [references/llm_evaluation_frameworks.md](references/llm_evaluation_frameworks.md) — evaluation metrics, text-generation and RAG-specific scoring, human-eval frameworks, A/B testing, benchmark datasets, and pipeline design. Read when measuring quality or comparing prompts.
- [references/agentic_system_design.md](references/agentic_system_design.md) — agent architectures (ReAct, Plan-and-Execute, Tool Use, multi-agent, memory/state) and design patterns with pseudocode. Read when building agents or tool-calling systems.
Scope & Limitations
This skill covers:
- Static prompt analysis: token counting, clarity scoring, structure detection, and optimization suggestions
- RAG evaluation: context relevance, answer faithfulness, groundedness, and retrieval metrics (Precision@K, ROUGE-L, MRR, NDCG)
- Agent workflow design: configuration validation, ASCII/Mermaid visualization, and token cost estimation
- Few-shot example extraction and management from existing prompts
This skill does NOT cover:
- Live LLM calls or runtime prompt testing --- all analysis is static/deterministic (see
senior-ml-engineerfor LLM integration) - Vector database setup or embedding generation --- RAG evaluator scores pre-retrieved contexts only (see
senior-data-engineerfor pipeline orchestration) - Fine-tuning, RLHF, or model training workflows (see
senior-ml-engineerfor model deployment) - Production monitoring, A/B test execution, or real-time drift detection (see
senior-data-scientistfor experiment design)
Integration Points
| Skill | Integration | Data Flow |
|---|---|---|
senior-ml-engineer | LLM integration and model deployment | Optimized prompts from this skill feed into llm_integration_builder.py prompt templates |
senior-data-scientist | A/B test design for prompt experiments | experiment_designer.py defines test parameters; this skill provides the prompt variants to compare |
senior-data-engineer | RAG pipeline orchestration | pipeline_orchestrator.py builds the retrieval pipeline; this skill evaluates its output quality |
senior-fullstack | End-to-end application scaffolding | Fullstack apps consume agent configs validated by agent_orchestrator.py |
senior-security | Prompt injection and adversarial input review | Security analysis covers the attack surface; this skill ensures prompts include defensive constraints |
senior-qa | Quality assurance for AI-powered features | QA test suites validate that optimized prompts produce consistent outputs in production |
Agentic System Design
Agent architectures, tool use patterns, and multi-agent orchestration with pseudocode.
Architectures Index
1. ReAct Pattern 2. Plan-and-Execute 3. Tool Use / Function Calling 4. Multi-Agent Collaboration 5. Memory and State Management 6. Agent Design Patterns
---
1. ReAct Pattern
Reasoning + Acting: The agent alternates between thinking about what to do and taking actions.
Architecture
┌─────────────────────────────────────────────────────────────┐
│ ReAct Loop │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ Thought │───▶│ Action │───▶│ Tool │───▶│Observat.│ │
│ └─────────┘ └─────────┘ └─────────┘ └────┬────┘ │
│ ▲ │ │
│ └────────────────────────────────────────────┘ │
│ (loop until done) │
└─────────────────────────────────────────────────────────────┘Pseudocode
def react_agent(query, tools, max_iterations=10):
"""
ReAct agent implementation.
Args:
query: User question
tools: Dict of available tools {name: function}
max_iterations: Safety limit
"""
context = f"Question: {query}\n"
for i in range(max_iterations):
# Generate thought and action
response = llm.generate(
REACT_PROMPT.format(
tools=format_tools(tools),
context=context
)
)
# Parse response
thought = extract_thought(response)
action = extract_action(response)
context += f"Thought: {thought}\n"
# Check for final answer
if action.name == "finish":
return action.argument
# Execute tool
if action.name in tools:
observation = tools[action.name](action.argument)
context += f"Action: {action.name}({action.argument})\n"
context += f"Observation: {observation}\n"
else:
context += f"Error: Unknown tool {action.name}\n"
return "Max iterations reached"Prompt Template
You are a helpful assistant that can use tools to answer questions.
Available tools:
{tools}
Answer format:
Thought: [your reasoning about what to do next]
Action: [tool_name(argument)] OR finish(final_answer)
{context}
Continue:When to Use
| Scenario | ReAct Fit |
|---|---|
| Simple Q&A with lookup | Good |
| Multi-step research | Good |
| Math calculations | Good |
| Creative writing | Poor |
| Real-time conversation | Poor |
---
2. Plan-and-Execute
Two-phase approach: First create a plan, then execute each step.
Architecture
┌──────────────────────────────────────────────────────────────┐
│ Plan-and-Execute │
├──────────────────────────────────────────────────────────────┤
│ │
│ Phase 1: Planning │
│ ┌──────────┐ ┌──────────────────────────────────────┐ │
│ │ Query │───▶│ Generate step-by-step plan │ │
│ └──────────┘ └──────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────────┐ │
│ │ Plan: [S1, S2, S3] │ │
│ └──────────┬───────────┘ │
│ │ │
│ Phase 2: Execution │ │
│ ┌──────────▼───────────┐ │
│ │ Execute Step 1 │ │
│ └──────────┬───────────┘ │
│ │ │
│ ┌──────────▼───────────┐ │
│ │ Execute Step 2 │──▶ Replan? │
│ └──────────┬───────────┘ │
│ │ │
│ ┌──────────▼───────────┐ │
│ │ Execute Step 3 │ │
│ └──────────┬───────────┘ │
│ │ │
│ ┌──────────▼───────────┐ │
│ │ Final Answer │ │
│ └──────────────────────┘ │
└──────────────────────────────────────────────────────────────┘Pseudocode
def plan_and_execute(query, tools):
"""
Plan-and-Execute agent.
Separates planning from execution for complex tasks.
"""
# Phase 1: Generate plan
plan = generate_plan(query)
results = []
# Phase 2: Execute each step
for i, step in enumerate(plan.steps):
# Execute step
result = execute_step(step, tools, results)
results.append(result)
# Optional: Check if replanning needed
if should_replan(step, result, plan):
remaining_steps = plan.steps[i+1:]
new_plan = replan(query, results, remaining_steps)
plan.steps = plan.steps[:i+1] + new_plan.steps
# Synthesize final answer
return synthesize_answer(query, results)
def generate_plan(query):
"""Generate execution plan from query."""
prompt = f"""
Create a step-by-step plan to answer this question:
{query}
Format each step as:
Step N: [action description]
Keep the plan concise (3-7 steps).
"""
response = llm.generate(prompt)
return parse_plan(response)
def execute_step(step, tools, previous_results):
"""Execute a single step using available tools."""
prompt = f"""
Execute this step: {step.description}
Previous results:
{format_results(previous_results)}
Available tools: {format_tools(tools)}
Provide the result of this step.
"""
return llm.generate(prompt)When to Use
| Task Complexity | Recommendation |
|---|---|
| Simple (1-2 steps) | Use ReAct |
| Medium (3-5 steps) | Plan-and-Execute |
| Complex (6+ steps) | Plan-and-Execute with replanning |
| Highly dynamic | ReAct with adaptive planning |
---
3. Tool Use / Function Calling
Structured tool invocation: LLM generates structured calls that are executed externally.
Tool Definition Schema
{
"name": "search_web",
"description": "Search the web for current information",
"parameters": {
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "Search query"
},
"num_results": {
"type": "integer",
"default": 5,
"description": "Number of results to return"
}
},
"required": ["query"]
}
}Implementation Pattern
class ToolRegistry:
"""Registry for agent tools."""
def __init__(self):
self.tools = {}
def register(self, name, func, schema):
"""Register a tool with its schema."""
self.tools[name] = {
"function": func,
"schema": schema
}
def get_schemas(self):
"""Get all tool schemas for LLM."""
return [t["schema"] for t in self.tools.values()]
def execute(self, name, arguments):
"""Execute a tool by name."""
if name not in self.tools:
raise ValueError(f"Unknown tool: {name}")
func = self.tools[name]["function"]
return func(**arguments)
def tool_use_agent(query, registry):
"""Agent with function calling."""
messages = [{"role": "user", "content": query}]
while True:
# Call LLM with tools
response = llm.chat(
messages=messages,
tools=registry.get_schemas(),
tool_choice="auto"
)
# Check if done
if response.finish_reason == "stop":
return response.content
# Execute tool calls
if response.tool_calls:
for call in response.tool_calls:
result = registry.execute(
call.function.name,
json.loads(call.function.arguments)
)
messages.append({
"role": "tool",
"tool_call_id": call.id,
"content": str(result)
})Tool Design Best Practices
| Practice | Example |
|---|---|
| Clear descriptions | "Search web for query" not "search" |
| Type hints | Use JSON Schema types |
| Default values | Provide sensible defaults |
| Error handling | Return error messages, not exceptions |
| Idempotency | Same input = same output |
---
4. Multi-Agent Collaboration
Orchestration Patterns
Pattern 1: Sequential Pipeline
Agent A → Agent B → Agent C → Output
Use case: Research → Analysis → WritingPattern 2: Hierarchical
┌─────────────┐
│ Coordinator │
└──────┬──────┘
┌──────────┼──────────┐
▼ ▼ ▼
┌───────┐ ┌───────┐ ┌───────┐
│Agent A│ │Agent B│ │Agent C│
└───────┘ └───────┘ └───────┘
Use case: Complex task decompositionPattern 3: Debate/Consensus
┌───────┐ ┌───────┐
│Agent A│◄───▶│Agent B│
└───┬───┘ └───┬───┘
│ │
└──────┬──────┘
▼
┌─────────────┐
│ Arbiter │
└─────────────┘
Use case: Critical decisions, fact-checkingPseudocode: Hierarchical Multi-Agent
class CoordinatorAgent:
"""Coordinates multiple specialized agents."""
def __init__(self, agents):
self.agents = agents # Dict[str, Agent]
def process(self, query):
# Decompose task
subtasks = self.decompose(query)
# Assign to agents
results = {}
for subtask in subtasks:
agent_name = self.select_agent(subtask)
result = self.agents[agent_name].execute(subtask)
results[subtask.id] = result
# Synthesize
return self.synthesize(query, results)
def decompose(self, query):
"""Break query into subtasks."""
prompt = f"""
Break this task into subtasks for specialized agents:
Task: {query}
Available agents:
- researcher: Gathers information
- analyst: Analyzes data
- writer: Produces content
Format:
1. [agent]: [subtask description]
"""
response = llm.generate(prompt)
return parse_subtasks(response)
def select_agent(self, subtask):
"""Select best agent for subtask."""
return subtask.assigned_agent
def synthesize(self, query, results):
"""Combine agent results into final answer."""
prompt = f"""
Combine these results to answer: {query}
Results:
{format_results(results)}
Provide a coherent final answer.
"""
return llm.generate(prompt)Communication Protocols
| Protocol | Description | Use When |
|---|---|---|
| Direct | Agent calls agent | Simple pipelines |
| Message queue | Async message passing | High throughput |
| Shared state | Shared memory/database | Collaborative editing |
| Broadcast | One-to-many | Status updates |
---
5. Memory and State Management
Memory Types
┌─────────────────────────────────────────────────────────────┐
│ Agent Memory System │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────┐ ┌─────────────────┐ │
│ │ Working Memory │ │ Episodic Memory │ │
│ │ (Current task) │ │ (Past sessions) │ │
│ └────────┬────────┘ └────────┬─────────┘ │
│ │ │ │
│ └────────┬───────────┘ │
│ ▼ │
│ ┌─────────────────────────────────────────┐ │
│ │ Semantic Memory │ │
│ │ (Long-term knowledge, embeddings) │ │
│ └─────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘Implementation
class AgentMemory:
"""Memory system for conversational agents."""
def __init__(self, embedding_model, vector_store):
self.embedding_model = embedding_model
self.vector_store = vector_store
self.working_memory = [] # Current conversation
self.buffer_size = 10 # Recent messages to keep
def add_message(self, role, content):
"""Add message to working memory."""
self.working_memory.append({
"role": role,
"content": content,
"timestamp": datetime.now()
})
# Trim if too long
if len(self.working_memory) > self.buffer_size:
# Summarize old messages before removing
old_messages = self.working_memory[:5]
summary = self.summarize(old_messages)
self.store_long_term(summary)
self.working_memory = self.working_memory[5:]
def store_long_term(self, content):
"""Store in semantic memory (vector store)."""
embedding = self.embedding_model.embed(content)
self.vector_store.add(
embedding=embedding,
metadata={"content": content, "type": "summary"}
)
def retrieve_relevant(self, query, k=5):
"""Retrieve relevant memories for context."""
query_embedding = self.embedding_model.embed(query)
results = self.vector_store.search(query_embedding, k=k)
return [r.metadata["content"] for r in results]
def get_context(self, query):
"""Build context for LLM from memories."""
relevant = self.retrieve_relevant(query)
recent = self.working_memory[-self.buffer_size:]
return {
"relevant_memories": relevant,
"recent_conversation": recent
}
def summarize(self, messages):
"""Summarize messages for long-term storage."""
content = "\n".join([
f"{m['role']}: {m['content']}"
for m in messages
])
prompt = f"Summarize this conversation:\n{content}"
return llm.generate(prompt)State Persistence Patterns
| Pattern | Storage | Use Case |
|---|---|---|
| In-memory | Dict/List | Single session |
| Redis | Key-value | Multi-session, fast |
| PostgreSQL | Relational | Complex queries |
| Vector DB | Embeddings | Semantic search |
---
6. Agent Design Patterns
Pattern: Reflection
Agent reviews and critiques its own output.
def reflective_agent(query, tools):
"""Agent that reflects on its answers."""
# Initial response
response = react_agent(query, tools)
# Reflection
critique = llm.generate(f"""
Review this answer for:
1. Accuracy - Is the information correct?
2. Completeness - Does it fully answer the question?
3. Clarity - Is it easy to understand?
Question: {query}
Answer: {response}
Critique:
""")
# Check if revision needed
if needs_revision(critique):
revised = llm.generate(f"""
Improve this answer based on the critique:
Original: {response}
Critique: {critique}
Improved answer:
""")
return revised
return responsePattern: Self-Ask
Break complex questions into simpler sub-questions.
def self_ask_agent(query, tools):
"""Agent that asks itself follow-up questions."""
context = []
while True:
prompt = f"""
Question: {query}
Previous Q&A:
{format_qa(context)}
Do you need to ask a follow-up question to answer this?
If yes: "Follow-up: [question]"
If no: "Final Answer: [answer]"
"""
response = llm.generate(prompt)
if response.startswith("Final Answer:"):
return response.replace("Final Answer:", "").strip()
# Answer follow-up question
follow_up = response.replace("Follow-up:", "").strip()
answer = simple_qa(follow_up, tools)
context.append({"q": follow_up, "a": answer})Pattern: Expert Routing
Route queries to specialized sub-agents.
class ExpertRouter:
"""Routes queries to expert agents."""
def __init__(self):
self.experts = {
"code": CodeAgent(),
"math": MathAgent(),
"research": ResearchAgent(),
"general": GeneralAgent()
}
def route(self, query):
"""Determine best expert for query."""
prompt = f"""
Classify this query into one category:
- code: Programming questions
- math: Mathematical calculations
- research: Fact-finding, current events
- general: Everything else
Query: {query}
Category:
"""
category = llm.generate(prompt).strip().lower()
return self.experts.get(category, self.experts["general"])
def process(self, query):
expert = self.route(query)
return expert.execute(query)---
Quick Reference: Pattern Selection
| Need | Pattern |
|---|---|
| Simple tool use | ReAct |
| Complex multi-step | Plan-and-Execute |
| API integration | Function Calling |
| Multiple perspectives | Multi-Agent Debate |
| Quality assurance | Reflection |
| Complex reasoning | Self-Ask |
| Domain expertise | Expert Routing |
| Conversation continuity | Memory System |
LLM Evaluation Frameworks
Concrete metrics, scoring methods, comparison tables, and A/B testing frameworks.
Frameworks Index
1. Evaluation Metrics Overview 2. Text Generation Metrics 3. RAG-Specific Metrics 4. Human Evaluation Frameworks 5. A/B Testing for Prompts 6. Benchmark Datasets 7. Evaluation Pipeline Design
---
1. Evaluation Metrics Overview
Metric Categories
| Category | Metrics | When to Use |
|---|---|---|
| Lexical | BLEU, ROUGE, Exact Match | Reference-based comparison |
| Semantic | BERTScore, Embedding similarity | Meaning preservation |
| Task-specific | F1, Accuracy, Precision/Recall | Classification, extraction |
| Quality | Coherence, Fluency, Relevance | Open-ended generation |
| Safety | Toxicity, Bias scores | Content moderation |
Choosing the Right Metric
Is there a single correct answer?
├── Yes → Exact Match or F1
└── No
└── Is there a reference output?
├── Yes → BLEU, ROUGE, or BERTScore
└── No
└── Can you define quality criteria?
├── Yes → Human evaluation + LLM-as-judge
└── No → A/B testing with user metrics---
2. Text Generation Metrics
BLEU (Bilingual Evaluation Understudy)
What it measures: N-gram overlap between generated and reference text.
Score range: 0 to 1 (higher is better)
Calculation:
BLEU = BP × exp(Σ wn × log(pn))
Where:
- BP = brevity penalty (penalizes short outputs)
- pn = precision of n-grams
- wn = weight (typically 0.25 for BLEU-4)Interpretation:
| BLEU Score | Quality |
|---|---|
| > 0.6 | Excellent |
| 0.4 - 0.6 | Good |
| 0.2 - 0.4 | Acceptable |
| < 0.2 | Poor |
Example:
Reference: "The quick brown fox jumps over the lazy dog"
Generated: "A fast brown fox leaps over the lazy dog"
1-gram precision: 7/9 = 0.78 (matched: brown, fox, over, the, lazy, dog)
2-gram precision: 4/8 = 0.50 (matched: brown fox, the lazy, lazy dog)
BLEU-4: ~0.35Limitations:
- Doesn't capture meaning (synonyms penalized)
- Position-independent
- Requires reference text
---
ROUGE (Recall-Oriented Understudy for Gisting Evaluation)
What it measures: Overlap focused on recall (coverage of reference).
Variants:
| Variant | Measures |
|---|---|
| ROUGE-1 | Unigram overlap |
| ROUGE-2 | Bigram overlap |
| ROUGE-L | Longest common subsequence |
| ROUGE-Lsum | LCS with sentence-level computation |
Calculation:
ROUGE-N Recall = (matching n-grams) / (n-grams in reference)
ROUGE-N Precision = (matching n-grams) / (n-grams in generated)
ROUGE-N F1 = 2 × (Precision × Recall) / (Precision + Recall)Example:
Reference: "The cat sat on the mat"
Generated: "The cat was sitting on the mat"
ROUGE-1:
Recall: 5/6 = 0.83 (matched: the, cat, on, the, mat)
Precision: 5/7 = 0.71
F1: 0.77
ROUGE-2:
Recall: 2/5 = 0.40 (matched: "the cat", "the mat")
Precision: 2/6 = 0.33
F1: 0.36Best for: Summarization, text compression
---
BERTScore
What it measures: Semantic similarity using contextual embeddings.
How it works: 1. Generate BERT embeddings for each token 2. Compute cosine similarity between token pairs 3. Apply greedy matching to find best alignment 4. Aggregate into Precision, Recall, F1
Advantages over lexical metrics:
- Captures synonyms and paraphrases
- Context-aware matching
- Better correlation with human judgment
Example:
Reference: "The movie was excellent"
Generated: "The film was outstanding"
Lexical (BLEU): Low score (only "The" and "was" match)
BERTScore: High score (semantic meaning preserved)Interpretation:
| BERTScore F1 | Quality |
|---|---|
| > 0.9 | Excellent |
| 0.8 - 0.9 | Good |
| 0.7 - 0.8 | Acceptable |
| < 0.7 | Review needed |
---
3. RAG-Specific Metrics
Context Relevance
What it measures: How relevant retrieved documents are to the query.
Calculation methods:
Method 1: Embedding similarity
relevance = cosine_similarity(
embed(query),
embed(context)
)Method 2: LLM-as-judge
Prompt: "Rate the relevance of this context to the question.
Question: {question}
Context: {context}
Rate from 1-5 where 5 is highly relevant."Target: > 0.8 for top-k contexts
---
Answer Faithfulness
What it measures: Whether the answer is supported by the context (no hallucination).
Evaluation prompt:
Given the context and answer, determine if every claim in the
answer is supported by the context.
Context: {context}
Answer: {answer}
For each claim in the answer:
1. Identify the claim
2. Find supporting evidence in context (or mark as unsupported)
3. Rate: Supported / Partially Supported / Not Supported
Overall faithfulness score: [0-1]Scoring:
Faithfulness = (supported claims) / (total claims)Target: > 0.95 for production systems
---
Retrieval Metrics
| Metric | Formula | What it measures |
|---|---|---|
| Precision@k | (relevant in top-k) / k | Quality of top results |
| Recall@k | (relevant in top-k) / (total relevant) | Coverage |
| MRR | 1 / (rank of first relevant) | Position of first hit |
| NDCG@k | DCG@k / IDCG@k | Ranking quality |
Example:
Query: "What is photosynthesis?"
Retrieved docs (k=5): [R, N, R, N, R] (R=relevant, N=not relevant)
Total relevant in corpus: 10
Precision@5 = 3/5 = 0.6
Recall@5 = 3/10 = 0.3
MRR = 1/1 = 1.0 (first doc is relevant)---
4. Human Evaluation Frameworks
Likert Scale Evaluation
Setup:
Rate the following response on a scale of 1-5:
Response: {generated_response}
Criteria:
- Relevance (1-5): Does it address the question?
- Accuracy (1-5): Is the information correct?
- Fluency (1-5): Is it well-written?
- Helpfulness (1-5): Would this be useful to the user?Sample size guidance:
| Confidence Level | Margin of Error | Required Samples |
|---|---|---|
| 95% | ±5% | 385 |
| 95% | ±10% | 97 |
| 90% | ±10% | 68 |
---
Comparative Evaluation (Side-by-Side)
Setup:
Compare these two responses to the question:
Question: {question}
Response A: {response_a}
Response B: {response_b}
Which response is better?
[ ] A is much better
[ ] A is slightly better
[ ] About the same
[ ] B is slightly better
[ ] B is much better
Why? _______________Advantages:
- Easier for humans than absolute scoring
- Reduces calibration issues
- Clear winner for A/B decisions
Analysis:
Win rate = (A wins + 0.5 × ties) / total
Bradley-Terry model for ranking multiple variants---
LLM-as-Judge
Setup:
You are an expert evaluator. Rate the quality of this response.
Question: {question}
Response: {response}
Reference (if available): {reference}
Evaluate on:
1. Correctness (0-10): Is the information accurate?
2. Completeness (0-10): Does it fully address the question?
3. Clarity (0-10): Is it easy to understand?
4. Conciseness (0-10): Is it appropriately brief?
Provide scores and brief justification for each.
Overall score (0-10):Calibration techniques:
- Include reference responses with known scores
- Use chain-of-thought for reasoning
- Compare against human baseline periodically
Known biases:
| Bias | Mitigation |
|---|---|
| Position bias | Randomize order |
| Length bias | Normalize or specify length |
| Self-preference | Use different model as judge |
| Verbosity preference | Penalize unnecessary length |
---
5. A/B Testing for Prompts
Experiment Design
Hypothesis template:
H0: Prompt A and Prompt B have equal performance on [metric]
H1: Prompt B improves [metric] by at least [minimum detectable effect]Sample size calculation:
n = 2 × ((z_α + z_β)² × σ²) / δ²
Where:
- z_α = 1.96 for 95% confidence
- z_β = 0.84 for 80% power
- σ = standard deviation of metric
- δ = minimum detectable effectQuick reference:
| MDE | Baseline Rate | Required n/variant |
|---|---|---|
| 5% relative | 50% | 3,200 |
| 10% relative | 50% | 800 |
| 20% relative | 50% | 200 |
---
Metrics to Track
Primary metrics:
| Metric | Measurement |
|---|---|
| Task success rate | % of queries with correct/helpful response |
| User satisfaction | Thumbs up/down or 1-5 rating |
| Engagement | Follow-up questions, session length |
Guardrail metrics:
| Metric | Threshold |
|---|---|
| Error rate | < 1% |
| Latency P95 | < 2s |
| Toxicity rate | < 0.1% |
| Cost per query | Within budget |
---
Analysis Framework
Statistical test selection:
Is the metric binary (success/failure)?
├── Yes → Chi-squared test or Z-test for proportions
└── No
└── Is the data normally distributed?
├── Yes → Two-sample t-test
└── No → Mann-Whitney U testInterpreting results:
p-value < 0.05: Statistically significant
Effect size (Cohen's d):
- Small: 0.2
- Medium: 0.5
- Large: 0.8
Decision: Ship if p < 0.05 AND effect size meets threshold AND guardrails pass---
6. Benchmark Datasets
General NLP Benchmarks
| Benchmark | Task | Size | Metric |
|---|---|---|---|
| MMLU | Knowledge QA | 14K | Accuracy |
| HellaSwag | Commonsense | 10K | Accuracy |
| TruthfulQA | Factuality | 817 | % Truthful |
| HumanEval | Code generation | 164 | pass@k |
| GSM8K | Math reasoning | 8.5K | Accuracy |
RAG Benchmarks
| Benchmark | Focus | Metrics |
|---|---|---|
| Natural Questions | Wikipedia QA | EM, F1 |
| HotpotQA | Multi-hop reasoning | EM, F1 |
| MS MARCO | Web search | MRR, Recall |
| BEIR | Zero-shot retrieval | NDCG@10 |
Creating Custom Benchmarks
Template:
{
"id": "custom-001",
"input": "What are the symptoms of diabetes?",
"expected_output": "Common symptoms include...",
"metadata": {
"category": "medical",
"difficulty": "easy",
"source": "internal docs"
},
"evaluation": {
"type": "semantic_similarity",
"threshold": 0.85
}
}Best practices:
- Minimum 100 examples per category
- Include edge cases (10-20%)
- Balance difficulty levels
- Version control your benchmark
- Update quarterly
---
7. Evaluation Pipeline Design
Automated Evaluation Pipeline
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Prompt │────▶│ LLM API │────▶│ Output │
│ Version │ │ │ │ Storage │
└─────────────┘ └─────────────┘ └──────┬──────┘
│
┌──────────────────────────┘
▼
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Metrics │◀────│ Evaluator │◀────│ Benchmark │
│ Dashboard │ │ Service │ │ Dataset │
└─────────────┘ └─────────────┘ └─────────────┘Implementation Checklist
□ Define success metrics
□ Primary metric (what you're optimizing)
□ Guardrail metrics (what must not regress)
□ Monitoring metrics (operational health)
□ Create benchmark dataset
□ Representative samples from production
□ Edge cases and failure modes
□ Golden answers or human labels
□ Set up evaluation infrastructure
□ Automated scoring pipeline
□ Version control for prompts
□ Results tracking and comparison
□ Establish baseline
□ Run current prompt against benchmark
□ Document scores for all metrics
□ Set improvement targets
□ Run experiments
□ Test one change at a time
□ Use statistical significance testing
□ Check all guardrail metrics
□ Deploy and monitor
□ Gradual rollout (canary)
□ Real-time metric monitoring
□ Rollback plan if regression---
Quick Reference: Metric Selection
| Use Case | Primary Metric | Secondary Metrics |
|---|---|---|
| Summarization | ROUGE-L | BERTScore, Compression ratio |
| Translation | BLEU | chrF, Human pref |
| QA (extractive) | Exact Match, F1 | |
| QA (generative) | BERTScore | Faithfulness, Relevance |
| Code generation | pass@k | Syntax errors |
| Classification | Accuracy, F1 | Precision, Recall |
| RAG | Faithfulness | Context relevance, MRR |
| Open-ended chat | Human eval | Helpfulness, Safety |
Prompt Engineering Patterns
Specific prompt techniques with example inputs and expected outputs.
Patterns Index
1. Zero-Shot Prompting 2. Few-Shot Prompting 3. Chain-of-Thought (CoT) 4. Role Prompting 5. Structured Output 6. Self-Consistency 7. ReAct (Reasoning + Acting) 8. Tree of Thoughts 9. Retrieval-Augmented Generation 10. Meta-Prompting
---
1. Zero-Shot Prompting
When to use: Simple, well-defined tasks where the model has sufficient training knowledge.
Pattern:
[Task instruction]
[Input]Example:
Input:
Classify the following customer review as positive, negative, or neutral.
Review: "The shipping was fast but the product quality was disappointing."Expected Output:
negativeBest practices:
- Be explicit about output format
- Use clear, unambiguous verbs (classify, extract, summarize)
- Specify constraints (word limits, format requirements)
When to avoid:
- Tasks requiring specific formatting the model hasn't seen
- Domain-specific tasks requiring specialized knowledge
- Tasks where consistency is critical
---
2. Few-Shot Prompting
When to use: Tasks requiring consistent formatting or domain-specific patterns.
Pattern:
[Task description]
Example 1:
Input: [example input]
Output: [example output]
Example 2:
Input: [example input]
Output: [example output]
Now process:
Input: [actual input]
Output:Example:
Input:
Extract the company name and founding year from the text.
Example 1:
Input: "Apple Inc. was founded in 1976 by Steve Jobs."
Output: {"company": "Apple Inc.", "year": 1976}
Example 2:
Input: "Microsoft Corporation started in 1975."
Output: {"company": "Microsoft Corporation", "year": 1975}
Example 3:
Input: "Founded in 1994, Amazon has grown into a tech giant."
Output: {"company": "Amazon", "year": 1994}
Now process:
Input: "Tesla, Inc. was established in 2003 by Martin Eberhard."
Output:Expected Output:
{"company": "Tesla, Inc.", "year": 2003}Example selection guidelines:
| Example Type | Purpose | Count |
|---|---|---|
| Simple/typical | Establish basic pattern | 1-2 |
| Edge case | Handle ambiguity | 1 |
| Different format | Show variations | 1 |
| Total | 3-5 |
Common mistakes:
- Too many examples (wastes tokens, may confuse)
- Inconsistent formatting between examples
- Examples too similar (model doesn't learn variation)
---
3. Chain-of-Thought (CoT)
When to use: Math problems, logical reasoning, multi-step analysis.
Pattern:
[Problem]
Think through this step by step:Example:
Input:
A store sells apples for $2 each. If you buy 5 or more, you get 20% off.
How much do 7 apples cost?
Think through this step by step:Expected Output:
Step 1: Calculate the regular price for 7 apples
7 apples × $2 = $14
Step 2: Check if discount applies
7 apples ≥ 5 apples, so 20% discount applies
Step 3: Calculate the discount amount
20% of $14 = 0.20 × $14 = $2.80
Step 4: Calculate final price
$14 - $2.80 = $11.20
The 7 apples cost $11.20Variations:
| Variation | Trigger Phrase | Use Case |
|---|---|---|
| Basic CoT | "Think step by step" | General reasoning |
| Explicit steps | "Break this into numbered steps" | Complex procedures |
| Show work | "Show your calculations" | Math problems |
| Reasoning chain | "Explain your reasoning" | Justification needed |
Zero-shot CoT: Simply append "Let's think step by step." to any prompt.
---
4. Role Prompting
When to use: Tasks requiring expertise, specific perspective, or consistent persona.
Pattern:
You are a [role/expert description].
[Context about the task]
[Specific instruction]Example:
Input:
You are a senior security engineer with 15 years of experience in web application security.
Review the following code snippet for security vulnerabilities:
def login(username, password): query = f"SELECT * FROM users WHERE username='{username}' AND password='{password}'" result = db.execute(query) return result
Identify vulnerabilities and provide specific fixes.Expected Output:
Security Vulnerabilities Identified:
1. SQL Injection (Critical)
- The code directly interpolates user input into SQL query
- Attacker can input: username=' OR '1'='1' --
- This bypasses authentication entirely
Fix:def login(username, password): query = "SELECT * FROM users WHERE username=? AND password=?" result = db.execute(query, (username, password)) return result
2. Plain-text Password Storage (Critical)
- Passwords appear to be stored and compared in plain text
- Fix: Use bcrypt or argon2 for password hashing
3. Missing Rate Limiting (Medium)
- No protection against brute force attacks
- Fix: Implement login attempt throttlingEffective role descriptions:
| Weak | Strong |
|---|---|
| "You are an expert" | "You are a senior data scientist with 10 years of experience in NLP" |
| "You are helpful" | "You are a patient teacher explaining concepts to beginners" |
| "You know about X" | "You are a certified AWS solutions architect specializing in serverless" |
---
5. Structured Output
When to use: When you need parseable responses (JSON, XML, CSV).
Pattern:
[Task instruction]
Respond in JSON format with exactly these fields:
- field1 (type): description
- field2 (type): description
[Input]
Return ONLY valid JSON, no markdown or explanation.Example:
Input:
Extract meeting details from this email.
Respond in JSON format with exactly these fields:
- date (string, ISO format): Meeting date
- time (string, 24h format): Meeting time
- attendees (array of strings): List of attendees
- topic (string): Meeting topic
- location (string or null): Meeting location if mentioned
Email: "Hi team, let's meet tomorrow at 2pm to discuss Q4 planning.
Sarah, Mike, and Lisa should attend. We'll use Conference Room B."
Today's date is 2024-01-15.
Return ONLY valid JSON, no markdown or explanation.Expected Output:
{
"date": "2024-01-16",
"time": "14:00",
"attendees": ["Sarah", "Mike", "Lisa"],
"topic": "Q4 planning",
"location": "Conference Room B"
}Format enforcement techniques:
# Strong enforcement
"Return ONLY valid JSON. Start with { and end with }"
# Schema validation hint
"The output must be valid JSON matching this TypeScript type:
type Output = { name: string; age: number; active: boolean }"
# Negative instruction
"Do NOT include markdown code blocks. Do NOT add explanations."---
6. Self-Consistency
When to use: Complex reasoning where multiple valid paths exist.
Pattern: 1. Generate multiple reasoning paths (temperature > 0) 2. Extract final answers from each path 3. Select most common answer (majority vote)
Example approach:
# Run this prompt 5 times with temperature=0.7
Solve this logic puzzle. Think through it step by step.
Three friends (Alice, Bob, Carol) each have a different pet (cat, dog, bird).
- Alice doesn't have the dog
- The person with the bird is not Carol
- Bob's pet has four legs
What pet does each person have?Implementation:
# Pseudo-code for self-consistency
answers = []
for i in range(5):
response = llm.generate(prompt, temperature=0.7)
answer = extract_final_answer(response)
answers.append(answer)
final_answer = majority_vote(answers)When to use:
- Math word problems
- Logic puzzles
- Any task where you can verify consistency
---
7. ReAct (Reasoning + Acting)
When to use: Tasks requiring external tool use or information gathering.
Pattern:
Answer the question using the available tools.
Tools available:
- search(query): Search the web
- calculate(expression): Evaluate math
- lookup(term): Look up in knowledge base
Format your response as:
Thought: [reasoning about what to do]
Action: [tool_name(arguments)]
Observation: [result from tool]
... (repeat as needed)
Thought: I now have enough information
Final Answer: [answer]
Question: [user question]Example:
Input:
Question: What is the population of France divided by the population of Belgium?Expected Output:
Thought: I need to find the population of France and Belgium, then calculate the ratio.
Action: search("population of France 2024")
Observation: France has a population of approximately 67.75 million.
Thought: Now I need Belgium's population.
Action: search("population of Belgium 2024")
Observation: Belgium has a population of approximately 11.7 million.
Thought: Now I can calculate the ratio.
Action: calculate(67.75 / 11.7)
Observation: 5.79
Thought: I now have enough information to answer.
Final Answer: The population of France is approximately 5.79 times the population of Belgium.---
8. Tree of Thoughts
When to use: Complex problems requiring exploration of multiple solution paths.
Pattern:
Problem: [complex problem]
Generate 3 different approaches to solve this:
Approach 1: [description]
Evaluation: [pros/cons]
Approach 2: [description]
Evaluation: [pros/cons]
Approach 3: [description]
Evaluation: [pros/cons]
Best approach: [selection with reasoning]
Solution using best approach: [detailed solution]Example:
Input:
Problem: Design a system to handle 10,000 concurrent user sessions with sub-100ms response times.
Generate 3 different architectural approaches:Expected Output:
Approach 1: Horizontal scaling with load balancer
- Multiple stateless app servers behind load balancer
- Session state in Redis cluster
Evaluation: Simple, proven pattern. May have Redis as bottleneck.
Approach 2: Serverless with edge computing
- AWS Lambda@Edge or Cloudflare Workers
- Session state in DynamoDB Global Tables
Evaluation: Auto-scales, but cold starts may impact latency.
Approach 3: Actor model with sticky sessions
- Akka/Orleans cluster with virtual actors
- Session state co-located with actor
Evaluation: Lowest latency, but complex to operate.
Best approach: Approach 1 (Horizontal scaling)
Reasoning: Best balance of simplicity, reliability, and performance for the given requirements.
Solution: [detailed architecture follows]---
9. Retrieval-Augmented Generation
When to use: Tasks requiring factual accuracy or current information.
Pattern:
Use the following context to answer the question. If the answer is not in the context, say "I don't have information about that."
Context:
[retrieved documents]
Question: [user question]
Answer based only on the context provided:Example:
Input:
Use the following context to answer the question.
Context:
Document 1: "The Acme Widget 3000 was released in March 2024. It features
a 12-hour battery life and weighs 250 grams."
Document 2: "Pricing for the Acme Widget 3000 starts at $299 for the base
model. The Pro version with extended battery is $399."
Question: How much does the Acme Widget 3000 cost and what is its battery life?
Answer based only on the context provided:Expected Output:
The Acme Widget 3000 starts at $299 for the base model, with a Pro version
available at $399. The standard model has a 12-hour battery life.Chunking strategies:
| Strategy | Chunk Size | Overlap | Best For |
|---|---|---|---|
| Fixed | 500 tokens | 50 tokens | General purpose |
| Semantic | Paragraph | None | Well-structured docs |
| Sentence | 3-5 sentences | 1 sentence | Q&A systems |
---
10. Meta-Prompting
When to use: Generating or improving prompts programmatically.
Pattern:
You are a prompt engineering expert.
Task: [description of what the prompt should do]
Target model: [GPT-4/Claude/etc.]
Constraints: [length limits, format requirements]
Generate an optimized prompt for this task.Example:
Input:
You are a prompt engineering expert.
Task: Create a prompt that extracts action items from meeting notes
Target model: GPT-4
Constraints:
- Output must be valid JSON
- Each action item needs: task, owner, due_date
- Handle cases where information is missing
Generate an optimized prompt for this task.Expected Output:
Extract action items from the following meeting notes.
For each action item, identify:
- task: The specific action to be taken
- owner: Person responsible (use "Unassigned" if not mentioned)
- due_date: Deadline in YYYY-MM-DD format (use null if not specified)
Meeting Notes:
{meeting_notes}
Respond with a JSON array. Example format:
[
{"task": "Review proposal", "owner": "Sarah", "due_date": "2024-01-20"},
{"task": "Send update", "owner": "Unassigned", "due_date": null}
]
Return ONLY the JSON array, no additional text.---
Pattern Selection Guide
| Task Type | Recommended Pattern |
|---|---|
| Simple classification | Zero-shot |
| Consistent formatting needed | Few-shot |
| Math/logic problems | Chain-of-Thought |
| Need expertise/perspective | Role Prompting |
| API integration | Structured Output |
| High-stakes decisions | Self-Consistency |
| Tool use required | ReAct |
| Complex problem solving | Tree of Thoughts |
| Factual Q&A | RAG |
| Prompt generation | Meta-Prompting |
Tools & Workflows
Read this when running the optimizer/evaluator/orchestrator tools or executing a prompt-engineering workflow.
Quick Start
# Analyze and optimize a prompt file
python scripts/prompt_optimizer.py prompts/my_prompt.txt --analyze
# Evaluate RAG retrieval quality
python scripts/rag_evaluator.py --contexts contexts.json --questions questions.json
# Visualize agent workflow from definition
python scripts/agent_orchestrator.py agent_config.yaml --visualize---
Tools Overview
1. Prompt Optimizer
Analyzes prompts for token efficiency, clarity, and structure. Generates optimized versions.
Input: Prompt text file or string Output: Analysis report with optimization suggestions
Usage:
# Analyze a prompt file
python scripts/prompt_optimizer.py prompt.txt --analyze
# Output:
# Token count: 847
# Estimated cost: $0.0025 (GPT-4)
# Clarity score: 72/100
# Issues found:
# - Ambiguous instruction at line 3
# - Missing output format specification
# - Redundant context (lines 12-15 repeat lines 5-8)
# Suggestions:
# 1. Add explicit output format: "Respond in JSON with keys: ..."
# 2. Remove redundant context to save 89 tokens
# 3. Clarify "analyze" -> "list the top 3 issues with severity ratings"
# Generate optimized version
python scripts/prompt_optimizer.py prompt.txt --optimize --output optimized.txt
# Count tokens for cost estimation
python scripts/prompt_optimizer.py prompt.txt --tokens --model gpt-4
# Extract and manage few-shot examples
python scripts/prompt_optimizer.py prompt.txt --extract-examples --output examples.json---
2. RAG Evaluator
Evaluates Retrieval-Augmented Generation quality by measuring context relevance and answer faithfulness.
Input: Retrieved contexts (JSON) and questions/answers Output: Evaluation metrics and quality report
Usage:
# Evaluate retrieval quality
python scripts/rag_evaluator.py --contexts retrieved.json --questions eval_set.json
# Output:
# === RAG Evaluation Report ===
# Questions evaluated: 50
#
# Retrieval Metrics:
# Context Relevance: 0.78 (target: >0.80)
# Retrieval Precision@5: 0.72
# Coverage: 0.85
#
# Generation Metrics:
# Answer Faithfulness: 0.91
# Groundedness: 0.88
#
# Issues Found:
# - 8 questions had no relevant context in top-5
# - 3 answers contained information not in context
#
# Recommendations:
# 1. Improve chunking strategy for technical documents
# 2. Add metadata filtering for date-sensitive queries
# Evaluate with custom metrics
python scripts/rag_evaluator.py --contexts retrieved.json --questions eval_set.json \
--metrics relevance,faithfulness,coverage
# Export detailed results
python scripts/rag_evaluator.py --contexts retrieved.json --questions eval_set.json \
--output report.json --verbose---
3. Agent Orchestrator
Parses agent definitions and visualizes execution flows. Validates tool configurations.
Input: Agent configuration (YAML/JSON) Output: Workflow visualization, validation report
Usage:
# Validate agent configuration
python scripts/agent_orchestrator.py agent.yaml --validate
# Output:
# === Agent Validation Report ===
# Agent: research_assistant
# Pattern: ReAct
#
# Tools (4 registered):
# [OK] web_search - API key configured
# [OK] calculator - No config needed
# [WARN] file_reader - Missing allowed_paths
# [OK] summarizer - Prompt template valid
#
# Flow Analysis:
# Max depth: 5 iterations
# Estimated tokens/run: 2,400-4,800
# Potential infinite loop: No
#
# Recommendations:
# 1. Add allowed_paths to file_reader for security
# 2. Consider adding early exit condition for simple queries
# Visualize agent workflow (ASCII)
python scripts/agent_orchestrator.py agent.yaml --visualize
# Output:
# ┌─────────────────────────────────────────┐
# │ research_assistant │
# │ (ReAct Pattern) │
# └─────────────────┬───────────────────────┘
# │
# ┌────────▼────────┐
# │ User Query │
# └────────┬────────┘
# │
# ┌────────▼────────┐
# │ Think │◄──────┐
# └────────┬────────┘ │
# │ │
# ┌────────▼────────┐ │
# │ Select Tool │ │
# └────────┬────────┘ │
# │ │
# ┌─────────────┼─────────────┐ │
# ▼ ▼ ▼ │
# [web_search] [calculator] [file_reader]
# │ │ │ │
# └─────────────┼─────────────┘ │
# │ │
# ┌────────▼────────┐ │
# │ Observe │───────┘
# └────────┬────────┘
# │
# ┌────────▼────────┐
# │ Final Answer │
# └─────────────────┘
# Export workflow as Mermaid diagram
python scripts/agent_orchestrator.py agent.yaml --visualize --format mermaid---
Prompt Engineering Workflows
Prompt Optimization Workflow
Use when improving an existing prompt's performance or reducing token costs.
Step 1: Baseline current prompt
python scripts/prompt_optimizer.py current_prompt.txt --analyze --output baseline.jsonStep 2: Identify issues Review the analysis report for:
- Token waste (redundant instructions, verbose examples)
- Ambiguous instructions (unclear output format, vague verbs)
- Missing constraints (no length limits, no format specification)
Step 3: Apply optimization patterns
| Issue | Pattern to Apply |
|---|---|
| Ambiguous output | Add explicit format specification |
| Too verbose | Extract to few-shot examples |
| Inconsistent results | Add role/persona framing |
| Missing edge cases | Add constraint boundaries |
Step 4: Generate optimized version
python scripts/prompt_optimizer.py current_prompt.txt --optimize --output optimized.txtStep 5: Compare results
python scripts/prompt_optimizer.py optimized.txt --analyze --compare baseline.json
# Shows: token reduction, clarity improvement, issues resolvedStep 6: Validate with test cases Run both prompts against your evaluation set and compare outputs.
---
Few-Shot Example Design Workflow
Use when creating examples for in-context learning.
Step 1: Define the task clearly
Task: Extract product entities from customer reviews
Input: Review text
Output: JSON with {product_name, sentiment, features_mentioned}Step 2: Select diverse examples (3-5 recommended)
| Example Type | Purpose |
|---|---|
| Simple case | Shows basic pattern |
| Edge case | Handles ambiguity |
| Complex case | Multiple entities |
| Negative case | What NOT to extract |
Step 3: Format consistently
Example 1:
Input: "Love my new iPhone 15, the camera is amazing!"
Output: {"product_name": "iPhone 15", "sentiment": "positive", "features_mentioned": ["camera"]}
Example 2:
Input: "The laptop was okay but battery life is terrible."
Output: {"product_name": "laptop", "sentiment": "mixed", "features_mentioned": ["battery life"]}Step 4: Validate example quality
python scripts/prompt_optimizer.py prompt_with_examples.txt --validate-examples
# Checks: consistency, coverage, format alignmentStep 5: Test with held-out cases Ensure model generalizes beyond your examples.
---
Structured Output Design Workflow
Use when you need reliable JSON/XML/structured responses.
Step 1: Define schema
{
"type": "object",
"properties": {
"summary": {"type": "string", "maxLength": 200},
"sentiment": {"enum": ["positive", "negative", "neutral"]},
"confidence": {"type": "number", "minimum": 0, "maximum": 1}
},
"required": ["summary", "sentiment"]
}Step 2: Include schema in prompt
Respond with JSON matching this schema:
- summary (string, max 200 chars): Brief summary of the content
- sentiment (enum): One of "positive", "negative", "neutral"
- confidence (number 0-1): Your confidence in the sentimentStep 3: Add format enforcement
IMPORTANT: Respond ONLY with valid JSON. No markdown, no explanation.
Start your response with { and end with }Step 4: Validate outputs
python scripts/prompt_optimizer.py structured_prompt.txt --validate-schema schema.json---
Common Patterns Quick Reference
| Pattern | When to Use | Example |
|---|---|---|
| Zero-shot | Simple, well-defined tasks | "Classify this email as spam or not spam" |
| Few-shot | Complex tasks, consistent format needed | Provide 3-5 examples before the task |
| Chain-of-Thought | Reasoning, math, multi-step logic | "Think step by step..." |
| Role Prompting | Expertise needed, specific perspective | "You are an expert tax accountant..." |
| Structured Output | Need parseable JSON/XML | Include schema + format enforcement |
---
Common Commands
# Prompt Analysis
python scripts/prompt_optimizer.py prompt.txt --analyze # Full analysis
python scripts/prompt_optimizer.py prompt.txt --tokens # Token count only
python scripts/prompt_optimizer.py prompt.txt --optimize # Generate optimized version
# RAG Evaluation
python scripts/rag_evaluator.py --contexts ctx.json --questions q.json # Evaluate
python scripts/rag_evaluator.py --contexts ctx.json --compare baseline # Compare to baseline
# Agent Development
python scripts/agent_orchestrator.py agent.yaml --validate # Validate config
python scripts/agent_orchestrator.py agent.yaml --visualize # Show workflow
python scripts/agent_orchestrator.py agent.yaml --estimate-cost # Token estimation---
Troubleshooting
| Problem | Cause | Solution |
|---|---|---|
| Token count seems inaccurate | Character-based estimation varies by language and special characters | Use --model flag matching your target model; Claude uses a 3.5 char/token ratio vs 4.0 for GPT models |
| Clarity score is low despite clear prompt | Vague-pattern detector flags common words like "analyze" or "some" even in valid contexts | Review flagged lines individually; not every match is a true issue --- focus on genuinely ambiguous instructions |
| Few-shot examples not detected | Examples do not follow the Input:/Output: or Example N: labeling convention | Format examples with explicit Input: and Output: prefixes so the extractor can parse them |
| RAG evaluator shows 0.0 for all metrics | Input JSON schema mismatch --- missing question, content, or question_id keys | Verify JSON uses the expected keys (question/query, content/text, question_id/query_id) |
| Agent YAML parsing fails | Built-in YAML parser is simplified and cannot handle advanced syntax (anchors, multi-line blocks) | Convert config to JSON, or restructure YAML to use only simple key-value pairs and dash-prefixed lists |
| Optimization produces minimal changes | --optimize only performs whitespace normalization, not semantic rewriting | Use --analyze first to get suggestions, then manually apply structural improvements before re-running --optimize |
| Mermaid diagram renders incorrectly | More than 6 tools overflow the generated subgraph | Reduce tool count in the config or manually edit the Mermaid output to split into sub-diagrams |
---
Success Criteria
- Prompt clarity score above 70/100 on all production prompts, measured via
prompt_optimizer.py --analyze - Token efficiency improved by 30%+ after applying optimization suggestions and removing redundant content
- RAG context relevance at or above 0.80 across evaluation sets, verified by
rag_evaluator.py - Answer faithfulness at or above 0.95 with zero unsupported claims in critical workflows
- Agent validation passes with zero errors for all deployed agent configurations
- Cost per agent run within budget --- estimated monthly spend confirmed via
agent_orchestrator.py --estimate-cost - Few-shot example coverage includes edge cases --- at least 1 simple, 1 complex, and 1 negative example per prompt template
---
Tool Reference
prompt_optimizer.py
Purpose: Static analysis tool for prompt engineering. Estimates token counts, scores clarity and structure, detects ambiguous instructions and redundant content, extracts few-shot examples, and generates optimized prompt versions.
Usage:
python scripts/prompt_optimizer.py <prompt_file> [options]Parameters:
| Flag | Short | Type | Default | Description |
|---|---|---|---|---|
prompt | _(positional)_ | string | _(required)_ | Path to the prompt text file to analyze |
--analyze | -a | flag | off | Run full analysis (clarity, structure, issues, suggestions) |
--tokens | -t | flag | off | Count tokens and estimate cost only |
--optimize | -O | flag | off | Generate whitespace-optimized version of the prompt |
--extract-examples | -e | flag | off | Extract few-shot examples (Input/Output pairs) as JSON |
--model | -m | choice | gpt-4 | Model for token/cost estimation. Choices: gpt-4, gpt-4-turbo, gpt-3.5-turbo, claude-3-opus, claude-3-sonnet, claude-3-haiku |
--output | -o | string | _(none)_ | Write results to this file path |
--json | -j | flag | off | Output analysis as JSON instead of human-readable report |
--compare | -c | string | _(none)_ | Path to a baseline analysis JSON file for comparison |
Example:
python scripts/prompt_optimizer.py prompt.txt --analyze --model claude-3-sonnet --jsonOutput Formats:
- Default (text): Human-readable report with metrics, scores, detected sections, issues, and suggestions
- JSON (`--json`): Structured
PromptAnalysisobject with keys:token_count,estimated_cost,model,clarity_score,structure_score,issues,suggestions,sections,has_examples,example_count,has_output_format,word_count,line_count - Token-only (`--tokens`): Single-line token count and cost estimate
- Examples (`--extract-examples`): JSON array of
{input_text, output_text, index}objects - Optimized (`--optimize`): Cleaned prompt text with normalized whitespace
---
rag_evaluator.py
Purpose: Evaluates Retrieval-Augmented Generation quality by measuring context relevance (lexical overlap, term coverage), answer faithfulness (claim-level verification), groundedness (ROUGE-L), and retrieval metrics (Precision@K, MRR, NDCG).
Usage:
python scripts/rag_evaluator.py --contexts <contexts.json> --questions <questions.json> [options]Parameters:
| Flag | Short | Type | Default | Description |
|---|---|---|---|---|
--contexts | -c | string | _(required)_ | Path to JSON file with retrieved contexts. Expected keys per object: question_id/query_id, content/text |
--questions | -q | string | _(required)_ | Path to JSON file with questions and answers. Expected keys per object: id, question/query, answer/response, expected/ground_truth |
--k | int | 5 | Number of top contexts to evaluate per question | |
--output | -o | string | _(none)_ | Write detailed report to this JSON file |
--json | -j | flag | off | Output as JSON instead of human-readable text |
--verbose | -v | flag | off | Include per-question detail breakdowns in the report |
--compare | string | _(none)_ | Path to a baseline report JSON for metric comparison |
Example:
python scripts/rag_evaluator.py --contexts retrieved.json --questions eval_set.json --k 10 --verbose --output report.jsonOutput Formats:
- Default (text): Human-readable report with summary, retrieval metrics (context relevance, Precision@K), generation metrics (faithfulness, groundedness), issues, and recommendations
- JSON (`--json`): Structured
RAGEvaluationReportobject with keys:total_questions,avg_context_relevance,avg_faithfulness,avg_groundedness,retrieval_metrics,coverage,issues,recommendations,question_details - Verbose (`--verbose`): Adds per-question
question_detailsarray containing individual context scores and faithfulness breakdowns
---
agent_orchestrator.py
Purpose: Parses agent configurations (YAML or JSON), validates tool registrations and flow correctness, generates ASCII or Mermaid workflow diagrams, and estimates token costs per run and monthly spend.
Usage:
python scripts/agent_orchestrator.py <config_file> [options]Parameters:
| Flag | Short | Type | Default | Description |
|---|---|---|---|---|
config | _(positional)_ | string | _(required)_ | Path to agent configuration file (YAML or JSON) |
--validate | -V | flag | off | Validate agent configuration (errors, warnings, tool status). Runs by default if no other action is specified |
--visualize | -v | flag | off | Generate workflow diagram |
--format | -f | choice | ascii | Visualization format. Choices: ascii, mermaid |
--estimate-cost | -e | flag | off | Estimate token usage and costs |
--runs | -r | int | 100 | Daily run count for monthly cost projection |
--output | -o | string | _(none)_ | Write output to this file path |
--json | -j | flag | off | Output validation and cost results as JSON |
Example:
python scripts/agent_orchestrator.py agent.yaml --validate --visualize --format mermaid --output workflow.mdOutput Formats:
- Validation (text): Agent info, tool status with OK/WARN indicators, flow analysis (max iterations, token estimate, loop detection), errors, and warnings
- Validation (JSON, `--json`): Structured
ValidationResultobject with keys:is_valid,errors,warnings,tool_status,estimated_tokens_per_run,potential_infinite_loop,max_depth - Visualization (`--visualize`): ASCII box-drawing diagram (default) or Mermaid flowchart (
--format mermaid) showing the agent pattern flow and registered tools - Cost estimation (`--estimate-cost`): Token range per run, cost range per run, and projected monthly cost at the specified daily run rate
#!/usr/bin/env python3
"""
Agent Orchestrator - Tool for designing and validating agent workflows
Features:
- Parse agent configurations (YAML/JSON)
- Validate tool registrations
- Visualize execution flows (ASCII/Mermaid)
- Estimate token usage per run
- Detect potential issues (loops, missing tools)
Usage:
python agent_orchestrator.py agent.yaml --validate
python agent_orchestrator.py agent.yaml --visualize
python agent_orchestrator.py agent.yaml --visualize --format mermaid
python agent_orchestrator.py agent.yaml --estimate-cost
"""
import argparse
import json
import re
import sys
from pathlib import Path
from typing import Dict, List, Optional, Set, Tuple, Any
from dataclasses import dataclass, asdict, field
from enum import Enum
class AgentPattern(Enum):
"""Supported agent patterns"""
REACT = "react"
PLAN_EXECUTE = "plan-execute"
TOOL_USE = "tool-use"
MULTI_AGENT = "multi-agent"
CUSTOM = "custom"
@dataclass
class ToolDefinition:
"""Definition of an agent tool"""
name: str
description: str
parameters: Dict[str, Any] = field(default_factory=dict)
required_config: List[str] = field(default_factory=list)
estimated_tokens: int = 100
@dataclass
class AgentConfig:
"""Agent configuration"""
name: str
pattern: AgentPattern
description: str
tools: List[ToolDefinition]
max_iterations: int = 10
system_prompt: str = ""
temperature: float = 0.7
model: str = "gpt-4"
@dataclass
class ValidationResult:
"""Result of agent validation"""
is_valid: bool
errors: List[str]
warnings: List[str]
tool_status: Dict[str, str]
estimated_tokens_per_run: Tuple[int, int] # (min, max)
potential_infinite_loop: bool
max_depth: int
def parse_yaml_simple(content: str) -> Dict[str, Any]:
"""Simple YAML parser for agent configs (no external dependencies)"""
result = {}
current_key = None
current_list = None
indent_stack = [(0, result)]
lines = content.split('\n')
for line in lines:
# Skip empty lines and comments
stripped = line.strip()
if not stripped or stripped.startswith('#'):
continue
# Calculate indent
indent = len(line) - len(line.lstrip())
# Check for list item
if stripped.startswith('- '):
item = stripped[2:].strip()
if current_list is not None:
# Check if it's a key-value pair
if ':' in item and not item.startswith('{'):
key, _, value = item.partition(':')
current_list.append({key.strip(): value.strip().strip('"\'')})
else:
current_list.append(item.strip('"\''))
continue
# Check for key-value pair
if ':' in stripped:
key, _, value = stripped.partition(':')
key = key.strip()
value = value.strip().strip('"\'')
# Pop indent stack as needed
while indent_stack and indent <= indent_stack[-1][0] and len(indent_stack) > 1:
indent_stack.pop()
current_dict = indent_stack[-1][1]
if value:
# Simple key-value
current_dict[key] = value
current_list = None
else:
# Start of nested structure or list
# Peek ahead to see if it's a list
next_line_idx = lines.index(line) + 1
if next_line_idx < len(lines):
next_stripped = lines[next_line_idx].strip()
if next_stripped.startswith('- '):
current_dict[key] = []
current_list = current_dict[key]
else:
current_dict[key] = {}
indent_stack.append((indent + 2, current_dict[key]))
current_list = None
return result
def load_config(path: Path) -> AgentConfig:
"""Load agent configuration from file"""
content = path.read_text(encoding='utf-8')
# Try JSON first
if path.suffix == '.json':
data = json.loads(content)
else:
# Try YAML
try:
data = parse_yaml_simple(content)
except Exception:
# Fallback to JSON if YAML parsing fails
data = json.loads(content)
# Parse pattern
pattern_str = data.get('pattern', 'react').lower()
try:
pattern = AgentPattern(pattern_str)
except ValueError:
pattern = AgentPattern.CUSTOM
# Parse tools
tools = []
for tool_data in data.get('tools', []):
if isinstance(tool_data, dict):
tools.append(ToolDefinition(
name=tool_data.get('name', 'unknown'),
description=tool_data.get('description', ''),
parameters=tool_data.get('parameters', {}),
required_config=tool_data.get('required_config', []),
estimated_tokens=tool_data.get('estimated_tokens', 100)
))
elif isinstance(tool_data, str):
tools.append(ToolDefinition(name=tool_data, description=''))
return AgentConfig(
name=data.get('name', 'agent'),
pattern=pattern,
description=data.get('description', ''),
tools=tools,
max_iterations=int(data.get('max_iterations', 10)),
system_prompt=data.get('system_prompt', ''),
temperature=float(data.get('temperature', 0.7)),
model=data.get('model', 'gpt-4')
)
def validate_agent(config: AgentConfig) -> ValidationResult:
"""Validate agent configuration"""
errors = []
warnings = []
tool_status = {}
# Validate name
if not config.name:
errors.append("Agent name is required")
# Validate tools
if not config.tools:
warnings.append("No tools defined - agent will have limited capabilities")
tool_names = set()
for tool in config.tools:
# Check for duplicates
if tool.name in tool_names:
errors.append(f"Duplicate tool name: {tool.name}")
tool_names.add(tool.name)
# Check required config
if tool.required_config:
missing = [c for c in tool.required_config if not c.startswith('$')]
if missing:
tool_status[tool.name] = f"WARN: Missing config: {missing}"
else:
tool_status[tool.name] = "OK"
else:
tool_status[tool.name] = "OK - No config needed"
# Check description
if not tool.description:
warnings.append(f"Tool '{tool.name}' has no description")
# Validate pattern-specific requirements
if config.pattern == AgentPattern.MULTI_AGENT:
if len(config.tools) < 2:
warnings.append("Multi-agent pattern typically requires 2+ specialized tools")
# Check for potential infinite loops
potential_loop = config.max_iterations > 50
# Estimate tokens
base_tokens = len(config.system_prompt.split()) * 1.3 if config.system_prompt else 200
tool_tokens = sum(t.estimated_tokens for t in config.tools)
min_tokens = int(base_tokens + tool_tokens)
max_tokens = int((base_tokens + tool_tokens * 2) * config.max_iterations)
return ValidationResult(
is_valid=len(errors) == 0,
errors=errors,
warnings=warnings,
tool_status=tool_status,
estimated_tokens_per_run=(min_tokens, max_tokens),
potential_infinite_loop=potential_loop,
max_depth=config.max_iterations
)
def generate_ascii_diagram(config: AgentConfig) -> str:
"""Generate ASCII workflow diagram"""
lines = []
# Header
width = max(40, len(config.name) + 10)
lines.append("┌" + "─" * width + "┐")
lines.append("│" + config.name.center(width) + "│")
lines.append("│" + f"({config.pattern.value} Pattern)".center(width) + "│")
lines.append("└" + "─" * (width // 2 - 1) + "┬" + "─" * (width // 2) + "┘")
lines.append(" " * (width // 2) + "│")
# User Query
lines.append(" " * (width // 2 - 8) + "┌───────────────┐")
lines.append(" " * (width // 2 - 8) + "│ User Query │")
lines.append(" " * (width // 2 - 8) + "└───────┬───────┘")
lines.append(" " * (width // 2) + "│")
if config.pattern == AgentPattern.REACT:
# ReAct loop
lines.append(" " * (width // 2 - 8) + "┌───────────────┐")
lines.append(" " * (width // 2 - 8) + "│ Think │◄──────┐")
lines.append(" " * (width // 2 - 8) + "└───────┬───────┘ │")
lines.append(" " * (width // 2) + "│ │")
lines.append(" " * (width // 2 - 8) + "┌───────────────┐ │")
lines.append(" " * (width // 2 - 8) + "│ Select Tool │ │")
lines.append(" " * (width // 2 - 8) + "└───────┬───────┘ │")
lines.append(" " * (width // 2) + "│ │")
# Tools
if config.tools:
tool_line = " ".join([f"[{t.name}]" for t in config.tools[:4]])
if len(config.tools) > 4:
tool_line += " ..."
lines.append(" " * 4 + tool_line)
lines.append(" " * (width // 2) + "│ │")
lines.append(" " * (width // 2 - 8) + "┌───────────────┐ │")
lines.append(" " * (width // 2 - 8) + "│ Observe │───────┘")
lines.append(" " * (width // 2 - 8) + "└───────┬───────┘")
elif config.pattern == AgentPattern.PLAN_EXECUTE:
# Plan phase
lines.append(" " * (width // 2 - 8) + "┌───────────────┐")
lines.append(" " * (width // 2 - 8) + "│ Create Plan │")
lines.append(" " * (width // 2 - 8) + "└───────┬───────┘")
lines.append(" " * (width // 2) + "│")
# Execute loop
lines.append(" " * (width // 2 - 8) + "┌───────────────┐")
lines.append(" " * (width // 2 - 8) + "│ Execute Step │◄──────┐")
lines.append(" " * (width // 2 - 8) + "└───────┬───────┘ │")
lines.append(" " * (width // 2) + "│ │")
if config.tools:
tool_line = " ".join([f"[{t.name}]" for t in config.tools[:4]])
lines.append(" " * 4 + tool_line)
lines.append(" " * (width // 2) + "│ │")
lines.append(" " * (width // 2 - 8) + "┌───────────────┐ │")
lines.append(" " * (width // 2 - 8) + "│ Check Done? │───────┘")
lines.append(" " * (width // 2 - 8) + "└───────┬───────┘")
else:
# Generic tool use
lines.append(" " * (width // 2 - 8) + "┌───────────────┐")
lines.append(" " * (width // 2 - 8) + "│ Process Query │")
lines.append(" " * (width // 2 - 8) + "└───────┬───────┘")
lines.append(" " * (width // 2) + "│")
if config.tools:
for tool in config.tools[:6]:
lines.append(" " * (width // 2 - 8) + f"├──▶ [{tool.name}]")
if len(config.tools) > 6:
lines.append(" " * (width // 2 - 8) + "├──▶ [...]")
# Final answer
lines.append(" " * (width // 2) + "│")
lines.append(" " * (width // 2 - 8) + "┌───────────────┐")
lines.append(" " * (width // 2 - 8) + "│ Final Answer │")
lines.append(" " * (width // 2 - 8) + "└───────────────┘")
return '\n'.join(lines)
def generate_mermaid_diagram(config: AgentConfig) -> str:
"""Generate Mermaid flowchart"""
lines = ["```mermaid", "flowchart TD"]
# Start and query
lines.append(f" subgraph {config.name}[{config.name}]")
lines.append(" direction TB")
lines.append(" A[User Query] --> B{Process}")
if config.pattern == AgentPattern.REACT:
lines.append(" B --> C[Think]")
lines.append(" C --> D{Select Tool}")
for i, tool in enumerate(config.tools[:6]):
lines.append(f" D -->|{tool.name}| T{i}[{tool.name}]")
lines.append(f" T{i} --> E[Observe]")
lines.append(" E -->|Continue| C")
lines.append(" E -->|Done| F[Final Answer]")
elif config.pattern == AgentPattern.PLAN_EXECUTE:
lines.append(" B --> P[Create Plan]")
lines.append(" P --> X{Execute Step}")
for i, tool in enumerate(config.tools[:6]):
lines.append(f" X -->|{tool.name}| T{i}[{tool.name}]")
lines.append(f" T{i} --> R[Review]")
lines.append(" R -->|More Steps| X")
lines.append(" R -->|Complete| F[Final Answer]")
else:
for i, tool in enumerate(config.tools[:6]):
lines.append(f" B -->|use| T{i}[{tool.name}]")
lines.append(f" T{i} --> F[Final Answer]")
lines.append(" end")
lines.append("```")
return '\n'.join(lines)
def estimate_cost(config: AgentConfig, runs: int = 100) -> Dict[str, Any]:
"""Estimate token costs for agent runs"""
validation = validate_agent(config)
min_tokens, max_tokens = validation.estimated_tokens_per_run
# Cost per 1K tokens
costs = {
'gpt-4': {'input': 0.03, 'output': 0.06},
'gpt-4-turbo': {'input': 0.01, 'output': 0.03},
'gpt-3.5-turbo': {'input': 0.0005, 'output': 0.0015},
'claude-3-opus': {'input': 0.015, 'output': 0.075},
'claude-3-sonnet': {'input': 0.003, 'output': 0.015},
}
model_cost = costs.get(config.model, costs['gpt-4'])
# Assume 60% input, 40% output
input_tokens = min_tokens * 0.6
output_tokens = min_tokens * 0.4
cost_per_run_min = (input_tokens / 1000 * model_cost['input'] +
output_tokens / 1000 * model_cost['output'])
input_tokens_max = max_tokens * 0.6
output_tokens_max = max_tokens * 0.4
cost_per_run_max = (input_tokens_max / 1000 * model_cost['input'] +
output_tokens_max / 1000 * model_cost['output'])
return {
'model': config.model,
'tokens_per_run': {'min': min_tokens, 'max': max_tokens},
'cost_per_run': {'min': round(cost_per_run_min, 4), 'max': round(cost_per_run_max, 4)},
'estimated_monthly': {
'runs': runs * 30,
'cost_min': round(cost_per_run_min * runs * 30, 2),
'cost_max': round(cost_per_run_max * runs * 30, 2)
}
}
def format_validation_report(config: AgentConfig, result: ValidationResult) -> str:
"""Format validation result as human-readable report"""
lines = []
lines.append("=" * 50)
lines.append("AGENT VALIDATION REPORT")
lines.append("=" * 50)
lines.append("")
lines.append(f"📋 AGENT INFO")
lines.append(f" Name: {config.name}")
lines.append(f" Pattern: {config.pattern.value}")
lines.append(f" Model: {config.model}")
lines.append("")
lines.append(f"🔧 TOOLS ({len(config.tools)} registered)")
for tool in config.tools:
status = result.tool_status.get(tool.name, "Unknown")
emoji = "✅" if status.startswith("OK") else "⚠️"
lines.append(f" {emoji} {tool.name} - {status}")
lines.append("")
lines.append("📊 FLOW ANALYSIS")
lines.append(f" Max iterations: {result.max_depth}")
lines.append(f" Estimated tokens: {result.estimated_tokens_per_run[0]:,} - {result.estimated_tokens_per_run[1]:,}")
lines.append(f" Potential loop: {'⚠️ Yes' if result.potential_infinite_loop else '✅ No'}")
lines.append("")
if result.errors:
lines.append(f"❌ ERRORS ({len(result.errors)})")
for error in result.errors:
lines.append(f" • {error}")
lines.append("")
if result.warnings:
lines.append(f"⚠️ WARNINGS ({len(result.warnings)})")
for warning in result.warnings:
lines.append(f" • {warning}")
lines.append("")
# Overall status
if result.is_valid:
lines.append("✅ VALIDATION PASSED")
else:
lines.append("❌ VALIDATION FAILED")
lines.append("")
lines.append("=" * 50)
return '\n'.join(lines)
def main():
parser = argparse.ArgumentParser(
description="Agent Orchestrator - Design and validate agent workflows",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Examples:
%(prog)s agent.yaml --validate
%(prog)s agent.yaml --visualize
%(prog)s agent.yaml --visualize --format mermaid
%(prog)s agent.yaml --estimate-cost --runs 100
Agent config format (YAML):
name: research_assistant
pattern: react
model: gpt-4
max_iterations: 10
tools:
- name: web_search
description: Search the web
required_config: [api_key]
- name: calculator
description: Evaluate math expressions
"""
)
parser.add_argument('config', help='Agent configuration file (YAML or JSON)')
parser.add_argument('--validate', '-V', action='store_true', help='Validate agent configuration')
parser.add_argument('--visualize', '-v', action='store_true', help='Visualize agent workflow')
parser.add_argument('--format', '-f', choices=['ascii', 'mermaid'], default='ascii',
help='Visualization format (default: ascii)')
parser.add_argument('--estimate-cost', '-e', action='store_true', help='Estimate token costs')
parser.add_argument('--runs', '-r', type=int, default=100, help='Daily runs for cost estimation')
parser.add_argument('--output', '-o', help='Output file path')
parser.add_argument('--json', '-j', action='store_true', help='Output as JSON')
args = parser.parse_args()
# Load config
config_path = Path(args.config)
if not config_path.exists():
print(f"Error: Config file not found: {args.config}", file=sys.stderr)
sys.exit(1)
try:
config = load_config(config_path)
except Exception as e:
print(f"Error parsing config: {e}", file=sys.stderr)
sys.exit(1)
# Default to validate if no action specified
if not any([args.validate, args.visualize, args.estimate_cost]):
args.validate = True
output_parts = []
# Validate
if args.validate:
result = validate_agent(config)
if args.json:
output_parts.append(json.dumps(asdict(result), indent=2))
else:
output_parts.append(format_validation_report(config, result))
# Visualize
if args.visualize:
if args.format == 'mermaid':
diagram = generate_mermaid_diagram(config)
else:
diagram = generate_ascii_diagram(config)
output_parts.append(diagram)
# Cost estimation
if args.estimate_cost:
costs = estimate_cost(config, args.runs)
if args.json:
output_parts.append(json.dumps(costs, indent=2))
else:
output_parts.append("")
output_parts.append("💰 COST ESTIMATION")
output_parts.append(f" Model: {costs['model']}")
output_parts.append(f" Tokens per run: {costs['tokens_per_run']['min']:,} - {costs['tokens_per_run']['max']:,}")
output_parts.append(f" Cost per run: ${costs['cost_per_run']['min']:.4f} - ${costs['cost_per_run']['max']:.4f}")
output_parts.append(f" Monthly ({costs['estimated_monthly']['runs']:,} runs):")
output_parts.append(f" Min: ${costs['estimated_monthly']['cost_min']:.2f}")
output_parts.append(f" Max: ${costs['estimated_monthly']['cost_max']:.2f}")
# Output
output = '\n'.join(output_parts)
print(output)
if args.output:
Path(args.output).write_text(output)
print(f"\nOutput saved to {args.output}")
if __name__ == '__main__':
main()
#!/usr/bin/env python3
"""
Prompt Optimizer - Static analysis tool for prompt engineering
Features:
- Token estimation (GPT-4/Claude approximation)
- Prompt structure analysis
- Clarity scoring
- Few-shot example extraction and management
- Optimization suggestions
Usage:
python prompt_optimizer.py prompt.txt --analyze
python prompt_optimizer.py prompt.txt --tokens --model gpt-4
python prompt_optimizer.py prompt.txt --optimize --output optimized.txt
python prompt_optimizer.py prompt.txt --extract-examples --output examples.json
"""
import argparse
import json
import re
import sys
from pathlib import Path
from typing import Dict, List, Optional, Tuple
from dataclasses import dataclass, asdict
# Token estimation ratios (chars per token approximation)
TOKEN_RATIOS = {
'gpt-4': 4.0,
'gpt-3.5': 4.0,
'claude': 3.5,
'default': 4.0
}
# Cost per 1K tokens (input)
COST_PER_1K = {
'gpt-4': 0.03,
'gpt-4-turbo': 0.01,
'gpt-3.5-turbo': 0.0005,
'claude-3-opus': 0.015,
'claude-3-sonnet': 0.003,
'claude-3-haiku': 0.00025,
'default': 0.01
}
@dataclass
class PromptAnalysis:
"""Results of prompt analysis"""
token_count: int
estimated_cost: float
model: str
clarity_score: int
structure_score: int
issues: List[Dict[str, str]]
suggestions: List[str]
sections: List[Dict[str, any]]
has_examples: bool
example_count: int
has_output_format: bool
word_count: int
line_count: int
@dataclass
class FewShotExample:
"""A single few-shot example"""
input_text: str
output_text: str
index: int
def estimate_tokens(text: str, model: str = 'default') -> int:
"""Estimate token count based on character ratio"""
ratio = TOKEN_RATIOS.get(model, TOKEN_RATIOS['default'])
return int(len(text) / ratio)
def estimate_cost(token_count: int, model: str = 'default') -> float:
"""Estimate cost based on token count"""
cost_per_1k = COST_PER_1K.get(model, COST_PER_1K['default'])
return round((token_count / 1000) * cost_per_1k, 6)
def find_ambiguous_instructions(text: str) -> List[Dict[str, str]]:
"""Find vague or ambiguous instructions"""
issues = []
# Vague verbs that need specificity
vague_patterns = [
(r'\b(analyze|process|handle|deal with)\b', 'Vague verb - specify the exact action'),
(r'\b(good|nice|appropriate|suitable)\b', 'Subjective term - define specific criteria'),
(r'\b(etc\.|and so on|and more)\b', 'Open-ended list - enumerate all items explicitly'),
(r'\b(if needed|as necessary|when appropriate)\b', 'Conditional without criteria - specify when'),
(r'\b(some|several|many|few|various)\b', 'Vague quantity - use specific numbers'),
]
lines = text.split('\n')
for i, line in enumerate(lines, 1):
for pattern, message in vague_patterns:
matches = re.finditer(pattern, line, re.IGNORECASE)
for match in matches:
issues.append({
'type': 'ambiguity',
'line': i,
'text': match.group(),
'message': message,
'context': line.strip()[:80]
})
return issues
def find_redundant_content(text: str) -> List[Dict[str, str]]:
"""Find potentially redundant content"""
issues = []
lines = text.split('\n')
# Check for repeated phrases (3+ words)
seen_phrases = {}
for i, line in enumerate(lines, 1):
words = line.split()
for j in range(len(words) - 2):
phrase = ' '.join(words[j:j+3]).lower()
phrase = re.sub(r'[^\w\s]', '', phrase)
if phrase and len(phrase) > 10:
if phrase in seen_phrases:
issues.append({
'type': 'redundancy',
'line': i,
'text': phrase,
'message': f'Phrase repeated from line {seen_phrases[phrase]}',
'context': line.strip()[:80]
})
else:
seen_phrases[phrase] = i
return issues
def check_output_format(text: str) -> Tuple[bool, List[str]]:
"""Check if prompt specifies output format"""
suggestions = []
format_indicators = [
r'respond\s+(in|with)\s+(json|xml|csv|markdown)',
r'output\s+format',
r'return\s+(only|just)',
r'format:\s*\n',
r'\{["\']?\w+["\']?\s*:', # JSON-like structure
r'```\w*\n', # Code block
]
has_format = any(re.search(p, text, re.IGNORECASE) for p in format_indicators)
if not has_format:
suggestions.append('Add explicit output format specification (e.g., "Respond in JSON with keys: ...")')
return has_format, suggestions
def extract_sections(text: str) -> List[Dict[str, any]]:
"""Extract logical sections from prompt"""
sections = []
# Common section patterns
section_patterns = [
r'^#+\s+(.+)$', # Markdown headers
r'^([A-Z][A-Za-z\s]+):\s*$', # Title Case Label:
r'^(Instructions|Context|Examples?|Input|Output|Task|Role|Format)[:.]',
]
lines = text.split('\n')
current_section = {'name': 'Introduction', 'start': 1, 'content': []}
for i, line in enumerate(lines, 1):
is_header = False
for pattern in section_patterns:
match = re.match(pattern, line.strip(), re.IGNORECASE)
if match:
if current_section['content']:
current_section['end'] = i - 1
current_section['line_count'] = len(current_section['content'])
sections.append(current_section)
current_section = {
'name': match.group(1).strip() if match.groups() else line.strip(),
'start': i,
'content': []
}
is_header = True
break
if not is_header:
current_section['content'].append(line)
# Add last section
if current_section['content']:
current_section['end'] = len(lines)
current_section['line_count'] = len(current_section['content'])
sections.append(current_section)
return sections
def extract_few_shot_examples(text: str) -> List[FewShotExample]:
"""Extract few-shot examples from prompt"""
examples = []
# Pattern 1: "Example N:" or "Example:" blocks
example_pattern = r'Example\s*\d*:\s*\n(Input:\s*(.+?)\n(?:Output:\s*(.+?)(?=\n\nExample|\n\n[A-Z]|\Z)))'
matches = re.finditer(example_pattern, text, re.DOTALL | re.IGNORECASE)
for i, match in enumerate(matches, 1):
examples.append(FewShotExample(
input_text=match.group(2).strip() if match.group(2) else '',
output_text=match.group(3).strip() if match.group(3) else '',
index=i
))
# Pattern 2: Input/Output pairs without "Example" label
if not examples:
io_pattern = r'Input:\s*["\']?(.+?)["\']?\s*\nOutput:\s*(.+?)(?=\nInput:|\Z)'
matches = re.finditer(io_pattern, text, re.DOTALL)
for i, match in enumerate(matches, 1):
examples.append(FewShotExample(
input_text=match.group(1).strip(),
output_text=match.group(2).strip(),
index=i
))
return examples
def calculate_clarity_score(text: str, issues: List[Dict]) -> int:
"""Calculate clarity score (0-100)"""
score = 100
# Deduct for issues
score -= len([i for i in issues if i['type'] == 'ambiguity']) * 5
score -= len([i for i in issues if i['type'] == 'redundancy']) * 3
# Check for structure
if not re.search(r'^#+\s|^[A-Z][a-z]+:', text, re.MULTILINE):
score -= 10 # No clear sections
# Check for instruction clarity
if not re.search(r'(you (should|must|will)|please|your task)', text, re.IGNORECASE):
score -= 5 # No clear directives
return max(0, min(100, score))
def calculate_structure_score(sections: List[Dict], has_format: bool, has_examples: bool) -> int:
"""Calculate structure score (0-100)"""
score = 50 # Base score
# Bonus for clear sections
if len(sections) >= 2:
score += 15
if len(sections) >= 4:
score += 10
# Bonus for output format
if has_format:
score += 15
# Bonus for examples
if has_examples:
score += 10
return min(100, score)
def generate_suggestions(analysis: PromptAnalysis) -> List[str]:
"""Generate optimization suggestions"""
suggestions = []
if not analysis.has_output_format:
suggestions.append('Add explicit output format: "Respond in JSON with keys: ..."')
if analysis.example_count == 0:
suggestions.append('Consider adding 2-3 few-shot examples for consistent outputs')
elif analysis.example_count == 1:
suggestions.append('Add 1-2 more examples to improve consistency')
elif analysis.example_count > 5:
suggestions.append(f'Consider reducing examples from {analysis.example_count} to 3-5 to save tokens')
if analysis.clarity_score < 70:
suggestions.append('Improve clarity: replace vague terms with specific instructions')
if analysis.token_count > 2000:
suggestions.append(f'Prompt is {analysis.token_count} tokens - consider condensing for cost efficiency')
# Check for role prompting
if not re.search(r'you are|act as|as a\s+\w+', analysis.sections[0].get('content', [''])[0] if analysis.sections else '', re.IGNORECASE):
suggestions.append('Consider adding role context: "You are an expert..."')
return suggestions
def analyze_prompt(text: str, model: str = 'gpt-4') -> PromptAnalysis:
"""Perform comprehensive prompt analysis"""
# Basic metrics
token_count = estimate_tokens(text, model)
cost = estimate_cost(token_count, model)
word_count = len(text.split())
line_count = len(text.split('\n'))
# Find issues
ambiguity_issues = find_ambiguous_instructions(text)
redundancy_issues = find_redundant_content(text)
all_issues = ambiguity_issues + redundancy_issues
# Extract structure
sections = extract_sections(text)
examples = extract_few_shot_examples(text)
has_format, format_suggestions = check_output_format(text)
# Calculate scores
clarity_score = calculate_clarity_score(text, all_issues)
structure_score = calculate_structure_score(sections, has_format, len(examples) > 0)
analysis = PromptAnalysis(
token_count=token_count,
estimated_cost=cost,
model=model,
clarity_score=clarity_score,
structure_score=structure_score,
issues=all_issues,
suggestions=[],
sections=[{'name': s['name'], 'lines': f"{s['start']}-{s.get('end', s['start'])}"} for s in sections],
has_examples=len(examples) > 0,
example_count=len(examples),
has_output_format=has_format,
word_count=word_count,
line_count=line_count
)
analysis.suggestions = generate_suggestions(analysis) + format_suggestions
return analysis
def optimize_prompt(text: str) -> str:
"""Generate optimized version of prompt"""
optimized = text
# Remove redundant whitespace
optimized = re.sub(r'\n{3,}', '\n\n', optimized)
optimized = re.sub(r' {2,}', ' ', optimized)
# Trim lines
lines = [line.rstrip() for line in optimized.split('\n')]
optimized = '\n'.join(lines)
return optimized.strip()
def format_report(analysis: PromptAnalysis) -> str:
"""Format analysis as human-readable report"""
report = []
report.append("=" * 50)
report.append("PROMPT ANALYSIS REPORT")
report.append("=" * 50)
report.append("")
report.append("📊 METRICS")
report.append(f" Token count: {analysis.token_count:,}")
report.append(f" Estimated cost: ${analysis.estimated_cost:.4f} ({analysis.model})")
report.append(f" Word count: {analysis.word_count:,}")
report.append(f" Line count: {analysis.line_count}")
report.append("")
report.append("📈 SCORES")
report.append(f" Clarity: {analysis.clarity_score}/100 {'✅' if analysis.clarity_score >= 70 else '⚠️'}")
report.append(f" Structure: {analysis.structure_score}/100 {'✅' if analysis.structure_score >= 70 else '⚠️'}")
report.append("")
report.append("📋 STRUCTURE")
report.append(f" Sections: {len(analysis.sections)}")
report.append(f" Examples: {analysis.example_count} {'✅' if analysis.has_examples else '❌'}")
report.append(f" Output format: {'✅ Specified' if analysis.has_output_format else '❌ Missing'}")
report.append("")
if analysis.sections:
report.append(" Detected sections:")
for section in analysis.sections:
report.append(f" - {section['name']} (lines {section['lines']})")
report.append("")
if analysis.issues:
report.append(f"⚠️ ISSUES FOUND ({len(analysis.issues)})")
for issue in analysis.issues[:10]: # Limit to first 10
report.append(f" Line {issue['line']}: {issue['message']}")
report.append(f" Found: \"{issue['text']}\"")
if len(analysis.issues) > 10:
report.append(f" ... and {len(analysis.issues) - 10} more issues")
report.append("")
if analysis.suggestions:
report.append("💡 SUGGESTIONS")
for i, suggestion in enumerate(analysis.suggestions, 1):
report.append(f" {i}. {suggestion}")
report.append("")
report.append("=" * 50)
return '\n'.join(report)
def main():
parser = argparse.ArgumentParser(
description="Prompt Optimizer - Analyze and optimize prompts",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Examples:
%(prog)s prompt.txt --analyze
%(prog)s prompt.txt --tokens --model claude-3-sonnet
%(prog)s prompt.txt --optimize --output optimized.txt
%(prog)s prompt.txt --extract-examples --output examples.json
"""
)
parser.add_argument('prompt', help='Prompt file to analyze')
parser.add_argument('--analyze', '-a', action='store_true', help='Run full analysis')
parser.add_argument('--tokens', '-t', action='store_true', help='Count tokens only')
parser.add_argument('--optimize', '-O', action='store_true', help='Generate optimized version')
parser.add_argument('--extract-examples', '-e', action='store_true', help='Extract few-shot examples')
parser.add_argument('--model', '-m', default='gpt-4',
choices=['gpt-4', 'gpt-4-turbo', 'gpt-3.5-turbo', 'claude-3-opus', 'claude-3-sonnet', 'claude-3-haiku'],
help='Model for token/cost estimation')
parser.add_argument('--output', '-o', help='Output file path')
parser.add_argument('--json', '-j', action='store_true', help='Output as JSON')
parser.add_argument('--compare', '-c', help='Compare with baseline analysis JSON')
args = parser.parse_args()
# Read prompt file
prompt_path = Path(args.prompt)
if not prompt_path.exists():
print(f"Error: File not found: {args.prompt}", file=sys.stderr)
sys.exit(1)
text = prompt_path.read_text(encoding='utf-8')
# Tokens only
if args.tokens:
token_count = estimate_tokens(text, args.model)
cost = estimate_cost(token_count, args.model)
if args.json:
print(json.dumps({
'tokens': token_count,
'cost': cost,
'model': args.model
}, indent=2))
else:
print(f"Tokens: {token_count:,}")
print(f"Estimated cost: ${cost:.4f} ({args.model})")
sys.exit(0)
# Extract examples
if args.extract_examples:
examples = extract_few_shot_examples(text)
output = [asdict(ex) for ex in examples]
if args.output:
Path(args.output).write_text(json.dumps(output, indent=2))
print(f"Extracted {len(examples)} examples to {args.output}")
else:
print(json.dumps(output, indent=2))
sys.exit(0)
# Optimize
if args.optimize:
optimized = optimize_prompt(text)
if args.output:
Path(args.output).write_text(optimized)
print(f"Optimized prompt written to {args.output}")
# Show comparison
orig_tokens = estimate_tokens(text, args.model)
new_tokens = estimate_tokens(optimized, args.model)
saved = orig_tokens - new_tokens
print(f"Tokens: {orig_tokens:,} -> {new_tokens:,} (saved {saved:,})")
else:
print(optimized)
sys.exit(0)
# Default: full analysis
analysis = analyze_prompt(text, args.model)
# Compare with baseline
if args.compare:
baseline_path = Path(args.compare)
if baseline_path.exists():
baseline = json.loads(baseline_path.read_text())
print("\n📊 COMPARISON WITH BASELINE")
print(f" Tokens: {baseline.get('token_count', 0):,} -> {analysis.token_count:,}")
print(f" Clarity: {baseline.get('clarity_score', 0)} -> {analysis.clarity_score}")
print(f" Issues: {len(baseline.get('issues', []))} -> {len(analysis.issues)}")
print()
if args.json:
print(json.dumps(asdict(analysis), indent=2))
else:
print(format_report(analysis))
# Write to output file
if args.output:
output_data = asdict(analysis)
Path(args.output).write_text(json.dumps(output_data, indent=2))
print(f"\nAnalysis saved to {args.output}")
if __name__ == '__main__':
main()