Skip to content

LCORE-2853: skills impact /tools#2024

Open
jrobertboos wants to merge 1 commit into
lightspeed-core:mainfrom
jrobertboos:lcore-2853
Open

LCORE-2853: skills impact /tools#2024
jrobertboos wants to merge 1 commit into
lightspeed-core:mainfrom
jrobertboos:lcore-2853

Conversation

@jrobertboos

@jrobertboos jrobertboos commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

Description

When agent skills are enabled, pydantic-ai registers capability tools (list_skills, load_skill, read_skill_resource, run_skill_script) that agents use internally. The /tools endpoint previously only surfaced Llama Stack toolgroups and MCP server tools, so clients could not discover these skills-related tools.

This PR adds get_agent_capability_tools() to serialize pydantic-ai capability toolsets into the same format as the /tools response, and merges them into the consolidated tools list in the endpoint handler (with deduplication by identifier). Skills tools are tagged with provider_id: agent-skills, toolgroup_id: builtin::agent-skills, and server_source: builtin.

Type of change

  • Refactor
  • New feature
  • Bug fix
  • CVE fix
  • Optimization
  • Documentation Update
  • Configuration Update
  • Bump-up service version
  • Bump-up dependent library
  • Bump-up library or tool used for development (does not change the final image)
  • CI configuration change
  • Konflux configuration change
  • Unit tests improvement
  • Integration tests improvement
  • End to end tests improvement
  • Benchmarks improvement

Tools used to create PR

Identify any AI code assistants used in this PR (for transparency and review context)

  • Assisted-by: Cursor
  • Generated by: N/A

Related Tickets & Documents

  • Related Issue # LCORE-2853
  • Closes #

Checklist before requesting a review

  • I have performed a self-review of my code.
  • PR has passed all pre-merge test jobs.
  • If it is a core feature, I have added thorough tests.

Testing

  • Added unit tests in tests/unit/utils/test_pydantic_ai.py for get_agent_capability_tools() (empty when skills disabled, correct tools/metadata when configured)
  • Added unit test in tests/unit/app/endpoints/test_tools.py verifying /tools includes capability tools when skills are configured
  • Manually tested the /tools endpoint with skills enabled and confirmed list_skills, load_skill, read_skill_resource, and run_skill_script appear with expected metadata

Summary by CodeRabbit

  • New Features

    • The tools list now includes available built-in agent skills alongside existing tools.
    • Skill-based tools are shown with clearer parameter details and descriptions.
  • Bug Fixes

    • Duplicate tools are now avoided when combining tool sources.
    • Tool counts in the summary view are now calculated more reliably.

@coderabbitai

coderabbitai Bot commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

Adds get_agent_capability_tools to src/utils/pydantic_ai.py, which serializes tools from pydantic-ai AbstractCapability instances (derived from configured skills) into the /tools response format. The /tools endpoint integrates these tools after MCP/builtin consolidation using identifier-based deduplication.

Agent Capability Tools Integration

Layer / File(s) Summary
Capability tool serialization helpers
src/utils/pydantic_ai.py
Adds AbstractCapability import, agent-skills provider/toolgroup Final[str] constants, and four new helpers: _json_schema_to_parameters (flattens JSON Schema properties), _capability_tool_description (extracts <summary> tag text), _capability_tools_from_toolset (builds structured tool metadata), and get_agent_capability_tools(skills) (aggregates all capability tool entries).
/tools endpoint integration
src/app/endpoints/tools.py
Imports get_agent_capability_tools and, after MCP/builtin tool consolidation, appends capability tools whose identifiers are not already present; adjusts final log to use precomputed builtin/MCP counts.
Unit tests
tests/unit/utils/test_pydantic_ai.py, tests/unit/app/endpoints/test_tools.py
Adds TestGetAgentCapabilityTools covering null/empty/configured-skills cases with full load_skill parameter schema assertions; adds endpoint test asserting capability tools (list_skills, load_skill, etc.) appear with correct provider metadata.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested reviewers

  • asimurka
  • tisnik
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is concise and accurately reflects the main change: enabling skills to affect the /tools endpoint.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
✨ Simplify code
  • Create PR with simplified code

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@jrobertboos

