MSGEncrypted commited on
Commit
4b65659
·
1 Parent(s): d10324f

skills prompt and tools

Browse files
libs/agent/src/agent/prompts.py ADDED
@@ -0,0 +1,77 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import json
4
+
5
+ from agent.models import EducationPptxInput
6
+
7
+
8
+ def education_outline_system(skill_body: str) -> str:
9
+ return f"""You are a lesson-planning assistant for teachers.
10
+ Follow the skill workflow below and output ONLY valid JSON (no markdown fences).
11
+
12
+ Skill workflow:
13
+ {skill_body}
14
+
15
+ JSON schema:
16
+ {{
17
+ "title": "string — presentation title",
18
+ "slides": [
19
+ {{
20
+ "title": "string — slide heading",
21
+ "bullets": ["string", "..."],
22
+ "speaker_note": "string — one sentence for the teacher"
23
+ }}
24
+ ]
25
+ }}
26
+
27
+ Rules:
28
+ - Use exactly the requested number of content slides (title slide is added separately by the tool).
29
+ - Bullets should be short, age-appropriate, and factual.
30
+ - speaker_note is optional but helpful for each slide.
31
+ """
32
+
33
+
34
+ def education_outline_user(req: EducationPptxInput) -> str:
35
+ return (
36
+ f"Topic: {req.topic}\n"
37
+ f"Grade level: {req.grade}\n"
38
+ f"Number of content slides: {req.slide_count}\n"
39
+ "Return JSON only."
40
+ )
41
+
42
+
43
+ def education_outline_repair(invalid_output: str, error: str) -> str:
44
+ return (
45
+ "The previous response was invalid JSON or did not match the schema.\n"
46
+ f"Validation error: {error}\n"
47
+ f"Previous output:\n{invalid_output}\n\n"
48
+ "Return corrected JSON only, no explanation."
49
+ )
50
+
51
+
52
+ def outline_to_markdown(title: str, slides: list[dict]) -> str:
53
+ lines = [f"# {title}", ""]
54
+ for index, slide in enumerate(slides, start=1):
55
+ lines.append(f"## Slide {index}: {slide.get('title', 'Untitled')}")
56
+ for bullet in slide.get("bullets", []):
57
+ lines.append(f"- {bullet}")
58
+ note = slide.get("speaker_note", "")
59
+ if note:
60
+ lines.append(f"\n*Teacher note:* {note}")
61
+ lines.append("")
62
+ return "\n".join(lines).strip()
63
+
64
+
65
+ def outline_json_example(slide_count: int) -> str:
66
+ example = {
67
+ "title": "Example Lesson",
68
+ "slides": [
69
+ {
70
+ "title": f"Slide {i}",
71
+ "bullets": ["Key point A", "Key point B"],
72
+ "speaker_note": "Brief teaching tip.",
73
+ }
74
+ for i in range(1, slide_count + 1)
75
+ ],
76
+ }
77
+ return json.dumps(example, indent=2)
libs/agent/src/agent/skills.py ADDED
@@ -0,0 +1,85 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import re
4
+ from dataclasses import dataclass
5
+ from pathlib import Path
6
+ from typing import Any
7
+
8
+ import yaml
9
+
10
+
11
+ @dataclass(frozen=True)
12
+ class Skill:
13
+ name: str
14
+ description: str
15
+ task: str
16
+ tools: list[str]
17
+ model_hints: list[str]
18
+ body: str
19
+ path: Path
20
+
21
+
22
+ def _find_skills_root() -> Path | None:
23
+ import os
24
+
25
+ env_path = os.environ.get("SKILLS_DIR")
26
+ if env_path:
27
+ root = Path(env_path)
28
+ if root.is_dir():
29
+ return root.resolve()
30
+
31
+ for base in (Path.cwd(), *Path.cwd().parents):
32
+ candidate = base / "skills"
33
+ if candidate.is_dir():
34
+ return candidate.resolve()
35
+ return None
36
+
37
+
38
+ def _parse_skill_md(path: Path) -> Skill:
39
+ text = path.read_text()
40
+ match = re.match(r"^---\s*\n(.*?)\n---\s*\n(.*)$", text, re.DOTALL)
41
+ if not match:
42
+ raise ValueError(f"{path}: expected YAML frontmatter delimited by ---")
43
+
44
+ meta: dict[str, Any] = yaml.safe_load(match.group(1)) or {}
45
+ body = match.group(2).strip()
46
+
47
+ return Skill(
48
+ name=str(meta.get("name", path.parent.name)),
49
+ description=str(meta.get("description", "")),
50
+ task=str(meta.get("task", "")),
51
+ tools=[str(t) for t in meta.get("tools", [])],
52
+ model_hints=[str(m) for m in meta.get("model_hints", [])],
53
+ body=body,
54
+ path=path,
55
+ )
56
+
57
+
58
+ class SkillRegistry:
59
+ def __init__(self, skills_root: Path | None = None) -> None:
60
+ self._root = skills_root or _find_skills_root()
61
+ self._skills: dict[str, Skill] = {}
62
+ if self._root is not None:
63
+ self._load_all()
64
+
65
+ @property
66
+ def root(self) -> Path | None:
67
+ return self._root
68
+
69
+ def _load_all(self) -> None:
70
+ assert self._root is not None
71
+ for skill_md in sorted(self._root.glob("*/SKILL.md")):
72
+ skill = _parse_skill_md(skill_md)
73
+ self._skills[skill.name] = skill
74
+
75
+ def get(self, name: str) -> Skill:
76
+ if name not in self._skills:
77
+ known = ", ".join(sorted(self._skills)) or "(none found)"
78
+ raise KeyError(f"Unknown skill {name!r}. Known: {known}")
79
+ return self._skills[name]
80
+
81
+ def list_by_task(self, task: str) -> list[Skill]:
82
+ return [s for s in self._skills.values() if s.task == task]
83
+
84
+ def all(self) -> list[Skill]:
85
+ return list(self._skills.values())
libs/agent/src/agent/tools/__init__.py ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ from agent.tools.pptx import create_pptx
2
+
3
+ __all__ = ["create_pptx"]
libs/agent/src/agent/tools/pptx.py ADDED
@@ -0,0 +1,67 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ import re
4
+ import uuid
5
+ from pathlib import Path
6
+
7
+ from pptx import Presentation
8
+ from pptx.util import Inches, Pt
9
+
10
+ from agent.models import SlideOutline
11
+
12
+
13
+ def _outputs_dir() -> Path:
14
+ import os
15
+ import tempfile
16
+
17
+ env = os.environ.get("AGENT_OUTPUTS_DIR")
18
+ if env:
19
+ path = Path(env)
20
+ else:
21
+ path = Path(tempfile.gettempdir()) / "agent_outputs"
22
+ path.mkdir(parents=True, exist_ok=True)
23
+ return path
24
+
25
+
26
+ def _safe_filename(title: str) -> str:
27
+ slug = re.sub(r"[^a-zA-Z0-9]+", "_", title.strip().lower()).strip("_")
28
+ return slug[:60] or "lesson"
29
+
30
+
31
+ def create_pptx(outline: SlideOutline, run_id: str | None = None) -> Path:
32
+ """Build a .pptx from a validated slide outline."""
33
+ rid = run_id or uuid.uuid4().hex[:12]
34
+ out_dir = _outputs_dir()
35
+ filename = f"{_safe_filename(outline.title)}_{rid}.pptx"
36
+ path = out_dir / filename
37
+
38
+ prs = Presentation()
39
+ prs.slide_width = Inches(10)
40
+ prs.slide_height = Inches(7.5)
41
+
42
+ title_layout = prs.slide_layouts[0]
43
+ title_slide = prs.slides.add_slide(title_layout)
44
+ title_slide.shapes.title.text = outline.title
45
+ if title_slide.placeholders[1].text_frame:
46
+ title_slide.placeholders[1].text = "Generated lesson slides"
47
+
48
+ bullet_layout = prs.slide_layouts[1]
49
+ for slide_spec in outline.slides:
50
+ slide = prs.slides.add_slide(bullet_layout)
51
+ slide.shapes.title.text = slide_spec.title
52
+ body = slide.placeholders[1].text_frame
53
+ body.clear()
54
+ for index, bullet in enumerate(slide_spec.bullets):
55
+ if index == 0:
56
+ p = body.paragraphs[0]
57
+ else:
58
+ p = body.add_paragraph()
59
+ p.text = bullet
60
+ p.level = 0
61
+ p.font.size = Pt(20)
62
+ if slide_spec.speaker_note:
63
+ notes = slide.notes_slide.notes_text_frame
64
+ notes.text = slide_spec.speaker_note
65
+
66
+ prs.save(str(path))
67
+ return path
libs/agent/src/agent/tools_registry.py ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations
2
+
3
+ from collections.abc import Callable
4
+ from dataclasses import dataclass
5
+ from typing import Any
6
+
7
+ from agent.models import SlideOutline
8
+ from agent.tools.pptx import create_pptx
9
+
10
+
11
+ @dataclass(frozen=True)
12
+ class ToolSpec:
13
+ name: str
14
+ description: str
15
+ handler: Callable[..., Any]
16
+
17
+
18
+ class ToolRegistry:
19
+ def __init__(self) -> None:
20
+ self._tools: dict[str, ToolSpec] = {}
21
+ self.register(
22
+ "create_pptx",
23
+ "Create a PowerPoint file from a validated SlideOutline",
24
+ self._handle_create_pptx,
25
+ )
26
+
27
+ def register(self, name: str, description: str, handler: Callable[..., Any]) -> None:
28
+ self._tools[name] = ToolSpec(name=name, description=description, handler=handler)
29
+
30
+ def get(self, name: str) -> ToolSpec:
31
+ if name not in self._tools:
32
+ raise KeyError(f"Unknown tool {name!r}")
33
+ return self._tools[name]
34
+
35
+ def _handle_create_pptx(self, outline: SlideOutline, run_id: str | None = None) -> str:
36
+ path = create_pptx(outline, run_id=run_id)
37
+ return str(path)