Skip to content

Conversation

@ryoppippi
Copy link
Member

@ryoppippi ryoppippi commented Aug 12, 2025

Summary

  • Tool Calling: Added intuitive call() and acall() methods for direct tool execution
  • Glob Filtering: Confirmed existing glob pattern support with negative patterns
  • Meta Tools: Implemented dynamic tool discovery and execution system using BM25 search

Details

This PR brings the Python SDK to feature parity with the Node SDK by implementing the key features that were recently added to the Node version:

1. Tool Calling Ability

  • Added call() method for more intuitive tool usage
    • Supports keyword arguments: tool.call(name="John", email="[email protected]")
    • Supports dictionary arguments: tool.call({"name": "John"})
    • Supports JSON string arguments: tool.call('{"name": "John"}')
  • Added acall() method for async execution
  • Maintains backward compatibility with existing execute() method

2. Glob Pattern Filtering (Already Existed)

  • Confirmed the Python SDK already has full glob pattern support
  • Supports negative patterns with ! prefix
  • Example: toolset.get_tools(["hris_*", "!hris_delete_*"])

3. Meta Tools (Beta)

  • Implemented meta_filter_relevant_tools for natural language tool discovery
  • Implemented meta_execute_tool for dynamic tool execution
  • Uses BM25 algorithm for relevance scoring (via bm25s library)
  • Accessible via tools.meta_tools() method

Testing

  • Added comprehensive tests for tool calling functionality
  • Added tests for meta tools discovery and execution
  • All existing tests pass
  • Example script provided in examples/meta_tools_example.py

Documentation

  • Updated README with new features
  • Added detailed docstrings for all new methods
  • Provided usage examples

Breaking Changes

None - all changes are additive and maintain backward compatibility.

Dependencies

  • Added bm25s>=0.2.2 for BM25 search functionality
  • Added numpy>=1.24.0 as dependency of bm25s

Summary by cubic

Added intuitive call() and acall() methods for tool execution, confirmed glob pattern filtering with negative patterns, and introduced meta tools for dynamic tool discovery and execution using BM25 search.

  • New Features

  • call() and acall() methods for direct tool usage.

  • Meta tools for natural language tool search and dynamic execution.

  • BM25-based relevance scoring for tool discovery.

  • Dependencies

  • Added bm25s and numpy for meta tool search functionality.

Created comprehensive guidance file for Claude Code with:

- Essential development commands (make targets)

- High-level code architecture overview

- Core components documentation (StackOneToolSet, Models, Parser, MCP Server)

- Key development patterns and authentication

- Imports of existing Cursor rules for consistency
- Fixed import statements to not include .mdc extension

- Fixed references to imported files using @filename syntax

- Removed duplicated content covered by imported Cursor rules
- Use @.cursor/rules/filename syntax when referencing imported files
- Changed from @import to direct @ references

- Added ./ for relative paths

- Formatted as bullet list for clarity
- Updated all references to use @./.cursor/rules/ pattern

- Ensures consistency throughout the document
- Add responses library for HTTP mocking

- Mock StackOne API calls in tests instead of skipping

- Import and execute examples directly to enable mocking

- Skip only when optional dependencies are missing
- Remove unused imports (os, Any)

- Remove trailing whitespace

- Remove whitespace from blank lines
- Implement tool calling ability with call() and acall() methods

- Add glob pattern filtering support (already existed, confirmed working)

- Implement meta tools for dynamic tool discovery and execution

- Add BM25-based search for tool discovery

- Include comprehensive tests for all new features

- Add example demonstrating meta tools usage

- Update README with new feature documentation
Copy link

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

cubic analysis

3 issues found across 16 files • Review in cubic

React with 👍 or 👎 to teach cubic. You can also tag @cubic-dev-ai to give feedback, ask questions, or re-run the review.

)

# Call the tool asynchronously
result = await mock_tool.acall(name="test", value=42)
Copy link

Choose a reason for hiding this comment

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

Using responses.activate with a function that performs the request in a background thread is unsafe—responses’ monkey-patch is not thread-safe, so the async test could leak real HTTP requests or become flaky.

Prompt for AI agents
Address the following comment on tests/test_tool_calling.py at line 132:

<comment>Using responses.activate with a function that performs the request in a background thread is unsafe—responses’ monkey-patch is not thread-safe, so the async test could leak real HTTP requests or become flaky.</comment>

<file context>
@@ -0,0 +1,161 @@
+&quot;&quot;&quot;Tests for tool calling functionality&quot;&quot;&quot;
+
+import json
+
+import pytest
+import responses
+
+from stackone_ai import StackOneTool
+from stackone_ai.models import ExecuteConfig, ToolParameters
</file context>

# Process results
search_results = []
for idx, score in zip(results[0], scores[0], strict=False):
if score < min_score:
Copy link

Choose a reason for hiding this comment

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

minScore is compared with the raw BM25 score instead of the 0-1 normalised score described in the docs, making the filter behave incorrectly.

Prompt for AI agents
Address the following comment on stackone_ai/meta_tools.py at line 86:

<comment>minScore is compared with the raw BM25 score instead of the 0-1 normalised score described in the docs, making the filter behave incorrectly.</comment>

<file context>
@@ -0,0 +1,277 @@
+&quot;&quot;&quot;Meta tools for dynamic tool discovery and execution&quot;&quot;&quot;
+
+from __future__ import annotations
+
+import json
+from typing import TYPE_CHECKING
+
+import bm25s
+import numpy as np
</file context>