Copy link
Copy Markdown
Contributor Author

Please Review:

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/app/endpoints/tools.py`:
- Around line 242-250: The deduplication in the consolidated_tools merge only
uses identifier, so capability tools can be incorrectly skipped when a
same-named tool exists from a different source. Update the merge logic in the
tools endpoint to deduplicate by the full source identity, using provider_id and
toolgroup_id alongside identifier for capability tools added via
get_agent_capability_tools(configuration.skills). Keep existing behavior for
unique tools, but ensure /tools can surface same-named tools from different
providers or groups without suppressing discoverability.

In `@src/utils/pydantic_ai.py`:
- Around line 105-145: The new helper docstrings in _json_schema_to_parameters,
_capability_tool_description, and _capability_tools_from_toolset are too minimal
for this repo’s docstring standard. Update each function’s docstring to the
local Google-style format with clear descriptive text plus Parameters:,
Returns:, and Raises: sections where applicable, keeping the wording aligned
with other functions under src/**/*.py.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 19ce1df6-12a5-4097-89b0-f17c45999d1b

📥 Commits

Reviewing files that changed from the base of the PR and between 8733dfe and 0b332c6.

📒 Files selected for processing (4)
  • src/app/endpoints/tools.py
  • src/utils/pydantic_ai.py
  • tests/unit/app/endpoints/test_tools.py
  • tests/unit/utils/test_pydantic_ai.py
📜 Review details
⏰ Context from checks skipped due to timeout. (15)
  • GitHub Check: unit_tests (3.13)
  • GitHub Check: unit_tests (3.12)
  • GitHub Check: build-pr
  • GitHub Check: Pylinter
  • GitHub Check: integration_tests (3.12)
  • GitHub Check: integration_tests (3.13)
  • GitHub Check: Konflux kflux-prd-rh02 / lightspeed-stack-0-6-on-pull-request
  • GitHub Check: Konflux kflux-prd-rh02 / lightspeed-stack-on-pull-request
  • GitHub Check: E2E: server mode / ci / group 3
  • GitHub Check: E2E: library mode / ci / group 2
  • GitHub Check: E2E: library mode / ci / group 1
  • GitHub Check: E2E: server mode / ci / group 2
  • GitHub Check: E2E: server mode / ci / group 1
  • GitHub Check: E2E: library mode / ci / group 3
  • GitHub Check: E2E Tests for Lightspeed Evaluation job
⚠️ CI failures not shown inline (2)

GitHub Actions: OpenAPI (Spectral) / spectral: LCORE-2853: skills impact /tools

Conclusion: failure

View job details

##[group]Run set -euo pipefail
 �[36;1mset -euo pipefail�[0m
 �[36;1muv run python scripts/generate_openapi_schema.py /tmp/openapi-generated.json�[0m
 �[36;1mif ! diff -u docs/openapi.json /tmp/openapi-generated.json; then�[0m
 �[36;1m  echo "::error::docs/openapi.json is out of date. Regenerate with: uv run scripts/generate_openapi_schema.py docs/openapi.json"�[0m

GitHub Actions: OpenAPI (Spectral) / 0_spectral.txt: LCORE-2853: skills impact /tools

Conclusion: failure

View job details

##[group]Run set -euo pipefail
 �[36;1mset -euo pipefail�[0m
 �[36;1muv run python scripts/generate_openapi_schema.py /tmp/openapi-generated.json�[0m
 �[36;1mif ! diff -u docs/openapi.json /tmp/openapi-generated.json; then�[0m
 �[36;1m  echo "::error::docs/openapi.json is out of date. Regenerate with: uv run scripts/generate_openapi_schema.py docs/openapi.json"�[0m
🧰 Additional context used
📓 Path-based instructions (3)
src/**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

src/**/*.py: Use absolute imports for internal modules: from authentication import get_auth_dependency
Llama Stack imports: Use from llama_stack_client import AsyncLlamaStackClient
Check constants.py for shared constants before defining new ones
All modules must start with descriptive docstrings explaining purpose
Use logger = get_logger(__name__) from log.py for module logging
All functions must have complete type annotations for parameters and return types, use modern syntax (str | int), and include descriptive docstrings
Use snake_case with descriptive, action-oriented names for functions (get_, validate_, check_)
Avoid in-place parameter modification anti-patterns; return new data structures instead of modifying function parameters
Use async def for I/O operations and external API calls
Use standard log levels with clear purposes: debug() for diagnostic info, info() for program execution, warning() for unexpected events, error() for serious problems
All classes must have descriptive docstrings explaining purpose and use PascalCase with standard suffixes: Configuration, Error/Exception, Resolver, Interface
Abstract classes must use ABC with @abstractmethod decorators
Follow Google Python docstring conventions with required sections: Parameters, Returns, Raises, and Attributes for classes

Files:

  • src/app/endpoints/tools.py
  • src/utils/pydantic_ai.py
src/app/**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

src/app/**/*.py: FastAPI dependencies: Import from fastapi module for APIRouter, HTTPException, Request, status, Depends
Use FastAPI HTTPException with appropriate status codes for API endpoints and handle APIConnectionError from Llama Stack

Files:

  • src/app/endpoints/tools.py
tests/**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

tests/**/*.py: Use pytest for all unit and integration tests; do not use unittest
Use pytest.mark.asyncio marker for async tests

Files:

  • tests/unit/app/endpoints/test_tools.py
  • tests/unit/utils/test_pydantic_ai.py
🧠 Learnings (2)
📚 Learning: 2026-04-06T20:18:07.852Z
Learnt from: major
Repo: lightspeed-core/lightspeed-stack PR: 1463
File: src/app/endpoints/rlsapi_v1.py:266-271
Timestamp: 2026-04-06T20:18:07.852Z
Learning: In the lightspeed-stack codebase, within `src/app/endpoints/` inference/MCP endpoints, treat `tools: Optional[list[Any]]` in MCP tool definitions as an intentional, consistent typing pattern (used across `query`, `responses`, `streaming_query`, `rlsapi_v1`). Do not raise or suggest this as a typing issue during code review; changing it in isolation could break endpoint typing consistency across the codebase.

Applied to files:

  • src/app/endpoints/tools.py
📚 Learning: 2026-06-24T13:45:37.249Z
Learnt from: Jdubrick
Repo: lightspeed-core/lightspeed-stack PR: 1971
File: src/utils/markdown_repair.py:31-36
Timestamp: 2026-06-24T13:45:37.249Z
Learning: In the lightspeed-stack repository, docstrings must use the section header name "Parameters:" (not "Args:") for function arguments, even if the project references Google Python docstring conventions. Ensure docstrings follow the project’s established "Parameters:" header format for any documented function parameters.

Applied to files:

  • src/app/endpoints/tools.py
  • tests/unit/app/endpoints/test_tools.py
  • tests/unit/utils/test_pydantic_ai.py
  • src/utils/pydantic_ai.py

Comment on lines +242 to +250
existing_tool_ids = {
tool.get("identifier") for tool in consolidated_tools if tool.get("identifier")
}
capability_tools = get_agent_capability_tools(configuration.skills)
for tool_dict in capability_tools:
identifier = tool_dict.get("identifier")
if identifier and identifier not in existing_tool_ids:
consolidated_tools.append(tool_dict)
existing_tool_ids.add(identifier)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Deduplicate capability tools by source, not by name alone.

This block only keys on identifier, but /tools already carries provider_id and toolgroup_id because same-named tools can exist in different sources. With the current logic, an MCP or builtin tool named list_skills/load_skill will silently suppress the capability tool, which defeats the discoverability this PR is adding.

Suggested fix
-    existing_tool_ids = {
-        tool.get("identifier") for tool in consolidated_tools if tool.get("identifier")
-    }
+    existing_tool_keys = {
+        (
+            tool.get("identifier"),
+            tool.get("provider_id"),
+            tool.get("toolgroup_id"),
+        )
+        for tool in consolidated_tools
+        if tool.get("identifier")
+    }
     capability_tools = get_agent_capability_tools(configuration.skills)
     for tool_dict in capability_tools:
-        identifier = tool_dict.get("identifier")
-        if identifier and identifier not in existing_tool_ids:
+        key = (
+            tool_dict.get("identifier"),
+            tool_dict.get("provider_id"),
+            tool_dict.get("toolgroup_id"),
+        )
+        if key[0] and key not in existing_tool_keys:
             consolidated_tools.append(tool_dict)
-            existing_tool_ids.add(identifier)
+            existing_tool_keys.add(key)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
existing_tool_ids = {
tool.get("identifier") for tool in consolidated_tools if tool.get("identifier")
}
capability_tools = get_agent_capability_tools(configuration.skills)
for tool_dict in capability_tools:
identifier = tool_dict.get("identifier")
if identifier and identifier not in existing_tool_ids:
consolidated_tools.append(tool_dict)
existing_tool_ids.add(identifier)
existing_tool_keys = {
(
tool.get("identifier"),
tool.get("provider_id"),
tool.get("toolgroup_id"),
)
for tool in consolidated_tools
if tool.get("identifier")
}
capability_tools = get_agent_capability_tools(configuration.skills)
for tool_dict in capability_tools:
key = (
tool_dict.get("identifier"),
tool_dict.get("provider_id"),
tool_dict.get("toolgroup_id"),
)
if key[0] and key not in existing_tool_keys:
consolidated_tools.append(tool_dict)
existing_tool_keys.add(key)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/app/endpoints/tools.py` around lines 242 - 250, The deduplication in the
consolidated_tools merge only uses identifier, so capability tools can be
incorrectly skipped when a same-named tool exists from a different source.
Update the merge logic in the tools endpoint to deduplicate by the full source
identity, using provider_id and toolgroup_id alongside identifier for capability
tools added via get_agent_capability_tools(configuration.skills). Keep existing
behavior for unique tools, but ensure /tools can surface same-named tools from
different providers or groups without suppressing discoverability.

