| name | inspect-scout |
| description | Analyze AI agent transcripts using Inspect Scout scanners, grep patterns, and LLM-based analysis |
| user-invocable | false |
Inspect Scout Reference
Inspect Scout is a library for analyzing AI agent transcripts with parallel processing and visualization capabilities. Use this when analyzing evaluation logs for patterns, behaviors, or specific events.
Installation: pip install inspect-scout
Source: https://github.com/meridianlabs-ai/inspect_scout
Core Concepts
Transcripts
A Transcript represents an LLM conversation to analyze (e.g., an agent rollout or sample from an Inspect eval). It contains:
class Transcript(TranscriptInfo):
transcript_id: str
source_type: str | None
source_id: str | None
source_uri: str | None
task_set: str | None
task_id: str | None
task_repeat: int | None
agent: str | None
model: str | None
model_options: dict | None
score: JsonValue | None
success: bool | None
error: str | None
limit: str | None
message_count: int | None
total_time: float | None
total_tokens: int | None
messages: list[ChatMessage]
events: list[Event]
metadata: dict[str, Any]
Scanners
Scanners are functions that analyze transcript content and return Result objects. Decorated with @scanner to specify what content to scan.
from inspect_scout import scanner, Scanner, Result
@scanner(messages="all")
def my_scanner() -> Scanner[Transcript]:
async def scan(transcript: Transcript) -> Result:
return Result(value=True, explanation="Found pattern")
return scan
@scanner(messages=["assistant"])
def assistant_scanner() -> Scanner[Transcript]:
...
@scanner(events=["tool"])
def tool_scanner() -> Scanner[ToolEvent]:
...
@scanner(messages=["user", "assistant"], events=["tool", "error"])
def combined_scanner() -> Scanner[Transcript]:
...
Results
class Result(BaseModel):
uuid: str | None
value: JsonValue
answer: str | None
explanation: str | None
metadata: dict | None
references: list[Reference]
label: str | None
type: str | None
Built-in Scanners
llm_scanner - LLM-based Analysis
Uses an LLM to analyze transcripts based on a question.
from inspect_scout import scanner, llm_scanner, Scanner
from inspect_scout._transcript.types import Transcript
@scanner(messages="all")
def did_agent_succeed() -> Scanner[Transcript]:
return llm_scanner(
question="Did the agent successfully complete its task?",
answer="boolean"
)
@scanner(messages="all")
def classify_failure() -> Scanner[Transcript]:
return llm_scanner(
question="Why did the agent fail?",
answer=["gave_up", "hit_limit", "wrong_approach", "environment_error", "other"]
)
@scanner(messages="all")
def rate_performance() -> Scanner[Transcript]:
return llm_scanner(
question="Rate the agent's performance from 1-10",
answer="numeric"
)
Answer types:
"boolean" - True/False
"numeric" - Number
"string" - Free text
list[str] - Single choice from labels
AnswerMultiLabel - Multi-label classification
AnswerStructured - Custom structured output
Additional options:
llm_scanner(
question="...",
answer="boolean",
model="openai/gpt-4o",
template="...",
template_variables={...},
preprocessor=...,
retry_refusals=3,
name="my_scanner"
)
grep_scanner - Pattern Matching
Fast pattern-based scanning without LLM calls.
from inspect_scout import scanner, grep_scanner, Scanner
from inspect_scout._transcript.types import Transcript
@scanner(messages=["assistant"])
def find_secrets() -> Scanner[Transcript]:
return grep_scanner(["password", "secret", "token", "api_key"])
@scanner(messages="all")
def find_urls() -> Scanner[Transcript]:
return grep_scanner(r"https?://\S+", regex=True)
@scanner(messages="all", events=["tool"])
def categorize_errors() -> Scanner[Transcript]:
return grep_scanner({
"permission_denied": ["permission denied", "access denied"],
"not_found": ["not found", "no such file"],
"timeout": ["timeout", "timed out"],
})
Options:
grep_scanner(
pattern,
regex=False,
ignore_case=True,
word_boundary=False,
name="my_grep"
)
Returns:
- Single pattern/list:
Result with value=count, explanation=context snippets
- Dict patterns:
list[Result] with one per label
CLI Usage
Running Scans
scout scan scanner.py -T ./logs --model openai/gpt-4o
scout scan scanner.py -T ./logs -F "task_set='cybench'" --model openai/gpt-4o
scout scan scanners/ -T ./logs --model openai/gpt-4o
scout scan scanner.py::my_scanner -T ./logs --model openai/gpt-4o
Viewing Results
scout view
scout view --scans ./scans/scan_id=abc123
scout view --scans s3://my-bucket/scans
scout scan list
Managing Scans
scout scan resume "scans/scan_id=iGEYSF6N7J3AoxzQmGgrZs"
scout scan complete "scan_id"
Parallelism Options
scout scan scanner.py -T ./logs \
--max-transcripts 25 \
--max-connections 10 \
--max-processes 4
Python API
Loading Transcripts
from inspect_scout import transcripts_from, columns as c
transcripts = transcripts_from("./logs")
transcripts = transcripts_from("s3://bucket/logs")
transcripts = transcripts_from("./logs")
transcripts = transcripts.where(c.task_set == "cybench")
transcripts = transcripts.where(c.model == "gpt-4")
transcripts = transcripts.where("score > 0.5")
transcripts = transcripts.limit(100)
transcripts = transcripts.shuffle(seed=42)
Running Scans Programmatically
from inspect_scout import scan, ScanJob, scanjob
await scan(
my_scanner(),
transcripts="./logs",
model="openai/gpt-4o"
)
@scanjob
def analysis_job() -> ScanJob:
return ScanJob(
scanners=[
did_agent_succeed(),
classify_failure(),
find_secrets(),
],
transcripts="./logs",
model="openai/gpt-4o"
)
Accessing Results
from inspect_scout import scan_results_df, scan_status
status = scan_status("scans/scan_id=abc123")
print(status.complete)
print(status.summary)
results = scan_results_df("scans/scan_id=abc123")
df = results.scanners["my_scanner"]
results = scan_results_df("scans/...", rows="results")
results = scan_results_df("scans/...", rows="transcripts")
DuckDB Integration
from inspect_scout import scan_results_db
db = scan_results_db("scans/scan_id=abc123")
Project Configuration
Create scout.yaml for reusable configuration:
transcripts: ./logs
model: openai/gpt-4o
transcripts:
path: s3://bucket/logs
where: "task_set = 'cybench'"
Then run: scout scan scanner.py (uses config automatically)
Helper Functions
messages_as_str
Convert messages to a string for LLM analysis:
from inspect_scout._scanner.extract import messages_as_str, MessagesPreprocessor
messages_str, extract_refs = await messages_as_str(transcript)
messages_str, extract_refs = await messages_as_str(
transcript,
preprocessor=MessagesPreprocessor(
exclude_system=True,
exclude_reasoning=True,
exclude_tool_calls=True,
),
include_ids=True
)
Validation
Validate scanner results against ground truth:
from inspect_scout import scan
await scan(
my_scanner(),
transcripts="./logs",
validation="validation.csv"
)
Results include validation_target, validation_result, and validation_predicate columns.
Common Patterns
Behavioral Analysis Scanner
@scanner(messages="all", events=["tool"])
def analyze_strategy() -> Scanner[Transcript]:
return llm_scanner(
question="""Analyze the agent's strategy:
1. What approach did it take?
2. Did it adapt when encountering obstacles?
3. What tools did it use most?""",
answer="string"
)
Error Detection Scanner
@scanner(messages="all", events=["tool", "error"])
def detect_errors() -> Scanner[Transcript]:
return grep_scanner({
"command_errors": ["command not found", "permission denied", "no such file"],
"api_errors": ["rate limit", "timeout", "connection refused"],
"logic_errors": ["assertion failed", "unexpected", "invalid"],
})
Success Criteria Scanner
@scanner(messages="all")
def verify_success() -> Scanner[Transcript]:
return llm_scanner(
question="Did the agent retrieve the flag and output it correctly?",
answer="boolean"
)
Multi-label Classification
from inspect_scout._llm_scanner import AnswerMultiLabel
@scanner(messages="all")
def classify_behaviors() -> Scanner[Transcript]:
return llm_scanner(
question="What behaviors did the agent exhibit?",
answer=AnswerMultiLabel(labels=[
"exploration",
"exploitation",
"backtracking",
"tool_chaining",
"giving_up"
])
)