-
Notifications
You must be signed in to change notification settings - Fork 0
feat: bring Python SDK to feature parity with Node SDK #17
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
Conversation
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
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.
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) |
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.
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 @@
+"""Tests for tool calling functionality"""
+
+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: |
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.
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 @@
+"""Meta tools for dynamic tool discovery and execution"""
+
+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) |
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.
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) -> JsonDict:
) from e
raise StackOneError(f"Request failed: {e}") from e
+ def call(self, *args: Any, **kwargs: Any) -> JsonDict:
+ """Call the tool with the given arguments
+
+ This method provides a more intuitive way to execute tools directly.
+
+ Args:
</file context>
| 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
|
merge after claude and test fix branch |
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.
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()andacall()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))) |
Copilot
AI
Aug 12, 2025
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.
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.
| normalized_score = float(1 / (1 + np.exp(-score / 10))) | |
| normalized_score = float(1 / (1 + np.exp(-score / self.SCORE_NORMALIZATION_SCALE))) |
| else: | ||
| func = partial(self.execute, kwargs if kwargs else None) | ||
|
|
||
| return await asyncio.get_event_loop().run_in_executor(None, func) |
Copilot
AI
Aug 12, 2025
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.
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.
| return await asyncio.get_event_loop().run_in_executor(None, func) | |
| return await asyncio.get_running_loop().run_in_executor(None, func) |
|
|
||
| # Process results | ||
| search_results = [] | ||
| for idx, score in zip(results[0], scores[0], strict=False): |
Copilot
AI
Aug 12, 2025
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.
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.
| for idx, score in zip(results[0], scores[0], strict=False): | |
| for idx, score in zip(results[0], scores[0], strict=True): |
| if spec and spec.loader: | ||
| module = importlib.util.module_from_spec(spec) | ||
| sys.modules["example"] = module | ||
| spec.loader.exec_module(module) |
Copilot
AI
Aug 12, 2025
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.
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.
| 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}" | |
| ) |
Resolved uv.lock conflict by regenerating lock file
Summary
call()andacall()methods for direct tool executionDetails
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
call()method for more intuitive tool usagetool.call(name="John", email="[email protected]")tool.call({"name": "John"})tool.call('{"name": "John"}')acall()method for async executionexecute()method2. Glob Pattern Filtering (Already Existed)
!prefixtoolset.get_tools(["hris_*", "!hris_delete_*"])3. Meta Tools (Beta)
meta_filter_relevant_toolsfor natural language tool discoverymeta_execute_toolfor dynamic tool executionbm25slibrary)tools.meta_tools()methodTesting
examples/meta_tools_example.pyDocumentation
Breaking Changes
None - all changes are additive and maintain backward compatibility.
Dependencies
bm25s>=0.2.2for BM25 search functionalitynumpy>=1.24.0as dependency of bm25sSummary 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.