project-development
by Murat Can Koylan
This skill should be used for project-level decisions about LLM-powered systems: whether an LLM is the right primitive for the task at hand, the shape of a multi-stage batch or agent pipeline, token and cost estimation, choosing between single-agent and multi-agent at the project level, structured output design for downstream parsing, and structuring agent-assisted iteration. Use this when the unit of work is a whole project or a multi-stage pipeline. Route individual tool design to tool-design and individual skill-loading or context-budget tactics to context-optimization.
Labels from its files: runs commands. 4 files, read Oct 21, 2026. Nothing was run.
npx skills add https://github.com/muratcankoylan/Agent-Skills-for-Context-Engineering --skill project-developmentWhat it lets an agent do
Each label, and the line in the skill's files that set it.
- Runs commands
Allows a shell or ships scripts the agent runs.
- It ships 1 script the agent can run.
Findings
Matches of fixed checks, with the text they matched.
No check matched anything in this skill's files.
Not a safety rating, and no finding is not a safety guarantee. These are fixed checks of the files as read on Oct 21, 2026. Nothing was run. How skills are checked.
SKILL.md
As the publisher wrote it. Shown as text, never rendered.
---
name: project-development
description: "This skill should be used for project-level decisions about LLM-powered systems: whether an LLM is the right primitive for the task at hand, the shape of a multi-stage batch or agent pipeline, token and cost estimation, choosing between single-agent and multi-agent at the project level, structured output design for downstream parsing, and structuring agent-assisted iteration. Use this when the unit of work is a whole project or a multi-stage pipeline. Route individual tool design to tool-design and individual skill-loading or context-budget tactics to context-optimization."
---
# Project Development Methodology
This skill covers the principles for identifying tasks suited to LLM processing, designing effective project architectures, and iterating rapidly using agent-assisted development. The methodology applies whether building a batch processing pipeline, a multi-agent research system, or an interactive agent application.
The unit of work for this skill is the whole project or a multi-stage pipeline. Individual tool design (descriptions, schemas, error messages) belongs to `tool-design`. Per-skill activation routing belongs to the corresponding skill plus the corpus index. This skill owns the project-level questions: should you build this with an LLM at all, what shape should the pipeline take, what does it cost, how should it be iterated.
## When to Activate
Activate this skill when the unit of work is a whole project or pipeline:
- Deciding whether an LLM is the right primitive for a task at all (task-model fit before any code).
- Shaping a multi-stage batch or agent pipeline (acquire / prepare / process / parse / render).
- Estimating tokens, dollar cost, and timelines for an LLM-heavy project.
- Choosing between single-agent and multi-agent at the project level.
- Structuring agent-assisted iteration (where the agent helps build the project itself).
- Designing structured output at the pipeline contract level (cross-stage handoff format).
Do not activate this skill for adjacent work owned by other skills:
- Per-tool description, schema, naming, response format, error message: `tool-design`.
- Per-trajectory token-efficiency tactics (masking, partitioning, caching): `context-optimization`.
- Deciding to split work across sub-agents at the agent topology level: `multi-agent-patterns`.
- Designing the autonomous control loop (locked metrics, novelty gates, human approval boundaries): `harness-engineering`.
## Core Concepts
### Task-Model Fit Recognition
Evaluate task-model fit before writing any code, because building automation on a fundamentally mismatched task wastes days of effort. Run every proposed task through these two tables to decide proceed-or-stop.
**Proceed when the task has these characteristics:**
| Characteristic | Rationale |
|----------------|-----------|
| Synthesis across sources | LLMs combine information from multiple inputs better than rule-based alternatives |
| Subjective judgment with rubrics | Grading, evaluation, and classification with criteria map naturally to language reasoning |
| Natural language output | When the goal is human-readable text, LLMs deliver it natively |
| Error tolerance | Individual failures do not break the overall system, so LLM non-determinism is acceptable |
| Batch processing | No conversational state required between items, which keeps context clean |
| Domain knowledge in training | The model already has relevant context, reducing prompt engineering overhead |
**Stop when the task has these characteristics:**
| Characteristic | Rationale |
|----------------|-----------|
| Precise computation | Math, counting, and exact algorithms are unreliable in language models |
| Real-time requirements | LLM latency is too high for sub-second responses |
| Perfect accuracy requirements | Hallucination risk makes 100% accuracy impossible |
| Proprietary data dependence | The model lacks necessary context and cannot acquire it from prompts alone |
| Sequential dependencies | Each step depends heavily on the previous result, compounding errors |
| Deterministic output requirements | Same input must produce identical output, which LLMs cannot guarantee |
### The Manual Prototype Step
Always validate task-model fit with a manual test before investing in automation. Copy one representative input into the model interface, evaluate the output quality, and use the result to answer these questions:
- Does the model have the knowledge required for this task?
- Can the model produce output in the format needed?
- What level of quality should be expected at scale?
- Are there obvious failure modes to address?
Do this because a failed manual prototype predicts a failed automated system, while a successful one provides both a quality baseline and a prompt-design template. The test takes minutes and prevents hours of wasted development.
### Pipeline Architecture
Structure LLM projects as staged pipelines because separation of deterministic and non-deterministic stages enables fast iteration and cost control. Design each stage to be:
- **Discrete**: Clear boundaries between stages so each can be debugged independently
- **Idempotent**: Re-running produces the same result, preventing duplicate work
- **Cacheable**: Intermediate results persist to disk, avoiding expensive re-computation
- **Independent**: Each stage can run separately, enabling selective re-execution
**Use this canonical pipeline structure:**
```
acquire -> prepare -> process -> parse -> render
```
1. **Acquire**: Fetch raw data from sources (APIs, files, databases)
2. **Prepare**: Transform data into prompt format
3. **Process**: Execute LLM calls (the expensive, non-deterministic step)
4. **Parse**: Extract structured data from LLM outputs
5. **Render**: Generate final outputs (reports, files, visualizations)
Stages 1, 2, 4, and 5 are deterministic. Stage 3 is non-deterministic and expensive. Maintain this separation because it allows re-running the expensive LLM stage only when necessary, while iterating quickly on parsing and rendering.
### File System as State Machine
Use the file system to track pipeline state rather than databases or in-memory structures, because file existence provides natural idempotency and human-readable debugging.
```
data/{id}/
raw.json # acquire stage complete
prompt.md # prepare stage complete
response.md # process stage complete
parsed.json # parse stage complete
```
Check if an item needs processing by checking whether the output file exists. Re-run a stage by deleting its output file and downstream files. Debug by reading the intermediate files directly. This pattern works because each directory is independent, enabling simple parallelization and trivial caching.
### Structured Output Design
Design prompts for structured, parseable outputs because prompt design directly determines parsing reliability. Include these elements in every structured prompt:
1. **Section markers**: Explicit headers or prefixes that parsers can match on
2. **Format examples**: Show exactly what output should look like
3. **Rationale disclosure**: State "I will be parsing this programmatically" so the model prioritizes format compliance
4. **Constrained values**: Enumerated options, score ranges, and fixed formats
Build parsers that handle LLM output variations gracefully, because LLMs do not follow instructions perfectly. Use regex patterns flexible enough for minor formatting variations, provide sensible defaults when sections are missing, and log parsing failures for review rather than crashing.
### Agent-Assisted Development
Use agent-capable models to accelerate development through rapid iteration: describe the project goal and constraints, let the agent generate initial implementation, test and iterate on specific failures, then refine prompts and architecture based on results.
Adopt these practices because they keep agent output focused and high-quality:
- Provide clear, specific requirements upfront to reduce revision cycles
- Break large projects into discrete components so each can be validated independently
- Test each component before moving to the next to catch failures early
- Keep the agent focused on one task at a time to prevent context degradation
### Cost and Scale Estimation
Estimate LLM processing costs before starting, because token costs compound quickly at scale and late discovery of budget overruns forces costly rework. Use this formula:
```
Total cost = (items x tokens_per_item x price_per_token) + API overhead
```
For batch processing, estimate input tokens per item (prompt + context), estimate output tokens per item (typical response length), multiply by item count, and add 20-30% buffer for retries and failures.
Track actual costs during development. If costs exceed estimates significantly, reduce context length through truncation, use smaller models for simpler items, cache and reuse partial results, or add parallel processing to reduce wall-clock time.
## Detailed Topics
### Choosing Single vs Multi-Agent Architecture
Default to single-agent pipelines for batch processing with independent items, because they are simpler to manage, cheaper to run, and easier to debug. Escalate to multi-agent architectures only when one of these conditions holds:
- Parallel exploration of different aspects is required
- The task exceeds single context window capacity
- Specialized sub-agents demonstrably improve quality on benchmarks
Choose multi-agent for context isolation, not role anthropomorphization. Sub-agents get fresh context windows for focused subtasks, which prevents context degradation on long-running tasks.
See `multi-agent-patterns` skill for detailed architecture guidance.
### Architectural Reduction
Start with minimal architecture and add complexity only when production evidence proves it necessary, because over-engineered scaffolding often constrains rather than enables model performance.
Vercel's d0 case study reports improved success after reducing many specialized tools to two primitives: command execution and SQL (claim-project-development-vercel-d0-reduction). The file system agent pattern uses standard Unix utilities instead of custom exploration tools.
**Reduce when:**
- The data layer is well-documented and consistently structured
- The model has sufficient reasoning capability
- Specialized tools are constraining rather than enabling
- More time is spent maintaining scaffolding than improving outcomes
**Add complexity when:**
- The underlying data is messy, inconsistent, or poorly documented
- The domain requires specialized knowledge the model lacks
- Safety constraints require limiting agent capabilities
- Operations are truly complex and benefit from structured workflows
See `tool-design` skill for detailed tool architecture guidance.
### Iteration and Refactoring
Plan for multiple architectural iterations from the start, because production agent systems at scale always require refactoring. Manus refactored their agent framework five times since launch. The Bitter Lesson suggests that structures added for current model limitations become constraints as models improve.
Build for change by following these practices:
- Keep architecture simple and unopinionated so refactoring is cheap
- Test across model generations to verify the harness is not limiting performance
- Design systems that benefit from model improvements rather than locking in limitations
## Practical Guidance
### Project Planning Template
Follow this template in order, because each step validates assumptions before the next step invests effort.
1. **Task Analysis**
- Define the input and desired output explicitly
- Classify: synthesis, generation, classification, or analysis
- Set an acceptable error rate based on business impact
- Estimate the value per successful completion to justify costs
2. **Manual Validation**
- Test one representative example with the target model
- Evaluate output quality and format against requirements
- Identify failure modes that need parser hardening or prompt revision
- Estimate tokens per item for cost projection
3. **Architecture Selection**
- Choose single pipeline vs multi-agent based on the criteria above
- Identify required tools and data sources
- Design storage and caching strategy using file-system state
- Plan parallelization approach for the process stage
4. **Cost Estimation**
- Calculate items x tokens x price with a 20-30% buffer
- Estimate development time for each pipeline stage
- Identify infrastructure requirements (API keys, storage, compute)
- Project ongoing operational costs for production runs
5. **Development Plan**
- Implement stage-by-stage, testing each before proceeding
- Define a testing strategy per stage with expected outputs
- Set iteration milestones tied to quality metrics
- Plan deployment approach with rollback capability
## Examples
**Example 1: Batch Analysis Pipeline (Karpathy's HN Time Capsule)**
Task: Analyze 930 HN discussions from 10 years ago with hindsight grading.
Architecture:
- 5-stage pipeline: fetch -> prompt -> analyze -> parse -> render
- File system state: data/{date}/{item_id}/ with stage output files
- Structured output: 6 sections with explicit format requirements
- Parallel execution: 15 workers for LLM calls
Results: $58 total cost, ~1 hour execution, static HTML output.
**Example 2: Architectural Reduction (Vercel d0)**
Task: Text-to-SQL agent for internal analytics.
Before: many specialized tools with lower measured success and longer average execution.
After: two tools (bash + SQL) with higher measured success and shorter average execution (claim-project-development-vercel-d0-reduction).
Key insight: The semantic layer was already good documentation. Claude just needed access to read files directly.
See [Case Studies](./references/case-studies.md) for detailed analysis.
## Guidelines
1. Validate task-model fit with manual prototyping before building automation
2. Structure pipelines as discrete, idempotent, cacheable stages
3. Use the file system for state management and debugging
4. Design prompts for structured, parseable outputs with explicit format examples
5. Start with minimal architecture; add complexity only when proven necessary
6. Estimate costs early and track throughout development
7. Build robust parsers that handle LLM output variations
8. Expect and plan for multiple architectural iterations
9. Test whether scaffolding helps or constrains model performance
10. Use agent-assisted development for rapid iteration on implementation
## Gotchas
1. **Skipping manual validation**: Building automation before verifying the model can do the task wastes significant time when the approach is fundamentally flawed. Always run one representative example through the model interface first.
2. **Monolithic pipelines**: Combining all stages into one script makes debugging and iteration difficult. Separate stages with persistent intermediate outputs so each can be re-run independently.
3. **Over-constraining the model**: Adding guardrails, pre-filtering, and validation logic that the model could handle on its own reduces performance. Test whether scaffolding helps or hurts before keeping it.
4. **Ignoring costs until production**: Token costs compound quickly at scale. Estimate and track from the beginning to avoid budget surprises that force architectural rework.
5. **Perfect parsing requirements**: Expecting LLMs to follow format instructions perfectly leads to brittle systems. Build robust parsers that handle variations and log failures for review.
6. **Premature optimization**: Adding caching, parallelization, and optimization before the basic pipeline works correctly wastes effort on code that may be discarded during iteration.
7. **Model version lock-in**: Building pipelines that only work with one specific model version creates fragile systems. Test across model generations and abstract the LLM call layer so models can be swapped without rewriting pipeline logic.
8. **Evaluation-less deployment**: Shipping agent pipelines without measuring output quality means regressions go undetected. Define quality metrics during development and run evaluation checks before and after every model or prompt change.
9. **Provenance drift**: Raw inputs, intermediate outputs, and final proposals separated across ad hoc folders become impossible to audit. Keep each pipeline run in a single directory with source evidence, transformations, validation reports, and decisions.
## Integration
This skill owns project-shape and pipeline decisions. Adjacent decisions are owned elsewhere:
- `tool-design`: the per-tool interface layer (descriptions, schemas, response formats, error messages, MCP namespacing, individual tool consolidation). If the question is "what should this specific tool look like" rather than "what should the pipeline look like," route there.
- `multi-agent-patterns`: agent topology decisions (supervisor vs swarm vs hierarchical, handoff protocols, context isolation across agents). This skill picks single-vs-multi at the project level; the topology details belong to multi-agent-patterns.
- `harness-engineering`: the autonomous control loop around the project (locked metrics, novelty gates, run state machine, human approval boundaries). If the question is "how do we make this run unattended for days," route there.
- `context-fundamentals`: the conceptual frame for context constraints that inform prompt design at every stage.
- `evaluation`: outcome measurement and quality gates for pipeline runs.
- `context-compression`: when long-running pipeline stages produce trajectories that need summarization.
## References
Internal references:
- [Case Studies](./references/case-studies.md) - Read when: evaluating architecture tradeoffs or reviewing real-world pipeline implementations (Karpathy HN Capsule, Vercel d0, Manus patterns)
- [Pipeline Patterns](./references/pipeline-patterns.md) - Read when: designing a new pipeline stage layout, choosing caching strategies, or debugging stage boundaries
Related skills in this collection:
- tool-design - Tool architecture and reduction patterns
- multi-agent-patterns - When to use multi-agent architectures
- evaluation - Output evaluation frameworks
External resources:
- Karpathy's HN Time Capsule project: https://github.com/karpathy/hn-time-capsule
- Vercel d0 architectural reduction: https://vercel.com/blog/we-removed-80-percent-of-our-agents-tools
- Manus context engineering: Peak Ji's blog on context engineering lessons
- Anthropic multi-agent research: How we built our multi-agent research system
---
## Skill Metadata
**Created**: 2025-12-25
**Last Updated**: 2026-05-15
**Author**: Agent Skills for Context Engineering Contributors
**Version**: 1.3.0
Scripts (1)
Read as text. anlyon.dev never runs them.
scripts/pipeline_template.pyShowHide
"""
LLM Batch Processing Pipeline Template.
A composable, staged pipeline architecture for LLM batch processing.
Each stage is discrete, idempotent, and cacheable. Customize the acquire,
prepare, process, parse, and render functions for your use case.
Use when:
- Building a new batch processing pipeline with structured LLM outputs
- Prototyping an acquire -> prepare -> process -> parse -> render workflow
- Need a file-system-based state machine for pipeline stage tracking
Usage:
python pipeline_template.py acquire --batch-id 2025-01-15
python pipeline_template.py prepare --batch-id 2025-01-15
python pipeline_template.py process --batch-id 2025-01-15 --workers 10
python pipeline_template.py parse --batch-id 2025-01-15
python pipeline_template.py render --batch-id 2025-01-15
python pipeline_template.py all --batch-id 2025-01-15
python pipeline_template.py clean --batch-id 2025-01-15 --clean-stage process
python pipeline_template.py estimate --batch-id 2025-01-15
Programmatic usage:
from pipeline_template import stage_acquire, stage_prepare, stage_process
stage_acquire("2025-01-15", limit=5)
stage_prepare("2025-01-15")
stage_process("2025-01-15", model="claude-sonnet-4-6", max_workers=3)
"""
import argparse
import json
import re
import time
from concurrent.futures import ThreadPoolExecutor, as_completed
from dataclasses import dataclass, field, asdict
from datetime import date
from pathlib import Path
from typing import Any
__all__ = [
"Item",
"ParsedResult",
"stage_acquire",
"stage_prepare",
"stage_process",
"stage_parse",
"stage_render",
"stage_clean",
"stage_estimate",
"parse_response",
"get_batch_dir",
"get_item_dir",
"get_output_dir",
]
# -----------------------------------------------------------------------------
# Configuration - Customize for your use case
# -----------------------------------------------------------------------------
DATA_DIR = Path("data")
OUTPUT_DIR = Path("output")
# Prompt template with structured output requirements
PROMPT_TEMPLATE = """Analyze the following content and provide your response in exactly this format.
## Summary
[2-3 sentence summary of the content]
## Key Points
- [Point 1]
- [Point 2]
- [Point 3]
## Score
Rating: [1-10]
Confidence: [low/medium/high]
## Reasoning
[Explanation of your analysis]
Follow this format exactly because I will be parsing it programmatically.
---
# Content to Analyze
Title: {title}
{content}
"""
# -----------------------------------------------------------------------------
# Data Structures
# -----------------------------------------------------------------------------
@dataclass
class Item:
"""Represents a single item to process through the pipeline.
Use when: creating items during the acquire stage or loading raw data
from any source (API, database, file system).
"""
id: str
title: str
content: str
metadata: dict[str, Any] = field(default_factory=dict)
@dataclass
class ParsedResult:
"""Structured result from LLM response parsing.
Use when: extracting structured data from free-text LLM responses
during the parse stage.
"""
summary: str = ""
key_points: list[str] = field(default_factory=list)
score: int | None = None
confidence: str = ""
reasoning: str = ""
parse_errors: list[str] = field(default_factory=list)
# -----------------------------------------------------------------------------
# Path Utilities
# -----------------------------------------------------------------------------
def get_batch_dir(batch_id: str) -> Path:
"""Get the data directory for a batch.
Use when: resolving the root directory for a specific batch run.
"""
return DATA_DIR / batch_id
def get_item_dir(batch_id: str, item_id: str) -> Path:
"""Get the directory for a specific item.
Use when: locating stage output files for a single pipeline item.
"""
return get_batch_dir(batch_id) / item_id
def get_output_dir(batch_id: str) -> Path:
"""Get the output directory for a batch.
Use when: writing final rendered outputs (HTML, reports, etc.).
"""
return OUTPUT_DIR / batch_id
# -----------------------------------------------------------------------------
# Stage: Acquire
# -----------------------------------------------------------------------------
def stage_acquire(batch_id: str, limit: int | None = None) -> list[Path]:
"""Stage 1: Acquire raw data from sources.
Use when: fetching data from APIs, databases, or file systems
and persisting it as raw.json per item for downstream stages.
Output: {batch_dir}/{item_id}/raw.json
Returns: List of item directories that were acquired.
"""
batch_dir = get_batch_dir(batch_id)
batch_dir.mkdir(parents=True, exist_ok=True)
# CUSTOMIZE: Replace with your data acquisition logic
items = fetch_items_from_source(limit)
acquired_dirs: list[Path] = []
for item in items:
item_dir = get_item_dir(batch_id, item.id)
item_dir.mkdir(exist_ok=True)
raw_file = item_dir / "raw.json"
if not raw_file.exists():
with open(raw_file, "w") as f:
json.dump(asdict(item), f, indent=2)
print(f"Acquired: {item.id}")
else:
print(f"Cached: {item.id}")
acquired_dirs.append(item_dir)
print(f"\nAcquire complete. {len(items)} items in {batch_dir}")
return acquired_dirs
def fetch_items_from_source(limit: int | None = None) -> list[Item]:
"""CUSTOMIZE: Implement your data fetching logic here.
Use when: pulling raw items from your specific data source.
Replace this with actual API calls, database queries, etc.
"""
# Example: Generate sample items
items: list[Item] = []
for i in range(limit or 10):
items.append(Item(
id=f"item-{i:04d}",
title=f"Sample Item {i}",
content=f"This is sample content for item {i}. " * 10,
metadata={"source": "example", "index": i},
))
return items
# -----------------------------------------------------------------------------
# Stage: Prepare
# -----------------------------------------------------------------------------
def stage_prepare(batch_id: str) -> int:
"""Stage 2: Generate prompts from raw data.
Use when: transforming raw acquired data into LLM-ready prompts
using the configured PROMPT_TEMPLATE.
Output: {batch_dir}/{item_id}/prompt.md
Returns: Number of items prepared.
"""
batch_dir = get_batch_dir(batch_id)
prepared_count = 0
for item_dir in sorted(batch_dir.iterdir()):
if not item_dir.is_dir():
continue
raw_file = item_dir / "raw.json"
prompt_file = item_dir / "prompt.md"
if not raw_file.exists():
continue
if prompt_file.exists():
continue
with open(raw_file) as f:
item_data: dict[str, Any] = json.load(f)
prompt = generate_prompt(item_data)
with open(prompt_file, "w") as f:
f.write(prompt)
prepared_count += 1
print(f"Prepared: {item_dir.name}")
print(f"\nPrepare complete. {prepared_count} items prepared.")
return prepared_count
def generate_prompt(item_data: dict[str, Any]) -> str:
"""Generate prompt from item data using template.
Use when: converting a raw item dict into a formatted prompt string.
"""
return PROMPT_TEMPLATE.format(
title=item_data.get("title", "Untitled"),
content=item_data.get("content", ""),
)
# -----------------------------------------------------------------------------
# Stage: Process
# -----------------------------------------------------------------------------
def stage_process(
batch_id: str,
model: str = "claude-sonnet-4-6",
max_workers: int = 5,
) -> list[tuple[str, int, str | None]]:
"""Stage 3: Execute LLM calls (the expensive, non-deterministic stage).
Use when: sending prepared prompts to the LLM API and caching
responses. This is the only non-deterministic stage.
Output: {batch_dir}/{item_id}/response.md
Returns: List of (item_id, char_count, error_or_none) tuples.
"""
batch_dir = get_batch_dir(batch_id)
# Collect items needing processing
to_process: list[tuple[Path, str]] = []
for item_dir in sorted(batch_dir.iterdir()):
if not item_dir.is_dir():
continue
prompt_file = item_dir / "prompt.md"
response_file = item_dir / "response.md"
if prompt_file.exists() and not response_file.exists():
to_process.append((item_dir, prompt_file.read_text()))
if not to_process:
print("No items to process.")
return []
print(f"Processing {len(to_process)} items with {max_workers} workers...")
results: list[tuple[str, int, str | None]] = []
def process_one(args: tuple[Path, str]) -> tuple[str, int, str | None]:
item_dir, prompt = args
response_file = item_dir / "response.md"
try:
# CUSTOMIZE: Replace with your LLM API call
response = call_llm(prompt, model)
with open(response_file, "w") as f:
f.write(response)
return (item_dir.name, len(response), None)
except Exception as e:
return (item_dir.name, 0, str(e))
with ThreadPoolExecutor(max_workers=max_workers) as executor:
futures = {executor.submit(process_one, item): item for item in to_process}
for future in as_completed(futures):
item_id, chars, error = future.result()
results.append((item_id, chars, error))
if error:
print(f" {item_id}: Error - {error}")
else:
print(f" {item_id}: Done ({chars} chars)")
print(f"\nProcess complete. {len(results)} items processed.")
return results
def call_llm(prompt: str, model: str) -> str:
"""CUSTOMIZE: Implement your LLM API call here.
Use when: sending a single prompt to the LLM and returning the response.
Replace with actual OpenAI, Anthropic, etc. API calls.
"""
# Example mock response - replace with actual API call
#
# import anthropic
# client = anthropic.Anthropic()
# message = client.messages.create(
# model=model,
# max_tokens=1024,
# messages=[{"role": "user", "content": prompt}],
# )
# return message.content[0].text
# Simulate API delay
time.sleep(0.1)
# Return mock structured response
return """## Summary
This is a sample summary of the analyzed content.
## Key Points
- First key observation from the content
- Second important finding
- Third notable aspect
## Score
Rating: 7
Confidence: medium
## Reasoning
The content demonstrates several characteristics that merit this rating.
The analysis considered multiple factors including relevance and clarity.
"""
# -----------------------------------------------------------------------------
# Stage: Parse
# -----------------------------------------------------------------------------
def stage_parse(batch_id: str) -> list[dict[str, Any]]:
"""Stage 4: Extract structured data from LLM responses.
Use when: converting free-text LLM responses into structured
ParsedResult objects for aggregation and rendering.
Output: {batch_dir}/{item_id}/parsed.json
Returns: List of parsed result dicts with item IDs.
"""
batch_dir = get_batch_dir(batch_id)
all_results: list[dict[str, Any]] = []
for item_dir in sorted(batch_dir.iterdir()):
if not item_dir.is_dir():
continue
response_file = item_dir / "response.md"
parsed_file = item_dir / "parsed.json"
if not response_file.exists():
continue
response = response_file.read_text()
result = parse_response(response)
with open(parsed_file, "w") as f:
json.dump(asdict(result), f, indent=2)
all_results.append({
"id": item_dir.name,
**asdict(result),
})
error_count = len(result.parse_errors)
print(f"Parsed: {item_dir.name} (score={result.score}, errors={error_count})")
# Save aggregated results
agg_file = batch_dir / "all_results.json"
with open(agg_file, "w") as f:
json.dump(all_results, f, indent=2)
print(f"\nParse complete. Results saved to {agg_file}")
return all_results
def parse_response(text: str) -> ParsedResult:
"""Parse structured LLM response with graceful error handling.
Use when: extracting sections, scores, and lists from a formatted
LLM response. Logs parse errors rather than raising exceptions.
"""
result = ParsedResult()
# Extract summary
try:
result.summary = extract_section(text, "Summary") or ""
except Exception as e:
result.parse_errors.append(f"Summary: {e}")
# Extract key points
try:
result.key_points = extract_list_items(text, "Key Points")
except Exception as e:
result.parse_errors.append(f"Key Points: {e}")
# Extract score
try:
result.score = extract_score(text, "Rating", 1, 10)
except Exception as e:
result.parse_errors.append(f"Score: {e}")
# Extract confidence
try:
result.confidence = extract_field(text, "Confidence") or ""
except Exception as e:
result.parse_errors.append(f"Confidence: {e}")
# Extract reasoning
try:
result.reasoning = extract_section(text, "Reasoning") or ""
except Exception as e:
result.parse_errors.append(f"Reasoning: {e}")
return result
def extract_section(text: str, section_name: str) -> str | None:
"""Extract content between section headers.
Use when: pulling a named markdown section from LLM output.
"""
pattern = rf'(?:^|\n)(?:#+ *)?{re.escape(section_name)}[:\s]*\n(.*?)(?=\n#|\Z)'
match = re.search(pattern, text, re.IGNORECASE | re.DOTALL)
return match.group(1).strip() if match else None
def extract_field(text: str, field_name: str) -> str | None:
"""Extract value after field label.
Use when: pulling a single key-value field (e.g., "Confidence: high").
"""
pattern = rf'(?:\*\*)?{re.escape(field_name)}(?:\*\*)?[\s:\-]+([^\n]+)'
match = re.search(pattern, text, re.IGNORECASE)
return match.group(1).strip() if match else None
def extract_list_items(text: str, section_name: str) -> list[str]:
"""Extract bullet points from a section.
Use when: parsing a markdown list under a named section header.
"""
section = extract_section(text, section_name)
if not section:
return []
items = re.findall(r'^[\-\*]\s*(.+)$', section, re.MULTILINE)
return [item.strip() for item in items]
def extract_score(
text: str, field_name: str, min_val: int, max_val: int
) -> int | None:
"""Extract and validate numeric score.
Use when: pulling a bounded integer score from LLM output.
"""
raw = extract_field(text, field_name)
if not raw:
return None
match = re.search(r'\d+', raw)
if not match:
return None
score = int(match.group())
return max(min_val, min(max_val, score))
# -----------------------------------------------------------------------------
# Stage: Render
# -----------------------------------------------------------------------------
def stage_render(batch_id: str) -> Path | None:
"""Stage 5: Generate final outputs from parsed results.
Use when: producing human-readable output (HTML, reports)
from aggregated parsed results.
Output: {output_dir}/index.html
Returns: Path to the rendered output file, or None if no results.
"""
batch_dir = get_batch_dir(batch_id)
output_dir = get_output_dir(batch_id)
output_dir.mkdir(parents=True, exist_ok=True)
# Load aggregated results
results_file = batch_dir / "all_results.json"
if not results_file.exists():
print("No results to render. Run parse stage first.")
return None
with open(results_file) as f:
results: list[dict[str, Any]] = json.load(f)
# CUSTOMIZE: Replace with your rendering logic
html = render_html(results, batch_id)
output_file = output_dir / "index.html"
with open(output_file, "w") as f:
f.write(html)
print(f"Rendered: {output_file}")
return output_file
def render_html(results: list[dict[str, Any]], batch_id: str) -> str:
"""Generate HTML output from results.
Use when: creating a summary HTML table from parsed pipeline results.
"""
import html as html_lib
rows = ""
for r in results:
rows += f"""
<tr>
<td>{html_lib.escape(r.get('id', ''))}</td>
<td>{html_lib.escape(r.get('summary', '')[:100])}...</td>
<td>{r.get('score', 'N/A')}</td>
<td>{html_lib.escape(r.get('confidence', ''))}</td>
</tr>"""
return f"""<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Results - {batch_id}</title>
<style>
body {{ font-family: system-ui, sans-serif; max-width: 1000px; margin: 0 auto; padding: 20px; }}
table {{ width: 100%; border-collapse: collapse; }}
th, td {{ text-align: left; padding: 10px; border-bottom: 1px solid #ddd; }}
th {{ background: #f5f5f5; }}
</style>
</head>
<body>
<h1>Results: {batch_id}</h1>
<p>{len(results)} items processed</p>
<table>
<tr>
<th>ID</th>
<th>Summary</th>
<th>Score</th>
<th>Confidence</th>
</tr>
{rows}
</table>
</body>
</html>"""
# -----------------------------------------------------------------------------
# Clean Stage
# -----------------------------------------------------------------------------
def stage_clean(batch_id: str, from_stage: str | None = None) -> int:
"""Remove stage outputs to enable re-processing.
Use when: a stage produced bad results and needs to be re-run,
or when clearing all intermediate files for a fresh pipeline run.
Returns: Number of files deleted.
"""
batch_dir = get_batch_dir(batch_id)
if not batch_dir.exists():
print(f"No data directory for {batch_id}")
return 0
stage_outputs: dict[str, list[str]] = {
"acquire": ["raw.json"],
"prepare": ["prompt.md"],
"process": ["response.md"],
"parse": ["parsed.json"],
}
stage_order = ["acquire", "prepare", "process", "parse", "render"]
if from_stage:
start_idx = stage_order.index(from_stage)
stages_to_clean = stage_order[start_idx:]
else:
stages_to_clean = stage_order
files_to_delete: set[str] = set()
for s in stages_to_clean:
files_to_delete.update(stage_outputs.get(s, []))
deleted_count = 0
for item_dir in batch_dir.iterdir():
if not item_dir.is_dir():
continue
for filename in files_to_delete:
filepath = item_dir / filename
if filepath.exists():
filepath.unlink()
deleted_count += 1
# Clean aggregated results
if "parse" in stages_to_clean:
agg_file = batch_dir / "all_results.json"
if agg_file.exists():
agg_file.unlink()
deleted_count += 1
print(f"Cleaned {deleted_count} files from stage '{from_stage or 'all'}' onwards")
return deleted_count
# -----------------------------------------------------------------------------
# Cost Estimation
# -----------------------------------------------------------------------------
def stage_estimate(batch_id: str) -> dict[str, Any] | None:
"""Estimate processing costs before running the process stage.
Use when: projecting token costs and budget requir… [4138 more characters not shown]