-
Notifications
You must be signed in to change notification settings - Fork 86
LCORE-2072: Add SkillsConfiguration model to config file #1736
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,31 @@ | ||
| name: Lightspeed Core Service (LCS) with Skills | ||
| service: | ||
| host: localhost | ||
| port: 8080 | ||
| auth_enabled: false | ||
| workers: 1 | ||
| color_log: true | ||
| access_log: true | ||
| llama_stack: | ||
| use_as_library_client: true | ||
| library_client_config_path: run.yaml | ||
| user_data_collection: | ||
| feedback_enabled: true | ||
| feedback_storage: "/tmp/data/feedback" | ||
| transcripts_enabled: true | ||
| transcripts_storage: "/tmp/data/transcripts" | ||
| authentication: | ||
| module: "noop" | ||
| # Agent skills configuration | ||
| # Skills provide domain-specific instructions and reference materials | ||
| # that the LLM can load on demand when relevant to the current task | ||
| skills: | ||
| paths: | ||
| # Option A: Directory containing multiple skill subdirectories | ||
| # Each subdirectory must contain a SKILL.md file | ||
| - "/var/skills/" | ||
|
|
||
| # Option B: Individual skill paths for fine-grained control | ||
| # - "/var/skills/openshift-troubleshooting/" | ||
| # - "/var/skills/code-review/" | ||
| # - "/opt/custom-skills/deployment-guide/" | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -1916,6 +1916,26 @@ class AzureEntraIdConfiguration(ConfigurationBase): | |
| ) | ||
|
|
||
|
|
||
| class SkillsConfiguration(ConfigurationBase): | ||
| """Agent skills configuration. | ||
|
|
||
| Specifies paths to skill directories. Skill metadata (name, description) | ||
| is read from SKILL.md frontmatter at startup. | ||
|
|
||
| Each path can point to either: | ||
| - A directory containing a SKILL.md file (single skill) | ||
| - A directory containing subdirectories with SKILL.md files (multiple skills) | ||
|
|
||
| Paths are validated at startup to ensure they exist and contain valid SKILL.md files. | ||
| """ | ||
|
|
||
| paths: list[str] = Field( | ||
| default_factory=list, | ||
| title="Skill paths", | ||
| description="Paths to skill directories or directories containing skill subdirectories.", | ||
| ) | ||
|
Comment on lines
+1932
to
+1936
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add validation for individual
Suggested patch class SkillsConfiguration(ConfigurationBase):
@@
paths: list[str] = Field(
default_factory=list,
title="Skill paths",
description="Paths to skill directories or directories containing skill subdirectories.",
)
+
+ `@field_validator`("paths")
+ `@classmethod`
+ def validate_paths(cls, value: list[str]) -> list[str]:
+ """Normalize and validate configured skill paths."""
+ seen: set[str] = set()
+ normalized_paths: list[str] = []
+ for path in value:
+ normalized = path.strip()
+ if not normalized:
+ raise ValueError("Skill paths must not contain empty values")
+ if normalized in seen:
+ raise ValueError(f"Duplicate skill path: '{normalized}'")
+ seen.add(normalized)
+ normalized_paths.append(normalized)
+ return normalized_pathsAs per coding guidelines: 🤖 Prompt for AI Agents |
||
|
|
||
|
|
||
| class Configuration(ConfigurationBase): | ||
| """Global service configuration.""" | ||
|
|
||
|
|
@@ -2076,6 +2096,12 @@ class Configuration(ConfigurationBase): | |
| "in rag.inline or rag.tool.", | ||
| ) | ||
|
|
||
| skills: Optional[SkillsConfiguration] = Field( | ||
| default=None, | ||
| title="Agent skills", | ||
| description="Agent skills configuration. Specifies paths to skill directories.", | ||
| ) | ||
|
|
||
| @model_validator(mode="after") | ||
| def validate_mcp_auth_headers(self) -> Self: | ||
| """ | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,47 @@ | ||
| """Unit tests for SkillsConfiguration model.""" | ||
|
|
||
| # pylint: disable=no-member | ||
| # Pydantic Field(default_factory=...) pattern confuses pylint's static analysis | ||
|
|
||
| import pytest | ||
| from pydantic import ValidationError | ||
|
|
||
| from models.config import SkillsConfiguration | ||
|
|
||
|
|
||
| class TestSkillsConfiguration: | ||
| """Tests for SkillsConfiguration model.""" | ||
|
|
||
| def test_empty_paths_list(self) -> None: | ||
| """Test that an explicit empty paths list is allowed.""" | ||
| config = SkillsConfiguration(paths=[]) | ||
| assert config.paths == [] | ||
|
|
||
| def test_no_unknown_fields_allowed(self) -> None: | ||
| """Test that SkillsConfiguration rejects unknown fields.""" | ||
| with pytest.raises(ValidationError, match="Extra inputs are not permitted"): | ||
| SkillsConfiguration(unknown_field="value") # type: ignore[call-arg] | ||
|
|
||
| def test_skill_paths(self) -> None: | ||
| """Test configuration with multiple skill paths.""" | ||
| config = SkillsConfiguration( | ||
| paths=[ | ||
| "/var/skills/openshift-troubleshooting", | ||
| "/var/skills/code-review", | ||
| "/opt/custom-skills", | ||
| ] | ||
| ) | ||
| assert len(config.paths) == 3 | ||
| assert "/var/skills/openshift-troubleshooting" in config.paths | ||
| assert "/var/skills/code-review" in config.paths | ||
| assert "/opt/custom-skills" in config.paths | ||
|
|
||
| def test_mixed_absolute_and_relative_paths(self) -> None: | ||
| """Test that both absolute and relative paths can be mixed.""" | ||
| config = SkillsConfiguration( | ||
| paths=["/var/skills", "./local-skills", "/opt/skills"] | ||
| ) | ||
| assert len(config.paths) == 3 | ||
| assert "/var/skills" in config.paths | ||
| assert "./local-skills" in config.paths | ||
| assert "/opt/skills" in config.paths | ||
|
Comment on lines
+39
to
+47
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @radofuchs what do you think about this test? From the code's perspective, absolute/relative doesn't matter. It's just "str" at the end of the day - but I added this coz it signals that we'll work with both absolute and relative path - ie this is my attempt at BDD :) |
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is there a reason that we need the paths? It would make sense if we had other data fields under skills but if paths is the only one then I think skills can just be a list. wdyt?
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yea I think that makes total sense. I was going off of what's here https://github.com/lightspeed-core/lightspeed-stack/blob/main/docs/design/agent-skills/agent-skills.md#configurationbut I do remember this discussion and AFAIR we'd decided we don't need path. I'll update the design doc tooBased on further discussions:
It's a little weird to look at now, but the current layout is the safest approach - I can't think of anything else that might be needed under the skills tab (eg, settings etc), but keeping it tabbe-ed future proofs it so keeping it as is