Skip to content

LCORE-1211 Fix /v1/tools returning empty fields for all tools#1246

Merged
tisnik merged 2 commits intolightspeed-core:mainfrom
max-svistunov:lcore-1211-tools-endpoint-empty
Mar 2, 2026
Merged

LCORE-1211 Fix /v1/tools returning empty fields for all tools#1246
tisnik merged 2 commits intolightspeed-core:mainfrom
max-svistunov:lcore-1211-tools-endpoint-empty

Conversation

@max-svistunov
Copy link
Contributor

@max-svistunov max-svistunov commented Mar 2, 2026

Description

Fix the /v1/tools endpoint. It was returning empty identifier, parameters, provider_id, and type fields for all tools. Reason: the Llama Stack SDK (v0.4.x) changed field names (e.g. identifier to name) and replaced the parameters list with the input_schema json; also provider_id and type fields were moved. This PR maps/propagates everything correctly.

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: Claude Opus 4.6
  • Generated by: Claude Opus 4.6

Related Tickets & Documents

  • Related Issue # LCORE-1211
  • Closes # LCORE-1211

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

Manual verification

curl -s http://localhost:8080/v1/tools -H "Authorization: Bearer test" | python -m json.tool

Unit tests

uv run pytest tests/unit/app/endpoints/test_tools.py -v

Summary by CodeRabbit

  • Refactor

    • Normalized tool definitions and unified parameter handling so tool metadata and input parameters are consistently derived and propagated across tool types and sources.
  • Tests

    • Expanded and added tests covering tool configuration, JSON Schema-to-parameter conversion, metadata propagation, and built-in vs. migrated tool scenarios to improve reliability.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 2, 2026

Walkthrough

Adds normalization for tool dictionaries in the tools endpoint: converts ToolDef input_schema to parameters, remaps name to identifier, and propagates provider_id and type from toolgroup to tools. Tests updated to use ToolDef-style mocks and verify parameter conversion.

Changes

Cohort / File(s) Summary
Endpoint logic
src/app/endpoints/tools.py
Adds _input_schema_to_parameters(schema) to convert JSON Schema->parameter list; adds _normalize_tool_dict(tool_dict, toolgroup) to remap fields and propagate provider_id/type; applies normalization in tools_endpoint_handler before setting server_source.
Unit tests
tests/unit/app/endpoints/test_tools.py
Reworks mocks to ToolDef-style (name, input_schema, output_schema) and moves provider_id/type to toolgroup mocks; adds _make_tool_def_mock helper; adds tests for input_schema->parameters conversion (including None/empty/required handling) and for propagation/overrides of provider/type and identifier/parameters mapping.
Exports / test helpers
src/app/endpoints/..., tests/...
Exposes _input_schema_to_parameters for testing; introduces new internal helper _normalize_tool_dict (internal-only, no public API change).

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • tisnik
🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main bug being fixed: the /v1/tools endpoint returning empty fields for all tools.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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 and usage tips.

@max-svistunov max-svistunov changed the title Fix /v1/tools returning empty fields for all tools LCORE-1211 Fix /v1/tools returning empty fields for all tools Mar 2, 2026
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

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 the current code and only fix it if needed.

Inline comments:
In `@src/app/endpoints/tools.py`:
- Around line 158-167: The code currently only remaps missing keys; update it to
treat empty values as missing too by checking falsy values rather than presence.
For identifier/name: replace the presence-only check with something like if
tool_dict.get("identifier") in (None, "") and "name" in tool_dict then set
tool_dict["identifier"]=tool_dict.pop("name"); for parameters/input_schema: if
not tool_dict.get("parameters") and "input_schema" in tool_dict then
tool_dict["parameters"]=_input_schema_to_parameters(tool_dict.pop("input_schema"));
and replace the setdefault calls for provider_id and type with conditional
assignments that overwrite empty/falsy values (e.g., if not
tool_dict.get("provider_id"): tool_dict["provider_id"]=toolgroup.provider_id and
if not tool_dict.get("type"): tool_dict["type"]=getattr(toolgroup, "type", None)
or "tool").

In `@tests/unit/app/endpoints/test_tools.py`:
- Around line 971-1081: Add a regression variant to
test_tools_endpoint_rag_builtin_toolgroup that simulates legacy empty fields:
when constructing the rag_tool via _make_tool_def_mock, include identifier="",
provider_id="", type="", and parameters=[] alongside the valid name and
input_schema, then call tools.tools_endpoint_handler (as already done) and
assert the returned ToolsResponse/tools[0] fields (identifier, provider_id,
type, server_source, toolgroup_id, and parameters converted from input_schema)
are populated from the modern fields; update/mock_client.tools.list to return
this modified rag_tool and keep the existing assertions to verify the endpoint
overrides empty legacy placeholders with the correct values.

ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 84105b2 and 3e5e30e.