Comment thread src/utils/pydantic_ai.py
Comment on lines +105 to +145
def _json_schema_to_parameters(
schema: Optional[dict[str, Any]],
) -> list[dict[str, Any]]:
"""Convert a JSON Schema object to the flat parameter list used by ``/tools``."""
if not schema or "properties" not in schema:
return []

required_params = set(schema.get("required", []))
parameters: list[dict[str, Any]] = []
for name, prop in schema["properties"].items():
parameter_type = prop.get("type")
if parameter_type is None and "anyOf" in prop:
for option in prop["anyOf"]:
if isinstance(option, dict) and option.get("type") not in (
None,
"null",
):
parameter_type = option["type"]
break
parameters.append(
{
"name": name,
"description": prop.get("description", ""),
"parameter_type": parameter_type or "string",
"required": name in required_params,
"default": prop.get("default"),
}
)
return parameters


def _capability_tool_description(description: str) -> str:
"""Extract a user-facing description from pydantic-ai tool docstrings."""
if match := re.search(r"<summary>(.*?)</summary>", description, re.DOTALL):
return match.group(1).strip()
return description.strip()


def _capability_tools_from_toolset(toolset: Any) -> list[dict[str, Any]]:
"""Serialize tools registered on a pydantic-ai capability toolset."""
raw_tools = getattr(toolset, "tools", None)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Expand the new helper docstrings to the repo format.

These three helpers only have one-line docstrings right now. In this repo, functions under src/**/*.py are expected to carry full Parameters: / Returns: / Raises: sections, so these new utilities are out of step with the local documentation contract.

As per coding guidelines, src/**/*.py functions must “include descriptive docstrings” and “follow Google Python docstring conventions with required sections: Parameters, Returns, Raises”; based on learnings, this repo uses Parameters: rather than Args:.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/utils/pydantic_ai.py` around lines 105 - 145, The new helper docstrings
in _json_schema_to_parameters, _capability_tool_description, and
_capability_tools_from_toolset are too minimal for this repo’s docstring
standard. Update each function’s docstring to the local Google-style format with
clear descriptive text plus Parameters:, Returns:, and Raises: sections where
applicable, keeping the wording aligned with other functions under src/**/*.py.

Sources: Coding guidelines, Learnings

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant