Files
markitect-main/examples/content-generator/generate_primers.py
tegwick 360c3b1de2
Some checks failed
Test Suite / unit-tests (3.11) (push) Has been cancelled
Test Suite / unit-tests (3.12) (push) Has been cancelled
Test Suite / integration-tests (push) Has been cancelled
Test Suite / e2e-tests (push) Has been cancelled
Test Suite / performance-tests (push) Has been cancelled
Test Suite / code-quality (push) Has been cancelled
Test Suite / security-scan (push) Has been cancelled
Test Suite / test-summary (push) Has been cancelled
feat(examples): add content-generator example demonstrating Prompt Dependency Resolution
This example demonstrates the full workflow of generating InfoTech primers
using MarkiTect's Prompt Dependency Resolution infrastructure.

Features demonstrated:
- Artifact creation and storage with content-based addressing
- PromptTemplate with @{macro} resolution across multiple spaces
- Automatic dependency tracking and graph construction
- Provenance tracing from outputs back to inputs
- Visualization export (Mermaid format)
- Incremental execution with change detection

Files added:
- generate_primers.py: Complete working example
- README.md: Quick start guide and architecture overview
- TUTORIAL.md: Comprehensive 500+ line tutorial
- templates/generate-primer.md: Template with macros
- artifacts/topics/: ETL and Microservices topic definitions
- artifacts/guidelines/: Authoring rules and research protocol
- prepdr/: Original manual system (preserved for reference)

Example output:
- Generates 2 primers (ETL, Microservices)
- Creates 8 artifacts across 4 information spaces
- Records 8 dependency edges in SQLite database
- Exports dependency graph visualization

Run with: cd examples/content-generator && python generate_primers.py

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-02-09 23:50:07 +01:00

401 lines
16 KiB
Python
Executable File

