261 lines
7.4 KiB
Python
261 lines
7.4 KiB
Python
|
|
"""
|
||
|
|
Template service for high-level template management operations.
|
||
|
|
|
||
|
|
This service extends artifact service to handle PromptTemplate-specific
|
||
|
|
operations including macro analysis and dependency extraction.
|
||
|
|
"""
|
||
|
|
|
||
|
|
from typing import List, Optional
|
||
|
|
|
||
|
|
from markitect.prompts.models import ArtifactMetadata, ArtifactType
|
||
|
|
from markitect.prompts.templates.models import (
|
||
|
|
PromptTemplate,
|
||
|
|
TemplateMetadata,
|
||
|
|
)
|
||
|
|
from markitect.prompts.templates.analyzer import TemplateAnalyzer, TemplateAnalysisResult
|
||
|
|
from markitect.prompts.services.artifact_service import ArtifactService
|
||
|
|
from markitect.prompts.repositories.interfaces import ArtifactNotFoundError
|
||
|
|
|
||
|
|
|
||
|
|
class TemplateService:
|
||
|
|
"""
|
||
|
|
Service for template management operations.
|
||
|
|
|
||
|
|
Provides high-level business logic for creating and analyzing templates,
|
||
|
|
building on top of ArtifactService.
|
||
|
|
"""
|
||
|
|
|
||
|
|
def __init__(self, artifact_service: ArtifactService):
|
||
|
|
"""
|
||
|
|
Initialize service with artifact service.
|
||
|
|
|
||
|
|
Args:
|
||
|
|
artifact_service: Artifact service for persistence
|
||
|
|
"""
|
||
|
|
self.artifact_service = artifact_service
|
||
|
|
self.analyzer = TemplateAnalyzer()
|
||
|
|
|
||
|
|
def create_template(
|
||
|
|
self,
|
||
|
|
space_id: str,
|
||
|
|
name: str,
|
||
|
|
content: str,
|
||
|
|
artifact_metadata: Optional[ArtifactMetadata] = None,
|
||
|
|
template_metadata: Optional[TemplateMetadata] = None,
|
||
|
|
analyze: bool = True,
|
||
|
|
) -> PromptTemplate:
|
||
|
|
"""
|
||
|
|
Create and optionally analyze a new template.
|
||
|
|
|
||
|
|
Args:
|
||
|
|
space_id: ID of containing space
|
||
|
|
name: Template name
|
||
|
|
content: Template content with macros
|
||
|
|
artifact_metadata: General artifact metadata
|
||
|
|
template_metadata: Template-specific metadata
|
||
|
|
analyze: Whether to analyze macros immediately
|
||
|
|
|
||
|
|
Returns:
|
||
|
|
Created template (analyzed if analyze=True)
|
||
|
|
|
||
|
|
Raises:
|
||
|
|
DuplicateArtifactError: If template already exists
|
||
|
|
MacroParsingError: If macro syntax is invalid (when analyze=True)
|
||
|
|
"""
|
||
|
|
# Create template
|
||
|
|
template = PromptTemplate.create(
|
||
|
|
space_id=space_id,
|
||
|
|
name=name,
|
||
|
|
content=content,
|
||
|
|
artifact_metadata=artifact_metadata,
|
||
|
|
template_metadata=template_metadata,
|
||
|
|
)
|
||
|
|
|
||
|
|
# Persist artifact
|
||
|
|
self.artifact_service.create_artifact(
|
||
|
|
space_id=space_id,
|
||
|
|
name=name,
|
||
|
|
content=content,
|
||
|
|
artifact_type=ArtifactType.TEMPLATE,
|
||
|
|
metadata=artifact_metadata,
|
||
|
|
)
|
||
|
|
|
||
|
|
# Analyze if requested
|
||
|
|
if analyze:
|
||
|
|
self.analyzer.analyze(template, content)
|
||
|
|
|
||
|
|
return template
|
||
|
|
|
||
|
|
def get_template(self, template_id: str, content: str) -> PromptTemplate:
|
||
|
|
"""
|
||
|
|
Retrieve template by ID.
|
||
|
|
|
||
|
|
Args:
|
||
|
|
template_id: Template identifier
|
||
|
|
content: Template content (needed to avoid storing content in DB twice)
|
||
|
|
|
||
|
|
Returns:
|
||
|
|
PromptTemplate instance
|
||
|
|
|
||
|
|
Raises:
|
||
|
|
ArtifactNotFoundError: If template doesn't exist
|
||
|
|
"""
|
||
|
|
artifact = self.artifact_service.get_artifact(template_id)
|
||
|
|
if artifact.artifact_type != ArtifactType.TEMPLATE:
|
||
|
|
raise ValueError(
|
||
|
|
f"Artifact '{template_id}' is not a template "
|
||
|
|
f"(type: {artifact.artifact_type})"
|
||
|
|
)
|
||
|
|
|
||
|
|
template = PromptTemplate.from_artifact(artifact)
|
||
|
|
# Analyze to populate macros
|
||
|
|
self.analyzer.analyze(template, content)
|
||
|
|
return template
|
||
|
|
|
||
|
|
def get_template_by_name(
|
||
|
|
self,
|
||
|
|
space_id: str,
|
||
|
|
name: str,
|
||
|
|
content: str,
|
||
|
|
) -> PromptTemplate:
|
||
|
|
"""
|
||
|
|
Retrieve template by space and name.
|
||
|
|
|
||
|
|
Args:
|
||
|
|
space_id: Space identifier
|
||
|
|
name: Template name
|
||
|
|
content: Template content
|
||
|
|
|
||
|
|
Returns:
|
||
|
|
PromptTemplate instance
|
||
|
|
|
||
|
|
Raises:
|
||
|
|
ArtifactNotFoundError: If template doesn't exist
|
||
|
|
"""
|
||
|
|
artifact = self.artifact_service.get_artifact_by_name(space_id, name)
|
||
|
|
if artifact.artifact_type != ArtifactType.TEMPLATE:
|
||
|
|
raise ValueError(
|
||
|
|
f"Artifact '{name}' is not a template "
|
||
|
|
f"(type: {artifact.artifact_type})"
|
||
|
|
)
|
||
|
|
|
||
|
|
template = PromptTemplate.from_artifact(artifact)
|
||
|
|
self.analyzer.analyze(template, content)
|
||
|
|
return template
|
||
|
|
|
||
|
|
def analyze_template(
|
||
|
|
self,
|
||
|
|
template: PromptTemplate,
|
||
|
|
content: str,
|
||
|
|
) -> TemplateAnalysisResult:
|
||
|
|
"""
|
||
|
|
Analyze template to extract macros and dependencies.
|
||
|
|
|
||
|
|
Args:
|
||
|
|
template: Template to analyze
|
||
|
|
content: Template content
|
||
|
|
|
||
|
|
Returns:
|
||
|
|
Analysis result with dependency information
|
||
|
|
|
||
|
|
Raises:
|
||
|
|
MacroParsingError: If macro syntax is invalid
|
||
|
|
"""
|
||
|
|
return self.analyzer.analyze(template, content)
|
||
|
|
|
||
|
|
def list_templates(self, space_id: str) -> List[PromptTemplate]:
|
||
|
|
"""
|
||
|
|
List all templates in a space.
|
||
|
|
|
||
|
|
Note: Templates are returned without macro analysis.
|
||
|
|
Call analyze_template() on individual templates as needed.
|
||
|
|
|
||
|
|
Args:
|
||
|
|
space_id: Space identifier
|
||
|
|
|
||
|
|
Returns:
|
||
|
|
List of templates (unanalyzed)
|
||
|
|
"""
|
||
|
|
artifacts = self.artifact_service.list_artifacts(
|
||
|
|
space_id=space_id,
|
||
|
|
artifact_type=ArtifactType.TEMPLATE,
|
||
|
|
)
|
||
|
|
|
||
|
|
templates = [PromptTemplate.from_artifact(a) for a in artifacts]
|
||
|
|
return templates
|
||
|
|
|
||
|
|
def quick_check_content(self, content: str) -> dict:
|
||
|
|
"""
|
||
|
|
Quick validation of template content.
|
||
|
|
|
||
|
|
Useful for checking content before template creation.
|
||
|
|
|
||
|
|
Args:
|
||
|
|
content: Template content to check
|
||
|
|
|
||
|
|
Returns:
|
||
|
|
Dictionary with macro counts and validation info
|
||
|
|
"""
|
||
|
|
return self.analyzer.quick_check(content)
|
||
|
|
|
||
|
|
def update_template_content(
|
||
|
|
self,
|
||
|
|
template_id: str,
|
||
|
|
new_content: str,
|
||
|
|
reanalyze: bool = True,
|
||
|
|
) -> PromptTemplate:
|
||
|
|
"""
|
||
|
|
Update template content.
|
||
|
|
|
||
|
|
Args:
|
||
|
|
template_id: Template to update
|
||
|
|
new_content: New content
|
||
|
|
reanalyze: Whether to reanalyze macros
|
||
|
|
|
||
|
|
Returns:
|
||
|
|
Updated template
|
||
|
|
|
||
|
|
Raises:
|
||
|
|
ArtifactNotFoundError: If template doesn't exist
|
||
|
|
MacroParsingError: If new content has invalid macros
|
||
|
|
"""
|
||
|
|
# Update artifact content
|
||
|
|
artifact = self.artifact_service.update_artifact_content(
|
||
|
|
template_id,
|
||
|
|
new_content,
|
||
|
|
)
|
||
|
|
|
||
|
|
# Create template from updated artifact
|
||
|
|
template = PromptTemplate.from_artifact(artifact)
|
||
|
|
|
||
|
|
# Reanalyze if requested
|
||
|
|
if reanalyze:
|
||
|
|
self.analyzer.analyze(template, new_content)
|
||
|
|
|
||
|
|
return template
|
||
|
|
|
||
|
|
def delete_template(self, template_id: str) -> bool:
|
||
|
|
"""
|
||
|
|
Delete a template.
|
||
|
|
|
||
|
|
Args:
|
||
|
|
template_id: Template to delete
|
||
|
|
|
||
|
|
Returns:
|
||
|
|
True if deleted, False if not found
|
||
|
|
"""
|
||
|
|
return self.artifact_service.delete_artifact(template_id)
|
||
|
|
|
||
|
|
def template_exists(self, space_id: str, name: str) -> bool:
|
||
|
|
"""
|
||
|
|
Check if template exists.
|
||
|
|
|
||
|
|
Args:
|
||
|
|
space_id: Space identifier
|
||
|
|
name: Template name
|
||
|
|
|
||
|
|
Returns:
|
||
|
|
True if template exists
|
||
|
|
"""
|
||
|
|
return self.artifact_service.artifact_exists(space_id, name)
|