214 lines
6.8 KiB
Python
214 lines
6.8 KiB
Python
"""
|
|
Base class for EdgarTools AI skills.
|
|
|
|
Provides the foundation for creating AI skills that integrate with
|
|
edgar.ai infrastructure. External packages can subclass BaseSkill to
|
|
create specialized skills (e.g., insider trading detection, fraud analysis).
|
|
"""
|
|
|
|
from abc import ABC, abstractmethod
|
|
from pathlib import Path
|
|
from typing import Dict, List, Optional, Callable
|
|
|
|
__all__ = ['BaseSkill']
|
|
|
|
|
|
class BaseSkill(ABC):
|
|
"""
|
|
Abstract base class for EdgarTools AI skills.
|
|
|
|
A skill packages:
|
|
- Documentation (markdown files with YAML frontmatter)
|
|
- Helper functions (workflow wrappers)
|
|
- Examples and patterns
|
|
|
|
External packages can subclass this to create specialized skills
|
|
that integrate seamlessly with edgar.ai infrastructure.
|
|
|
|
Example:
|
|
>>> from edgar.ai.skills.base import BaseSkill
|
|
>>> from pathlib import Path
|
|
>>>
|
|
>>> class InsiderTradingSkill(BaseSkill):
|
|
... @property
|
|
... def name(self) -> str:
|
|
... return "Insider Trading Detection"
|
|
...
|
|
... @property
|
|
... def description(self) -> str:
|
|
... return "Analyze Form 4 filings for insider trading patterns"
|
|
...
|
|
... @property
|
|
... def content_dir(self) -> Path:
|
|
... return Path(__file__).parent / "content"
|
|
...
|
|
... def get_helpers(self) -> Dict[str, Callable]:
|
|
... return {
|
|
... 'detect_unusual_trades': self.detect_unusual_trades,
|
|
... }
|
|
"""
|
|
|
|
@property
|
|
@abstractmethod
|
|
def name(self) -> str:
|
|
"""
|
|
Skill name for display and identification.
|
|
|
|
Should be descriptive and unique. Example: "SEC Filing Analysis"
|
|
|
|
Returns:
|
|
Human-readable skill name
|
|
"""
|
|
pass
|
|
|
|
@property
|
|
@abstractmethod
|
|
def description(self) -> str:
|
|
"""
|
|
Brief description of skill capabilities.
|
|
|
|
Used by AI agents to determine when to activate the skill.
|
|
Should clearly describe what problems the skill solves.
|
|
|
|
Returns:
|
|
One-sentence skill description
|
|
"""
|
|
pass
|
|
|
|
@property
|
|
@abstractmethod
|
|
def content_dir(self) -> Path:
|
|
"""
|
|
Directory containing skill documentation (markdown files).
|
|
|
|
This directory should contain:
|
|
- skill.md: Main skill documentation with YAML frontmatter
|
|
- objects.md: Object reference (optional)
|
|
- workflows.md: Workflow patterns (optional)
|
|
- readme.md: Installation/overview (optional)
|
|
|
|
Returns:
|
|
Path to skill content directory
|
|
"""
|
|
pass
|
|
|
|
@abstractmethod
|
|
def get_helpers(self) -> Dict[str, Callable]:
|
|
"""
|
|
Return dictionary of helper functions this skill provides.
|
|
|
|
Helper functions are convenience wrappers that simplify
|
|
common workflows for the skill's domain.
|
|
|
|
Returns:
|
|
Dict mapping function names to callable objects
|
|
|
|
Example:
|
|
>>> {
|
|
... 'get_revenue_trend': helpers.get_revenue_trend,
|
|
... 'compare_companies': helpers.compare_companies,
|
|
... }
|
|
"""
|
|
pass
|
|
|
|
# Non-abstract methods with default implementations
|
|
|
|
def get_object_docs(self) -> List[Path]:
|
|
"""
|
|
Return paths to centralized object documentation files to include in exports.
|
|
|
|
Override this method to specify which centralized API reference docs
|
|
should be included when exporting the skill. These docs are copied to
|
|
an 'api-reference/' subdirectory in the exported skill package.
|
|
|
|
Returns:
|
|
List of Path objects pointing to markdown documentation files
|
|
|
|
Example:
|
|
>>> def get_object_docs(self) -> List[Path]:
|
|
... from pathlib import Path
|
|
... root = Path(__file__).parent.parent.parent
|
|
... return [
|
|
... root / "entity/docs/Company.md",
|
|
... root / "xbrl/docs/XBRL.md",
|
|
... ]
|
|
"""
|
|
return [] # Default: no object docs
|
|
|
|
def get_documents(self) -> List[str]:
|
|
"""
|
|
List of markdown documents in this skill.
|
|
|
|
Returns:
|
|
List of document names (without .md extension)
|
|
"""
|
|
if not self.content_dir.exists():
|
|
return []
|
|
return [f.stem for f in self.content_dir.glob("*.md")]
|
|
|
|
def get_document_content(self, name: str) -> str:
|
|
"""
|
|
Get content of a specific markdown document.
|
|
|
|
Args:
|
|
name: Document name (with or without .md extension)
|
|
|
|
Returns:
|
|
Full markdown content as string
|
|
|
|
Raises:
|
|
FileNotFoundError: If document doesn't exist
|
|
"""
|
|
doc_name = name if name.endswith('.md') else f"{name}.md"
|
|
doc_path = self.content_dir / doc_name
|
|
|
|
if not doc_path.exists():
|
|
available = ", ".join(self.get_documents())
|
|
raise FileNotFoundError(
|
|
f"Document '{name}' not found in skill '{self.name}'. "
|
|
f"Available: {available}"
|
|
)
|
|
|
|
return doc_path.read_text()
|
|
|
|
def export(self, format: str = "claude-desktop", output_dir: Optional[Path] = None, **kwargs) -> Path:
|
|
"""
|
|
Export skill in specified format.
|
|
|
|
Args:
|
|
format: Export format (default: "claude-desktop")
|
|
- "claude-desktop": Claude Desktop Skills format (ZIP)
|
|
- "claude-skills": Official Claude Skills format (~/.claude/skills/)
|
|
output_dir: Where to create export (default: ./skills_export/)
|
|
**kwargs: Additional format-specific parameters
|
|
- create_zip (bool): For claude-desktop format (default: True)
|
|
- install (bool): For claude-skills format (default: True)
|
|
|
|
Returns:
|
|
Path to exported skill directory or archive
|
|
|
|
Example:
|
|
>>> skill = EdgarToolsSkill()
|
|
>>> # Export as ZIP for Claude Desktop upload
|
|
>>> path = skill.export(format="claude-desktop")
|
|
>>> # Export to ~/.claude/skills/ for automatic discovery
|
|
>>> path = skill.export(format="claude-skills")
|
|
"""
|
|
from edgar.ai.exporters import export_skill
|
|
return export_skill(self, format=format, output_dir=output_dir, **kwargs)
|
|
|
|
def __repr__(self) -> str:
|
|
"""String representation of the skill."""
|
|
return f"{self.__class__.__name__}(name='{self.name}')"
|
|
|
|
def __str__(self) -> str:
|
|
"""Human-readable skill description."""
|
|
docs_count = len(self.get_documents())
|
|
helpers_count = len(self.get_helpers())
|
|
return (
|
|
f"Skill: {self.name}\n"
|
|
f"Description: {self.description}\n"
|
|
f"Documents: {docs_count}\n"
|
|
f"Helper Functions: {helpers_count}"
|
|
)
|