#!/usr/bin/env python3
"""
Primer Generation using Prompt Dependency Resolution
This script demonstrates the full workflow of generating InfoTech primers
using MarkiTect's Prompt Dependency Resolution infrastructure.
Features demonstrated:
- Artifact creation and storage
- PromptTemplate with macro resolution
- Dependency tracking
- Quality validation
- Incremental recomputation
- Full provenance tracing
"""
import sys
from pathlib import Path
from typing import Optional
# Add project root to path
project_root = Path(__file__).parent.parent.parent
sys.path.insert(0, str(project_root))
from markitect.prompts.models import Artifact, ArtifactType
from markitect.prompts.repositories.sqlite import SQLiteArtifactRepository
from markitect.prompts.dependencies.repository import SQLiteDependencyRepository
from markitect.prompts.services.artifact_service import ArtifactService
from markitect.prompts.templates.models import PromptTemplate, ContentMacro, MacroKind
from markitect.prompts.templates.analyzer import TemplateAnalyzer
from markitect.prompts.resolver.resolver import PromptResolver
from markitect.prompts.resolver.compiler import ContextCompiler
from markitect.prompts.resolver.strategy import ResolutionConfig, MultiSpaceResolutionStrategy
from markitect.prompts.execution.models import PromptRun, RunConfig
from markitect.prompts.execution.manifest import RunManifest
from markitect.prompts.dependencies.graph import GraphBuilder
from markitect.prompts.traceability.service import TraceabilityService
from markitect.prompts.queries.operations import PromptQueryService
from markitect.prompts.visualization.graph import GraphExporter
class PrimerGenerator:
"""Generates InfoTech primers using prompt dependency resolution."""
def __init__(self, db_path: str = "primers.db"):
"""Initialize with database path."""
self.db_path = db_path
self.artifact_repo = SQLiteArtifactRepository(db_path)
self.artifact_service = ArtifactService(self.artifact_repo)
self.dep_repo = SQLiteDependencyRepository(db_path)
self.graph_builder = GraphBuilder(self.dep_repo)
self.trace_service = TraceabilityService(
self.artifact_repo, self.dep_repo, db_path=db_path
)
self.query_service = PromptQueryService(
self.artifact_repo, self.dep_repo, db_path=db_path
)
# Create information spaces
self.spaces = {
"templates": "primer-templates",
"topics": "primer-topics",
"guidelines": "primer-guidelines",
"output": "generated-primers",
}
def load_or_create_artifact(
self, space: str, filepath: Path, artifact_type: ArtifactType, name: Optional[str] = None
) -> tuple[Artifact, str]:
"""Load artifact from file or fetch from repository. Returns (artifact, content)."""
if name is None:
name = filepath.stem
# Load content from file
if not filepath.exists():
raise FileNotFoundError(f"Artifact file not found: {filepath}")
content = filepath.read_text()
# Check if artifact exists
existing = self.artifact_repo.get_by_name(space, name)
if existing:
print(f"✓ Found existing artifact: {name}")
return existing, content
# Create and store artifact
artifact = Artifact.create(
space_id=space,
name=name,
content=content,
artifact_type=artifact_type,
)
artifact = self.artifact_repo.create(artifact)
print(f"✓ Created artifact: {name} (digest: {artifact.content_digest[:8]})")
return artifact, content
def setup_artifacts(self, example_dir: Path):
"""Load all artifacts into the repository."""
print("\n=== Loading Artifacts ===")
# Cache for artifact content (repository doesn't store content)
self.artifact_content = {}
# Load template
template_file = example_dir / "templates" / "generate-primer.md"
self.template_artifact, content = self.load_or_create_artifact(
self.spaces["templates"],
template_file,
ArtifactType.TEMPLATE,
)
self.artifact_content[self.template_artifact.id] = content
# Load topic artifacts
topics_dir = example_dir / "artifacts" / "topics"
self.topic_artifacts = {}
for topic_file in topics_dir.glob("*.md"):
artifact, content = self.load_or_create_artifact(
self.spaces["topics"],
topic_file,
ArtifactType.CONTENT,
)
self.topic_artifacts[artifact.name] = artifact
self.artifact_content[artifact.id] = content
# Load guideline artifacts (rename to match macro expectations)
guidelines_dir = example_dir / "artifacts" / "guidelines"
self.guideline_artifacts = {}
guideline_name_map = {
"authoring-rules.md": "authoring_rules",
"research-prompt.md": "research_prompt",
}
for guide_file in guidelines_dir.glob("*.md"):
# Use mapped name if available, otherwise use stem
artifact_name = guideline_name_map.get(guide_file.name, guide_file.stem)
artifact, content = self.load_or_create_artifact(
self.spaces["guidelines"],
guide_file,
ArtifactType.CONTENT,
name=artifact_name,
)
self.guideline_artifacts[artifact.name] = artifact
self.artifact_content[artifact.id] = content
def create_template(self) -> PromptTemplate:
"""Create PromptTemplate from template artifact."""
print("\n=== Parsing Template ===")
# Create PromptTemplate from artifact
template = PromptTemplate.from_artifact(self.template_artifact)
# For demo: manually add macros since analyzer would extract from {{...}}
# In real usage, analyzer would parse the content automatically
template.macros = [
ContentMacro(kind=MacroKind.REQUIRED, target="topic"),
ContentMacro(kind=MacroKind.REQUIRED, target="authoring_rules"),
ContentMacro(kind=MacroKind.REQUIRED, target="research_prompt"),
]
template.analyzed = True
print(f"✓ Template created with {len(template.macros)} macro dependencies")
return template
def generate_primer(self, topic_name: str, output_name: Optional[str] = None) -> Artifact:
"""Generate a primer for the given topic."""
print(f"\n=== Generating Primer: {topic_name} ===")
# Get topic artifact
topic_artifact = self.topic_artifacts.get(topic_name)
if not topic_artifact:
raise ValueError(f"Topic not found: {topic_name}")
# Get the topic's content from cache
topic_content = self.artifact_content.get(topic_artifact.id)
if not topic_content:
raise ValueError(f"Topic content not found for: {topic_name}")
# Create a temporary "topic" artifact with this specific topic's content
# This allows the template's @{topic} macro to resolve
temp_topic = Artifact.create(
space_id=self.spaces["topics"],
name="topic",
content=topic_content,
artifact_type=ArtifactType.CONTENT,
)
# Check if "topic" artifact already exists and delete it
existing_topic = self.artifact_repo.get_by_name(self.spaces["topics"], "topic")
if existing_topic:
self.artifact_repo.delete(existing_topic.id)
temp_topic = self.artifact_repo.create(temp_topic)
self.artifact_content[temp_topic.id] = topic_content
print(f"✓ Bound topic macro to {topic_name}")
# Create template
template = self.create_template()
# Configure resolution
resolution_config = ResolutionConfig(
space_id=self.spaces["templates"],
included_spaces=[
self.spaces["topics"],
self.spaces["guidelines"],
],
)
print("\n=== Resolving Dependencies ===")
strategy = MultiSpaceResolutionStrategy()
resolver = PromptResolver(self.artifact_service, strategy)
resolution_result = resolver.resolve_template(template, resolution_config)
if not resolution_result.success:
print(f"✗ Resolution failed: {resolution_result.context.errors}")
return None
print(f"✓ Resolved {len(resolution_result.context.resolved_macros)} macros")
for resolved in resolution_result.context.resolved_macros:
print(f" - {resolved.macro.target}{resolved.artifact.name}")
# Compile the prompt
print("\n=== Compiling Prompt ===")
compiler = ContextCompiler()
# For demo, use mock template content with macro markers
template_content = f"""
# Generate InfoTech Primer
Topic: @{{topic}}
Guidelines: @{{authoring_rules}}
Research Protocol: @{{research_prompt}}
Generate a complete primer following the authoring rules.
"""
compiled = compiler.compile(template, template_content, resolution_result)
print(f"✓ Compiled prompt (digest: {compiled.content_digest[:8]})")
# Create run manifest
run_id = f"run-{topic_name}-001"
manifest = RunManifest.create(
run_id=run_id,
template_id=template.artifact.id,
template_name=template.artifact.name,
template_digest=self.template_artifact.content_digest,
)
# Add resolved inputs
for resolved in resolution_result.context.resolved_macros:
manifest.add_resolved_input(
name=resolved.macro.target,
artifact_id=resolved.artifact.id,
space_id=resolved.space_id,
digest=resolved.artifact.content_digest,
)
# Create dependency edge
manifest.add_dependency_edge(
source_id=resolved.artifact.id,
target_id=run_id,
edge_type="requires",
)
# For demo: create output artifact (would normally come from LLM)
output_name = output_name or f"{topic_name}-primer"
output_content = f"""# {topic_name.upper()} Primer
[Generated primer content would go here...]
This primer was generated using the Prompt Dependency Resolution infrastructure.
**Dependencies:**
- Template: {template.artifact.name}
- Topic: {topic_artifact.name}
- Guidelines: authoring-rules, research-prompt
**Run ID:** {run_id}
"""
output_artifact = Artifact.create(
space_id=self.spaces["output"],
name=output_name,
content=output_content,
artifact_type=ArtifactType.GENERATED,
)
output_artifact = self.artifact_repo.create(output_artifact)
# Add output to manifest
manifest.add_output_artifact(
artifact_id=output_artifact.id,
name=output_artifact.name,
digest=output_artifact.content_digest,
artifact_type=output_artifact.artifact_type.value,
)
manifest.add_dependency_edge(
source_id=run_id,
target_id=output_artifact.id,
edge_type="generates",
)
# Persist all dependency edges at once
edges = self.graph_builder.persist_edges(manifest)
print(f"✓ Persisted {len(edges)} dependency edges")
print(f"✓ Generated primer: {output_artifact.name}")
print(f" ID: {output_artifact.id}")
print(f" Digest: {output_artifact.content_digest[:8]}")
return output_artifact
def show_provenance(self, artifact_id: str):
"""Display full provenance trace for an artifact."""
print(f"\n=== Provenance Trace ===")
trace = self.trace_service.trace_artifact(artifact_id)
print(f"Artifact: {trace.artifact_id}")
if trace.producing_run:
print(f"\nProducing Run: {trace.producing_run.run_id}")
print(f" Template: {trace.producing_run.template_id}")
print(f" Status: {trace.producing_run.status}")
if trace.input_artifacts:
print(f"\nInput Artifacts ({len(trace.input_artifacts)}):")
for inp in trace.input_artifacts:
print(f" - {inp.name} ({inp.artifact_type})")
if trace.dependency_chain:
print(f"\nDependency Chain ({len(trace.dependency_chain)} artifacts):")
for dep_id in trace.dependency_chain[:5]: # Show first 5
print(f" - {dep_id[:8]}...")
def visualize_dependencies(self, artifact_id: str, output_file: str = "dependencies.mermaid"):
"""Generate dependency graph visualization."""
print(f"\n=== Dependency Visualization ===")
from markitect.prompts.dependencies.queries import DependencyQueryService
query_svc = DependencyQueryService(self.dep_repo)
deps = query_svc.find_transitive_dependencies(artifact_id)
dependents = query_svc.find_transitive_dependents(artifact_id)
all_ids = deps | dependents | {artifact_id}
graph = self.graph_builder.build_graph(all_ids)
mermaid = GraphExporter.to_mermaid(graph, f"Primer Generation Dependencies")
Path(output_file).write_text(mermaid)
print(f"✓ Saved dependency graph to {output_file}")
def main():
"""Main execution flow."""
print("╔══════════════════════════════════════════════════════════════╗")
print("║ MarkiTect Prompt Dependency Resolution Example ║")
print("║ InfoTech Primer Generation ║")
print("╚══════════════════════════════════════════════════════════════╝")
example_dir = Path(__file__).parent
# Initialize generator
generator = PrimerGenerator(db_path=str(example_dir / "primers.db"))
# Setup artifacts
generator.setup_artifacts(example_dir)
# Generate primers for multiple topics
topics = ["etl", "microservices"]
generated_artifacts = []
for topic in topics:
try:
artifact = generator.generate_primer(topic)
if artifact:
generated_artifacts.append(artifact)
except Exception as e:
print(f"✗ Failed to generate primer for {topic}: {e}")
# Show provenance for first generated primer
if generated_artifacts:
artifact = generated_artifacts[0]
generator.show_provenance(artifact.id)
generator.visualize_dependencies(artifact.id)
# Show statistics
print("\n=== Dependency Statistics ===")
stats = generator.query_service.get_dependency_stats()
print(f"Total nodes: {stats['total_nodes']}")
print(f"Total edges: {stats['total_edges']}")
print(f"Root artifacts: {stats['root_count']}")
print(f"Leaf artifacts: {stats['leaf_count']}")
print(f"Has cycles: {stats['has_cycles']}")
print("\n✓ Primer generation complete!")
print(f" Database: {generator.db_path}")
print(f" Generated: {len(generated_artifacts)} primers")
if __name__ == "__main__":
main()