else:
func = partial(self.execute, kwargs if kwargs else None)

return await asyncio.get_event_loop().run_in_executor(None, func)
Copy link

Choose a reason for hiding this comment

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

Deprecated asyncio.get_event_loop() used inside coroutine; switch to asyncio.get_running_loop() to avoid deprecation warnings and future breakage

Prompt for AI agents
Address the following comment on stackone_ai/models.py at line 277:

<comment>Deprecated asyncio.get_event_loop() used inside coroutine; switch to asyncio.get_running_loop() to avoid deprecation warnings and future breakage</comment>

<file context>
@@ -214,6 +214,68 @@ def execute(self, arguments: str | JsonDict | None = None) -&gt; JsonDict:
                 ) from e
             raise StackOneError(f&quot;Request failed: {e}&quot;) from e
 
+    def call(self, *args: Any, **kwargs: Any) -&gt; JsonDict:
+        &quot;&quot;&quot;Call the tool with the given arguments
+
+        This method provides a more intuitive way to execute tools directly.
+
+        Args:
</file context>
Suggested change
return await asyncio.get_event_loop().run_in_executor(None, func)
return await asyncio.get_running_loop().run_in_executor(None, func)

Move imports out of function body for better code organization
Remove import from inside function body for better code organization
@ryoppippi
Copy link
Member Author

merge after claude and test fix branch

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

This PR brings the Python SDK to feature parity with the Node SDK by implementing three key enhancements: intuitive tool calling methods, meta tools for dynamic discovery, and glob pattern filtering support.

  • Tool Calling: Added call() and acall() methods for more intuitive tool execution with support for multiple argument formats
  • Meta Tools: Implemented BM25-based tool discovery system enabling natural language queries for finding and executing tools
  • Glob Filtering: Confirmed and documented existing support for advanced pattern filtering including negative patterns

Reviewed Changes

Copilot reviewed 15 out of 16 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
stackone_ai/models.py Added call() and acall() methods to StackOneTool class, implemented meta_tools() method on Tools class
stackone_ai/meta_tools.py New module implementing BM25-based tool search index and meta tool creation functions
tests/test_tool_calling.py Comprehensive tests for new tool calling functionality with various argument formats
tests/test_meta_tools.py Test suite for meta tools discovery and execution capabilities
pyproject.toml Added bm25s and numpy dependencies for meta tools functionality
examples/meta_tools_example.py Detailed example demonstrating meta tools usage with various AI frameworks
Multiple workflow files Updated GitHub Actions to use pinned commit SHAs for security
Comments suppressed due to low confidence (1)

tests/test_meta_tools.py:85

  • This line appears to be testing production code logic but is duplicated from the meta_tools.py file. The test should verify the search functionality without reimplementing the search logic.
        assert len(index.tools) == len(sample_tools)

tool = self.tool_map[tool_name]

# Normalize score to 0-1 range
normalized_score = float(1 / (1 + np.exp(-score / 10)))
Copy link

Copilot AI Aug 12, 2025

Choose a reason for hiding this comment

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

The magic number 10 in the score normalization formula should be extracted as a named constant to improve maintainability and make the normalization logic more explicit.

Suggested change
normalized_score = float(1 / (1 + np.exp(-score / 10)))
normalized_score = float(1 / (1 + np.exp(-score / self.SCORE_NORMALIZATION_SCALE)))

Copilot uses AI. Check for mistakes.
else:
func = partial(self.execute, kwargs if kwargs else None)

return await asyncio.get_event_loop().run_in_executor(None, func)
Copy link

Copilot AI Aug 12, 2025

Choose a reason for hiding this comment

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

Using asyncio.get_event_loop() is deprecated in favor of asyncio.get_running_loop() in modern Python. This could cause issues in some async contexts.

Suggested change
return await asyncio.get_event_loop().run_in_executor(None, func)
return await asyncio.get_running_loop().run_in_executor(None, func)

Copilot uses AI. Check for mistakes.

# Process results
search_results = []
for idx, score in zip(results[0], scores[0], strict=False):
Copy link

Copilot AI Aug 12, 2025

Choose a reason for hiding this comment

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

Using strict=False in zip could mask bugs if results[0] and scores[0] have different lengths. The BM25 retrieval should guarantee matching lengths, so strict=True would be safer.

Suggested change
for idx, score in zip(results[0], scores[0], strict=False):
for idx, score in zip(results[0], scores[0], strict=True):

Copilot uses AI. Check for mistakes.
if spec and spec.loader:
module = importlib.util.module_from_spec(spec)
sys.modules["example"] = module
spec.loader.exec_module(module)
Copy link

Copilot AI Aug 12, 2025

Choose a reason for hiding this comment

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

Dynamically importing and executing example modules could be a security risk if the examples contain malicious code. Consider using subprocess execution or sandboxing for safer test isolation.

Suggested change
spec.loader.exec_module(module)
# Run the example module in a subprocess for isolation
result = subprocess.run(
[sys.executable, str(example_path)],
capture_output=True,
text=True,
)
assert result.returncode == 0, (
f"Example {example_file} failed with exit code {result.returncode}\n"
f"stdout:\n{result.stdout}\n"
f"stderr:\n{result.stderr}"
)

Copilot uses AI. Check for mistakes.
Resolved uv.lock conflict by regenerating lock file
@ryoppippi ryoppippi merged commit 8b6de99 into main Aug 13, 2025
2 checks passed
@ryoppippi ryoppippi deleted the feature/tool-calling-meta-tools branch August 13, 2025 10:36
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.

3 participants