📒 Files selected for processing (2)
  • src/app/endpoints/tools.py
  • tests/unit/app/endpoints/test_tools.py

@max-svistunov
Copy link
Contributor Author

@radofuchs @tisnik Could you PTAL?

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
src/app/endpoints/tools.py (1)

65-85: Return a normalized copy instead of mutating tool_dict in place.

Line 65 currently mutates input and Line 178 relies on side effects. Returning a new dict makes the flow safer and easier to reason about.

♻️ Proposed refactor
-def _normalize_tool_dict(tool_dict: dict[str, Any], toolgroup: Any) -> None:
+def _normalize_tool_dict(tool_dict: dict[str, Any], toolgroup: Any) -> dict[str, Any]:
     """Normalize a ToolDef dict to the endpoint's response format.
@@
-    if "name" in tool_dict and not tool_dict.get("identifier"):
-        tool_dict["identifier"] = tool_dict["name"]
-    tool_dict.pop("name", None)
+    normalized_tool_dict = dict(tool_dict)
+    if "name" in normalized_tool_dict and not normalized_tool_dict.get("identifier"):
+        normalized_tool_dict["identifier"] = normalized_tool_dict["name"]
+    normalized_tool_dict.pop("name", None)

-    if "input_schema" in tool_dict and not tool_dict.get("parameters"):
-        tool_dict["parameters"] = _input_schema_to_parameters(tool_dict["input_schema"])
-    tool_dict.pop("input_schema", None)
+    if "input_schema" in normalized_tool_dict and not normalized_tool_dict.get("parameters"):
+        normalized_tool_dict["parameters"] = _input_schema_to_parameters(
+            normalized_tool_dict["input_schema"]
+        )
+    normalized_tool_dict.pop("input_schema", None)

-    if not tool_dict.get("provider_id"):
-        tool_dict["provider_id"] = toolgroup.provider_id
-    if not tool_dict.get("type"):
-        tool_dict["type"] = getattr(toolgroup, "type", None) or "tool"
+    if not normalized_tool_dict.get("provider_id"):
+        normalized_tool_dict["provider_id"] = toolgroup.provider_id
+    if not normalized_tool_dict.get("type"):
+        normalized_tool_dict["type"] = getattr(toolgroup, "type", None) or "tool"
+
+    return normalized_tool_dict
@@
-            _normalize_tool_dict(tool_dict, toolgroup)
+            tool_dict = _normalize_tool_dict(tool_dict, toolgroup)

As per coding guidelines: "Avoid in-place parameter modification anti-patterns; return new data structures instead".

Also applies to: 178-178

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/app/endpoints/tools.py` around lines 65 - 85, _update
_normalize_tool_dict to return a new normalized dict instead of mutating the
input: copy the incoming tool_dict (shallow), apply the renames and default
propagation on the copy (handle "name" -> "identifier", "input_schema" ->
"parameters" via _input_schema_to_parameters, set provider_id and type from
toolgroup), and return that new dict; then update all call sites that currently
rely on side effects (e.g., the caller that previously ignored the return at the
place referenced by the review) to use the returned value (assign/replace the
original variable with the returned dict) so no in-place modification is
required.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/app/endpoints/tools.py`:
- Around line 65-85: _update _normalize_tool_dict to return a new normalized
dict instead of mutating the input: copy the incoming tool_dict (shallow), apply
the renames and default propagation on the copy (handle "name" -> "identifier",
"input_schema" -> "parameters" via _input_schema_to_parameters, set provider_id
and type from toolgroup), and return that new dict; then update all call sites
that currently rely on side effects (e.g., the caller that previously ignored
the return at the place referenced by the review) to use the returned value
(assign/replace the original variable with the returned dict) so no in-place
modification is required.

ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3e5e30e and 757290c.

📒 Files selected for processing (2)
  • src/app/endpoints/tools.py
  • tests/unit/app/endpoints/test_tools.py

Copy link
Contributor

@tisnik tisnik left a comment

Choose a reason for hiding this comment

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

LGTM

@tisnik tisnik merged commit 6528bc2 into lightspeed-core:main Mar 2, 2026
22 checks passed
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.

2 participants