stackone-ai 2.5.0__tar.gz → 2.6.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- stackone_ai-2.6.0/.release-please-manifest.json +3 -0
- {stackone_ai-2.5.0 → stackone_ai-2.6.0}/CHANGELOG.md +14 -0
- {stackone_ai-2.5.0 → stackone_ai-2.6.0}/PKG-INFO +1 -1
- stackone_ai-2.6.0/examples/search_tool_example.py +66 -0
- {stackone_ai-2.5.0 → stackone_ai-2.6.0}/examples/semantic_search_example.py +0 -1
- {stackone_ai-2.5.0 → stackone_ai-2.6.0}/examples/test_examples.py +1 -0
- stackone_ai-2.6.0/examples/workday_integration.py +91 -0
- {stackone_ai-2.5.0 → stackone_ai-2.6.0}/pyproject.toml +1 -1
- {stackone_ai-2.5.0 → stackone_ai-2.6.0}/stackone_ai/__init__.py +1 -1
- {stackone_ai-2.5.0 → stackone_ai-2.6.0}/stackone_ai/models.py +2 -1
- {stackone_ai-2.5.0 → stackone_ai-2.6.0}/stackone_ai/toolset.py +49 -10
- {stackone_ai-2.5.0 → stackone_ai-2.6.0}/tests/test_semantic_search.py +78 -0
- {stackone_ai-2.5.0 → stackone_ai-2.6.0}/uv.lock +1 -1
- stackone_ai-2.5.0/.release-please-manifest.json +0 -3
- stackone_ai-2.5.0/examples/search_tool_example.py +0 -314
- {stackone_ai-2.5.0 → stackone_ai-2.6.0}/.claude/rules/development-workflow.md +0 -0
- {stackone_ai-2.5.0 → stackone_ai-2.6.0}/.claude/rules/examples-standards.md +0 -0
- {stackone_ai-2.5.0 → stackone_ai-2.6.0}/.claude/rules/git-workflow.md +0 -0
- {stackone_ai-2.5.0 → stackone_ai-2.6.0}/.claude/rules/nix-workflow.md +0 -0
- {stackone_ai-2.5.0 → stackone_ai-2.6.0}/.claude/rules/no-relative-imports.md +0 -0
- {stackone_ai-2.5.0 → stackone_ai-2.6.0}/.claude/rules/package-installation.md +0 -0
- {stackone_ai-2.5.0 → stackone_ai-2.6.0}/.claude/rules/release-please-standards.md +0 -0
- {stackone_ai-2.5.0 → stackone_ai-2.6.0}/.claude/rules/uv-scripts.md +0 -0
- {stackone_ai-2.5.0 → stackone_ai-2.6.0}/.cursor/rules/development-workflow.mdc +0 -0
- {stackone_ai-2.5.0 → stackone_ai-2.6.0}/.cursor/rules/examples-standards.mdc +0 -0
- {stackone_ai-2.5.0 → stackone_ai-2.6.0}/.cursor/rules/git-workflow.mdc +0 -0
- {stackone_ai-2.5.0 → stackone_ai-2.6.0}/.cursor/rules/no-relative-imports.mdc +0 -0
- {stackone_ai-2.5.0 → stackone_ai-2.6.0}/.cursor/rules/package-installation.mdc +0 -0
- {stackone_ai-2.5.0 → stackone_ai-2.6.0}/.cursor/rules/release-please-standards.mdc +0 -0
- {stackone_ai-2.5.0 → stackone_ai-2.6.0}/.cursor/rules/uv-scripts.mdc +0 -0
- {stackone_ai-2.5.0 → stackone_ai-2.6.0}/.envrc +0 -0
- {stackone_ai-2.5.0 → stackone_ai-2.6.0}/.github/actions/setup-nix/action.yaml +0 -0
- {stackone_ai-2.5.0 → stackone_ai-2.6.0}/.github/dependabot.yaml +0 -0
- {stackone_ai-2.5.0 → stackone_ai-2.6.0}/.github/workflows/ci.yaml +0 -0
- {stackone_ai-2.5.0 → stackone_ai-2.6.0}/.github/workflows/nix-flake-update.yaml +0 -0
- {stackone_ai-2.5.0 → stackone_ai-2.6.0}/.github/workflows/nix-flake.yaml +0 -0
- {stackone_ai-2.5.0 → stackone_ai-2.6.0}/.github/workflows/release.yaml +0 -0
- {stackone_ai-2.5.0 → stackone_ai-2.6.0}/.gitignore +0 -0
- {stackone_ai-2.5.0 → stackone_ai-2.6.0}/.gitleaks.toml +0 -0
- {stackone_ai-2.5.0 → stackone_ai-2.6.0}/.gitmodules +0 -0
- {stackone_ai-2.5.0 → stackone_ai-2.6.0}/.mcp.json +0 -0
- {stackone_ai-2.5.0 → stackone_ai-2.6.0}/.release-please-config.json +0 -0
- {stackone_ai-2.5.0 → stackone_ai-2.6.0}/CLAUDE.md +0 -0
- {stackone_ai-2.5.0 → stackone_ai-2.6.0}/LICENSE +0 -0
- {stackone_ai-2.5.0 → stackone_ai-2.6.0}/README.md +0 -0
- {stackone_ai-2.5.0 → stackone_ai-2.6.0}/examples/agent_tool_search.py +0 -0
- {stackone_ai-2.5.0 → stackone_ai-2.6.0}/examples/crewai_integration.py +0 -0
- {stackone_ai-2.5.0 → stackone_ai-2.6.0}/examples/file_uploads.py +0 -0
- {stackone_ai-2.5.0 → stackone_ai-2.6.0}/examples/index.py +0 -0
- {stackone_ai-2.5.0 → stackone_ai-2.6.0}/examples/langchain_integration.py +0 -0
- {stackone_ai-2.5.0 → stackone_ai-2.6.0}/examples/openai_integration.py +0 -0
- {stackone_ai-2.5.0 → stackone_ai-2.6.0}/examples/stackone_account_ids.py +0 -0
- {stackone_ai-2.5.0 → stackone_ai-2.6.0}/flake.lock +0 -0
- {stackone_ai-2.5.0 → stackone_ai-2.6.0}/flake.nix +0 -0
- {stackone_ai-2.5.0 → stackone_ai-2.6.0}/justfile +0 -0
- {stackone_ai-2.5.0 → stackone_ai-2.6.0}/stackone_ai/constants.py +0 -0
- {stackone_ai-2.5.0 → stackone_ai-2.6.0}/stackone_ai/feedback/__init__.py +0 -0
- {stackone_ai-2.5.0 → stackone_ai-2.6.0}/stackone_ai/feedback/tool.py +0 -0
- {stackone_ai-2.5.0 → stackone_ai-2.6.0}/stackone_ai/integrations/__init__.py +0 -0
- {stackone_ai-2.5.0 → stackone_ai-2.6.0}/stackone_ai/integrations/langgraph.py +0 -0
- {stackone_ai-2.5.0 → stackone_ai-2.6.0}/stackone_ai/local_search.py +0 -0
- {stackone_ai-2.5.0 → stackone_ai-2.6.0}/stackone_ai/py.typed +0 -0
- {stackone_ai-2.5.0 → stackone_ai-2.6.0}/stackone_ai/semantic_search.py +0 -0
- {stackone_ai-2.5.0 → stackone_ai-2.6.0}/stackone_ai/utils/__init__.py +0 -0
- {stackone_ai-2.5.0 → stackone_ai-2.6.0}/stackone_ai/utils/normalize.py +0 -0
- {stackone_ai-2.5.0 → stackone_ai-2.6.0}/stackone_ai/utils/tfidf_index.py +0 -0
- {stackone_ai-2.5.0 → stackone_ai-2.6.0}/tests/conftest.py +0 -0
- {stackone_ai-2.5.0 → stackone_ai-2.6.0}/tests/mocks/serve.ts +0 -0
- {stackone_ai-2.5.0 → stackone_ai-2.6.0}/tests/test_agent_tools.py +0 -0
- {stackone_ai-2.5.0 → stackone_ai-2.6.0}/tests/test_feedback.py +0 -0
- {stackone_ai-2.5.0 → stackone_ai-2.6.0}/tests/test_fetch_tools.py +0 -0
- {stackone_ai-2.5.0 → stackone_ai-2.6.0}/tests/test_integrations_langgraph.py +0 -0
- {stackone_ai-2.5.0 → stackone_ai-2.6.0}/tests/test_local_search.py +0 -0
- {stackone_ai-2.5.0 → stackone_ai-2.6.0}/tests/test_models.py +0 -0
- {stackone_ai-2.5.0 → stackone_ai-2.6.0}/tests/test_tfidf_index.py +0 -0
- {stackone_ai-2.5.0 → stackone_ai-2.6.0}/tests/test_tool_calling.py +0 -0
- {stackone_ai-2.5.0 → stackone_ai-2.6.0}/tests/test_toolset.py +0 -0
- {stackone_ai-2.5.0 → stackone_ai-2.6.0}/vendor/stackone-ai-node/.claude/rules/development-workflow.md +0 -0
- {stackone_ai-2.5.0 → stackone_ai-2.6.0}/vendor/stackone-ai-node/.claude/rules/file-operations.md +0 -0
- {stackone_ai-2.5.0 → stackone_ai-2.6.0}/vendor/stackone-ai-node/.claude/rules/git-workflow.md +0 -0
- {stackone_ai-2.5.0 → stackone_ai-2.6.0}/vendor/stackone-ai-node/.claude/rules/pnpm-usage.md +0 -0
- {stackone_ai-2.5.0 → stackone_ai-2.6.0}/vendor/stackone-ai-node/.claude/rules/typescript-patterns.md +0 -0
- {stackone_ai-2.5.0 → stackone_ai-2.6.0}/vendor/stackone-ai-node/.claude/rules/typescript-testing.md +0 -0
- {stackone_ai-2.5.0 → stackone_ai-2.6.0}/vendor/stackone-ai-node/.claude/skills/orama-integration/SKILL.md +0 -0
- {stackone_ai-2.5.0 → stackone_ai-2.6.0}/vendor/stackone-ai-node/.cursor/rules/development-workflow.mdc +0 -0
- {stackone_ai-2.5.0 → stackone_ai-2.6.0}/vendor/stackone-ai-node/.cursor/rules/file-operations.mdc +0 -0
- {stackone_ai-2.5.0 → stackone_ai-2.6.0}/vendor/stackone-ai-node/.cursor/rules/git-workflow.mdc +0 -0
- {stackone_ai-2.5.0 → stackone_ai-2.6.0}/vendor/stackone-ai-node/.cursor/rules/orama-integration.mdc +0 -0
- {stackone_ai-2.5.0 → stackone_ai-2.6.0}/vendor/stackone-ai-node/.cursor/rules/pnpm-usage.mdc +0 -0
- {stackone_ai-2.5.0 → stackone_ai-2.6.0}/vendor/stackone-ai-node/.cursor/rules/typescript-patterns.mdc +0 -0
- {stackone_ai-2.5.0 → stackone_ai-2.6.0}/vendor/stackone-ai-node/.cursor/rules/typescript-testing.mdc +0 -0
- {stackone_ai-2.5.0 → stackone_ai-2.6.0}/vendor/stackone-ai-node/.envrc +0 -0
- {stackone_ai-2.5.0 → stackone_ai-2.6.0}/vendor/stackone-ai-node/.git +0 -0
- {stackone_ai-2.5.0 → stackone_ai-2.6.0}/vendor/stackone-ai-node/.github/CODEOWNERS +0 -0
- {stackone_ai-2.5.0 → stackone_ai-2.6.0}/vendor/stackone-ai-node/.github/actions/setup-nix/action.yaml +0 -0
- {stackone_ai-2.5.0 → stackone_ai-2.6.0}/vendor/stackone-ai-node/.github/workflows/check-title.yaml +0 -0
- {stackone_ai-2.5.0 → stackone_ai-2.6.0}/vendor/stackone-ai-node/.github/workflows/ci.yaml +0 -0
- {stackone_ai-2.5.0 → stackone_ai-2.6.0}/vendor/stackone-ai-node/.github/workflows/claude.yaml +0 -0
- {stackone_ai-2.5.0 → stackone_ai-2.6.0}/vendor/stackone-ai-node/.github/workflows/dry-publish.yaml +0 -0
- {stackone_ai-2.5.0 → stackone_ai-2.6.0}/vendor/stackone-ai-node/.github/workflows/nix-flake.yaml +0 -0
- {stackone_ai-2.5.0 → stackone_ai-2.6.0}/vendor/stackone-ai-node/.github/workflows/release.yaml +0 -0
- {stackone_ai-2.5.0 → stackone_ai-2.6.0}/vendor/stackone-ai-node/.gitignore +0 -0
- {stackone_ai-2.5.0 → stackone_ai-2.6.0}/vendor/stackone-ai-node/.gitleaks.toml +0 -0
- {stackone_ai-2.5.0 → stackone_ai-2.6.0}/vendor/stackone-ai-node/.mcp.json +0 -0
- {stackone_ai-2.5.0 → stackone_ai-2.6.0}/vendor/stackone-ai-node/.oxfmtrc.jsonc +0 -0
- {stackone_ai-2.5.0 → stackone_ai-2.6.0}/vendor/stackone-ai-node/.oxlintrc.jsonc +0 -0
- {stackone_ai-2.5.0 → stackone_ai-2.6.0}/vendor/stackone-ai-node/AGENTS.md +0 -0
- {stackone_ai-2.5.0 → stackone_ai-2.6.0}/vendor/stackone-ai-node/CHANGELOG.md +0 -0
- {stackone_ai-2.5.0 → stackone_ai-2.6.0}/vendor/stackone-ai-node/CLAUDE.md +0 -0
- {stackone_ai-2.5.0 → stackone_ai-2.6.0}/vendor/stackone-ai-node/LICENSE +0 -0
- {stackone_ai-2.5.0 → stackone_ai-2.6.0}/vendor/stackone-ai-node/README.md +0 -0
- {stackone_ai-2.5.0 → stackone_ai-2.6.0}/vendor/stackone-ai-node/examples/README.md +0 -0
- {stackone_ai-2.5.0 → stackone_ai-2.6.0}/vendor/stackone-ai-node/examples/ai-sdk-integration.test.ts +0 -0
- {stackone_ai-2.5.0 → stackone_ai-2.6.0}/vendor/stackone-ai-node/examples/ai-sdk-integration.ts +0 -0
- {stackone_ai-2.5.0 → stackone_ai-2.6.0}/vendor/stackone-ai-node/examples/anthropic-integration.ts +0 -0
- {stackone_ai-2.5.0 → stackone_ai-2.6.0}/vendor/stackone-ai-node/examples/claude-agent-sdk-integration.test.ts +0 -0
- {stackone_ai-2.5.0 → stackone_ai-2.6.0}/vendor/stackone-ai-node/examples/claude-agent-sdk-integration.ts +0 -0
- {stackone_ai-2.5.0 → stackone_ai-2.6.0}/vendor/stackone-ai-node/examples/fetch-tools-debug.ts +0 -0
- {stackone_ai-2.5.0 → stackone_ai-2.6.0}/vendor/stackone-ai-node/examples/fetch-tools.test.ts +0 -0
- {stackone_ai-2.5.0 → stackone_ai-2.6.0}/vendor/stackone-ai-node/examples/fetch-tools.ts +0 -0
- {stackone_ai-2.5.0 → stackone_ai-2.6.0}/vendor/stackone-ai-node/examples/meta-tools.ts +0 -0
- {stackone_ai-2.5.0 → stackone_ai-2.6.0}/vendor/stackone-ai-node/examples/openai-integration.test.ts +0 -0
- {stackone_ai-2.5.0 → stackone_ai-2.6.0}/vendor/stackone-ai-node/examples/openai-integration.ts +0 -0
- {stackone_ai-2.5.0 → stackone_ai-2.6.0}/vendor/stackone-ai-node/examples/openai-responses-integration.test.ts +0 -0
- {stackone_ai-2.5.0 → stackone_ai-2.6.0}/vendor/stackone-ai-node/examples/openai-responses-integration.ts +0 -0
- {stackone_ai-2.5.0 → stackone_ai-2.6.0}/vendor/stackone-ai-node/examples/package.json +0 -0
- {stackone_ai-2.5.0 → stackone_ai-2.6.0}/vendor/stackone-ai-node/examples/tanstack-ai-integration.test.ts +0 -0
- {stackone_ai-2.5.0 → stackone_ai-2.6.0}/vendor/stackone-ai-node/examples/tanstack-ai-integration.ts +0 -0
- {stackone_ai-2.5.0 → stackone_ai-2.6.0}/vendor/stackone-ai-node/examples/tsconfig.json +0 -0
- {stackone_ai-2.5.0 → stackone_ai-2.6.0}/vendor/stackone-ai-node/flake.lock +0 -0
- {stackone_ai-2.5.0 → stackone_ai-2.6.0}/vendor/stackone-ai-node/flake.nix +0 -0
- {stackone_ai-2.5.0 → stackone_ai-2.6.0}/vendor/stackone-ai-node/knip.config.ts +0 -0
- {stackone_ai-2.5.0 → stackone_ai-2.6.0}/vendor/stackone-ai-node/lefthook.yaml +0 -0
- {stackone_ai-2.5.0 → stackone_ai-2.6.0}/vendor/stackone-ai-node/mocks/handlers.example-api.ts +0 -0
- {stackone_ai-2.5.0 → stackone_ai-2.6.0}/vendor/stackone-ai-node/mocks/handlers.mcp.ts +0 -0
- {stackone_ai-2.5.0 → stackone_ai-2.6.0}/vendor/stackone-ai-node/mocks/handlers.openai.ts +0 -0
- {stackone_ai-2.5.0 → stackone_ai-2.6.0}/vendor/stackone-ai-node/mocks/handlers.stackone-ai.ts +0 -0
- {stackone_ai-2.5.0 → stackone_ai-2.6.0}/vendor/stackone-ai-node/mocks/handlers.stackone-rpc.ts +0 -0
- {stackone_ai-2.5.0 → stackone_ai-2.6.0}/vendor/stackone-ai-node/mocks/handlers.ts +0 -0
- {stackone_ai-2.5.0 → stackone_ai-2.6.0}/vendor/stackone-ai-node/mocks/handlers.utils.ts +0 -0
- {stackone_ai-2.5.0 → stackone_ai-2.6.0}/vendor/stackone-ai-node/mocks/mcp-server.ts +0 -0
- {stackone_ai-2.5.0 → stackone_ai-2.6.0}/vendor/stackone-ai-node/mocks/node.ts +0 -0
- {stackone_ai-2.5.0 → stackone_ai-2.6.0}/vendor/stackone-ai-node/package.json +0 -0
- {stackone_ai-2.5.0 → stackone_ai-2.6.0}/vendor/stackone-ai-node/pnpm-lock.yaml +0 -0
- {stackone_ai-2.5.0 → stackone_ai-2.6.0}/vendor/stackone-ai-node/pnpm-workspace.yaml +0 -0
- {stackone_ai-2.5.0 → stackone_ai-2.6.0}/vendor/stackone-ai-node/src/consts.ts +0 -0
- {stackone_ai-2.5.0 → stackone_ai-2.6.0}/vendor/stackone-ai-node/src/feedback.test.ts +0 -0
- {stackone_ai-2.5.0 → stackone_ai-2.6.0}/vendor/stackone-ai-node/src/feedback.ts +0 -0
- {stackone_ai-2.5.0 → stackone_ai-2.6.0}/vendor/stackone-ai-node/src/headers.test.ts +0 -0
- {stackone_ai-2.5.0 → stackone_ai-2.6.0}/vendor/stackone-ai-node/src/headers.ts +0 -0
- {stackone_ai-2.5.0 → stackone_ai-2.6.0}/vendor/stackone-ai-node/src/index.ts +0 -0
- {stackone_ai-2.5.0 → stackone_ai-2.6.0}/vendor/stackone-ai-node/src/mcp-client.test.ts +0 -0
- {stackone_ai-2.5.0 → stackone_ai-2.6.0}/vendor/stackone-ai-node/src/mcp-client.ts +0 -0
- {stackone_ai-2.5.0 → stackone_ai-2.6.0}/vendor/stackone-ai-node/src/requestBuilder.test.ts +0 -0
- {stackone_ai-2.5.0 → stackone_ai-2.6.0}/vendor/stackone-ai-node/src/requestBuilder.ts +0 -0
- {stackone_ai-2.5.0 → stackone_ai-2.6.0}/vendor/stackone-ai-node/src/rpc-client.test.ts +0 -0
- {stackone_ai-2.5.0 → stackone_ai-2.6.0}/vendor/stackone-ai-node/src/rpc-client.ts +0 -0
- {stackone_ai-2.5.0 → stackone_ai-2.6.0}/vendor/stackone-ai-node/src/schema.ts +0 -0
- {stackone_ai-2.5.0 → stackone_ai-2.6.0}/vendor/stackone-ai-node/src/tool.test-d.ts +0 -0
- {stackone_ai-2.5.0 → stackone_ai-2.6.0}/vendor/stackone-ai-node/src/tool.test.ts +0 -0
- {stackone_ai-2.5.0 → stackone_ai-2.6.0}/vendor/stackone-ai-node/src/tool.ts +0 -0
- {stackone_ai-2.5.0 → stackone_ai-2.6.0}/vendor/stackone-ai-node/src/toolsets.test-d.ts +0 -0
- {stackone_ai-2.5.0 → stackone_ai-2.6.0}/vendor/stackone-ai-node/src/toolsets.test.ts +0 -0
- {stackone_ai-2.5.0 → stackone_ai-2.6.0}/vendor/stackone-ai-node/src/toolsets.ts +0 -0
- {stackone_ai-2.5.0 → stackone_ai-2.6.0}/vendor/stackone-ai-node/src/types.ts +0 -0
- {stackone_ai-2.5.0 → stackone_ai-2.6.0}/vendor/stackone-ai-node/src/utils/array.test.ts +0 -0
- {stackone_ai-2.5.0 → stackone_ai-2.6.0}/vendor/stackone-ai-node/src/utils/array.ts +0 -0
- {stackone_ai-2.5.0 → stackone_ai-2.6.0}/vendor/stackone-ai-node/src/utils/error-stackone-api.test.ts +0 -0
- {stackone_ai-2.5.0 → stackone_ai-2.6.0}/vendor/stackone-ai-node/src/utils/error-stackone-api.ts +0 -0
- {stackone_ai-2.5.0 → stackone_ai-2.6.0}/vendor/stackone-ai-node/src/utils/error-stackone.test.ts +0 -0
- {stackone_ai-2.5.0 → stackone_ai-2.6.0}/vendor/stackone-ai-node/src/utils/error-stackone.ts +0 -0
- {stackone_ai-2.5.0 → stackone_ai-2.6.0}/vendor/stackone-ai-node/src/utils/tfidf-index.test.ts +0 -0
- {stackone_ai-2.5.0 → stackone_ai-2.6.0}/vendor/stackone-ai-node/src/utils/tfidf-index.ts +0 -0
- {stackone_ai-2.5.0 → stackone_ai-2.6.0}/vendor/stackone-ai-node/src/utils/try-import.test.ts +0 -0
- {stackone_ai-2.5.0 → stackone_ai-2.6.0}/vendor/stackone-ai-node/src/utils/try-import.ts +0 -0
- {stackone_ai-2.5.0 → stackone_ai-2.6.0}/vendor/stackone-ai-node/src/utils/type.ts +0 -0
- {stackone_ai-2.5.0 → stackone_ai-2.6.0}/vendor/stackone-ai-node/tsconfig.json +0 -0
- {stackone_ai-2.5.0 → stackone_ai-2.6.0}/vendor/stackone-ai-node/tsdown.config.ts +0 -0
- {stackone_ai-2.5.0 → stackone_ai-2.6.0}/vendor/stackone-ai-node/vitest.config.ts +0 -0
- {stackone_ai-2.5.0 → stackone_ai-2.6.0}/vendor/stackone-ai-node/vitest.setup.ts +0 -0
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [2.6.0](https://github.com/StackOneHQ/stackone-ai-python/compare/stackone-ai-v2.5.1...stackone-ai-v2.6.0) (2026-04-07)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
|
|
8
|
+
* include available connectors in search/execute tool descriptions ([#165](https://github.com/StackOneHQ/stackone-ai-python/issues/165)) ([544f41e](https://github.com/StackOneHQ/stackone-ai-python/commit/544f41ef1340d11f58be1a34e627aa8e81f1102d))
|
|
9
|
+
|
|
10
|
+
## [2.5.1](https://github.com/StackOneHQ/stackone-ai-python/compare/stackone-ai-v2.5.0...stackone-ai-v2.5.1) (2026-03-26)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
### Bug Fixes
|
|
14
|
+
|
|
15
|
+
* **search:** fall back to local search when semantic results don't match MCP tools ([#159](https://github.com/StackOneHQ/stackone-ai-python/issues/159)) ([2c86475](https://github.com/StackOneHQ/stackone-ai-python/commit/2c864759f43dc701d1bfa8407badf4a10f608332))
|
|
16
|
+
|
|
3
17
|
## [2.5.0](https://github.com/StackOneHQ/stackone-ai-python/compare/stackone-ai-v2.4.0...stackone-ai-v2.5.0) (2026-03-25)
|
|
4
18
|
|
|
5
19
|
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"""Search tool patterns: callable wrapper and config overrides.
|
|
2
|
+
|
|
3
|
+
For semantic search basics, see semantic_search_example.py.
|
|
4
|
+
For full agent execution, see agent_tool_search.py.
|
|
5
|
+
|
|
6
|
+
Prerequisites:
|
|
7
|
+
- STACKONE_API_KEY environment variable
|
|
8
|
+
- STACKONE_ACCOUNT_ID environment variable
|
|
9
|
+
|
|
10
|
+
Run with:
|
|
11
|
+
uv run python examples/search_tool_example.py
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import os
|
|
17
|
+
|
|
18
|
+
try:
|
|
19
|
+
from dotenv import load_dotenv
|
|
20
|
+
|
|
21
|
+
load_dotenv()
|
|
22
|
+
except ModuleNotFoundError:
|
|
23
|
+
pass
|
|
24
|
+
|
|
25
|
+
from stackone_ai import StackOneToolSet
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def main() -> None:
|
|
29
|
+
api_key = os.getenv("STACKONE_API_KEY")
|
|
30
|
+
account_id = os.getenv("STACKONE_ACCOUNT_ID")
|
|
31
|
+
|
|
32
|
+
if not api_key:
|
|
33
|
+
print("Set STACKONE_API_KEY to run this example.")
|
|
34
|
+
return
|
|
35
|
+
if not account_id:
|
|
36
|
+
print("Set STACKONE_ACCOUNT_ID to run this example.")
|
|
37
|
+
return
|
|
38
|
+
|
|
39
|
+
# --- Example 1: get_search_tool() callable ---
|
|
40
|
+
print("=== get_search_tool() callable ===\n")
|
|
41
|
+
|
|
42
|
+
toolset = StackOneToolSet(api_key=api_key, account_id=account_id, search={})
|
|
43
|
+
search_tool = toolset.get_search_tool()
|
|
44
|
+
|
|
45
|
+
queries = ["cancel an event", "list employees", "send a message"]
|
|
46
|
+
for query in queries:
|
|
47
|
+
tools = search_tool(query, top_k=3)
|
|
48
|
+
names = [t.name for t in tools]
|
|
49
|
+
print(f' "{query}" -> {", ".join(names) or "(none)"}')
|
|
50
|
+
|
|
51
|
+
# --- Example 2: Constructor top_k vs per-call override ---
|
|
52
|
+
print("\n=== Constructor top_k vs per-call override ===\n")
|
|
53
|
+
|
|
54
|
+
toolset_3 = StackOneToolSet(api_key=api_key, account_id=account_id, search={"top_k": 3})
|
|
55
|
+
|
|
56
|
+
query = "manage employee records"
|
|
57
|
+
|
|
58
|
+
tools_3 = toolset_3.search_tools(query)
|
|
59
|
+
print(f"Constructor top_k=3: got {len(tools_3)} tools")
|
|
60
|
+
|
|
61
|
+
tools_override = toolset_3.search_tools(query, top_k=10)
|
|
62
|
+
print(f"Per-call top_k=10 (overrides constructor 3): got {len(tools_override)} tools")
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
if __name__ == "__main__":
|
|
66
|
+
main()
|
|
@@ -133,7 +133,6 @@ def example_search_action_names():
|
|
|
133
133
|
print(f"Top {len(results_limited)} matches from the full catalog:")
|
|
134
134
|
for r in results_limited:
|
|
135
135
|
print(f" [{r.similarity_score:.2f}] {r.id}")
|
|
136
|
-
print(f" {r.description}")
|
|
137
136
|
print()
|
|
138
137
|
|
|
139
138
|
# Show filtering effect when account_ids are available
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"""Workday integration: timeout and account scoping for slow providers.
|
|
2
|
+
|
|
3
|
+
Workday can take 10-15s to respond. This example shows how to configure
|
|
4
|
+
timeout and account_ids through the execute config.
|
|
5
|
+
|
|
6
|
+
Prerequisites:
|
|
7
|
+
- STACKONE_API_KEY environment variable
|
|
8
|
+
- STACKONE_ACCOUNT_ID environment variable (a Workday-connected account)
|
|
9
|
+
- OPENAI_API_KEY environment variable
|
|
10
|
+
|
|
11
|
+
Run with:
|
|
12
|
+
uv run python examples/workday_integration.py
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import json
|
|
18
|
+
import os
|
|
19
|
+
|
|
20
|
+
try:
|
|
21
|
+
from dotenv import load_dotenv
|
|
22
|
+
|
|
23
|
+
load_dotenv()
|
|
24
|
+
except ModuleNotFoundError:
|
|
25
|
+
pass
|
|
26
|
+
|
|
27
|
+
from openai import OpenAI
|
|
28
|
+
|
|
29
|
+
from stackone_ai import StackOneToolSet
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def main() -> None:
|
|
33
|
+
api_key = os.getenv("STACKONE_API_KEY")
|
|
34
|
+
account_id = os.getenv("STACKONE_ACCOUNT_ID")
|
|
35
|
+
|
|
36
|
+
if not api_key:
|
|
37
|
+
print("Set STACKONE_API_KEY to run this example.")
|
|
38
|
+
return
|
|
39
|
+
if not account_id:
|
|
40
|
+
print("Set STACKONE_ACCOUNT_ID to run this example.")
|
|
41
|
+
return
|
|
42
|
+
|
|
43
|
+
# Timeout for slow providers, account_id for scoping
|
|
44
|
+
toolset = StackOneToolSet(
|
|
45
|
+
api_key=api_key,
|
|
46
|
+
account_id=account_id,
|
|
47
|
+
search={"method": "auto", "top_k": 5},
|
|
48
|
+
timeout=120,
|
|
49
|
+
)
|
|
50
|
+
client = OpenAI()
|
|
51
|
+
|
|
52
|
+
def run_agent(messages: list[dict], tools: list[dict], max_steps: int = 10) -> None:
|
|
53
|
+
"""Simple agent loop: call LLM, execute tools, repeat."""
|
|
54
|
+
for _ in range(max_steps):
|
|
55
|
+
response = client.chat.completions.create(model="gpt-5.4", messages=messages, tools=tools)
|
|
56
|
+
choice = response.choices[0]
|
|
57
|
+
|
|
58
|
+
if not choice.message.tool_calls:
|
|
59
|
+
print(f"Answer: {choice.message.content}")
|
|
60
|
+
return
|
|
61
|
+
|
|
62
|
+
messages.append(choice.message.model_dump(exclude_none=True))
|
|
63
|
+
for tc in choice.message.tool_calls:
|
|
64
|
+
print(f" -> {tc.function.name}({tc.function.arguments[:80]})")
|
|
65
|
+
result = toolset.execute(tc.function.name, tc.function.arguments)
|
|
66
|
+
messages.append({"role": "tool", "tool_call_id": tc.id, "content": json.dumps(result)})
|
|
67
|
+
|
|
68
|
+
# --- Example 1: Search and execute mode ---
|
|
69
|
+
print("=== Search and execute mode ===\n")
|
|
70
|
+
run_agent(
|
|
71
|
+
messages=[
|
|
72
|
+
{"role": "system", "content": "Use tool_search to find tools, then tool_execute to run them."},
|
|
73
|
+
{"role": "user", "content": "List the first 5 employees."},
|
|
74
|
+
],
|
|
75
|
+
tools=toolset.openai(mode="search_and_execute"),
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
# --- Example 2: Normal mode ---
|
|
79
|
+
print("\n=== Normal mode ===\n")
|
|
80
|
+
tools = toolset.fetch_tools(actions=["workday_*_employee*"])
|
|
81
|
+
if len(tools) == 0:
|
|
82
|
+
print("No Workday tools found for this account.")
|
|
83
|
+
else:
|
|
84
|
+
run_agent(
|
|
85
|
+
messages=[{"role": "user", "content": "List the first 5 employees."}],
|
|
86
|
+
tools=tools.to_openai(),
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
if __name__ == "__main__":
|
|
91
|
+
main()
|
|
@@ -65,6 +65,7 @@ class ExecuteConfig(BaseModel):
|
|
|
65
65
|
parameter_locations: dict[str, ParameterLocation] = Field(
|
|
66
66
|
default_factory=dict, description="Maps parameter names to their location in the request"
|
|
67
67
|
)
|
|
68
|
+
timeout: float = Field(default=60.0, description="Request timeout in seconds")
|
|
68
69
|
|
|
69
70
|
|
|
70
71
|
class ToolParameters(BaseModel):
|
|
@@ -249,7 +250,7 @@ class StackOneTool(BaseModel):
|
|
|
249
250
|
if query_params:
|
|
250
251
|
request_kwargs["params"] = query_params
|
|
251
252
|
|
|
252
|
-
response = httpx.request(**request_kwargs)
|
|
253
|
+
response = httpx.request(**request_kwargs, timeout=self._execute_config.timeout)
|
|
253
254
|
response_status = response.status_code
|
|
254
255
|
response.raise_for_status()
|
|
255
256
|
|
|
@@ -59,7 +59,7 @@ class SearchConfig(TypedDict, total=False):
|
|
|
59
59
|
class ExecuteToolsConfig(TypedDict, total=False):
|
|
60
60
|
"""Execution configuration for the StackOneToolSet constructor.
|
|
61
61
|
|
|
62
|
-
Controls default account scoping for tool execution.
|
|
62
|
+
Controls default account scoping and timeout for tool execution.
|
|
63
63
|
|
|
64
64
|
When set to ``None`` (default), no account scoping is applied.
|
|
65
65
|
When provided, ``account_ids`` flow through to ``openai(mode="search_and_execute")``
|
|
@@ -69,6 +69,10 @@ class ExecuteToolsConfig(TypedDict, total=False):
|
|
|
69
69
|
account_ids: list[str]
|
|
70
70
|
"""Account IDs to scope tool discovery and execution."""
|
|
71
71
|
|
|
72
|
+
timeout: float
|
|
73
|
+
"""Request timeout in seconds. Default: 60. Can also be set as a top-level
|
|
74
|
+
constructor param which takes precedence."""
|
|
75
|
+
|
|
72
76
|
|
|
73
77
|
_SEARCH_DEFAULT: SearchConfig = {"method": "auto"}
|
|
74
78
|
|
|
@@ -205,13 +209,14 @@ class _ExecuteTool(StackOneTool):
|
|
|
205
209
|
return {"error": f"Invalid input: {exc}", "tool_name": tool_name}
|
|
206
210
|
|
|
207
211
|
|
|
208
|
-
def _create_search_tool(api_key: str) -> _SearchTool:
|
|
212
|
+
def _create_search_tool(api_key: str, connectors: str = "") -> _SearchTool:
|
|
209
213
|
name = "tool_search"
|
|
214
|
+
connector_line = f" Available connectors: {connectors}." if connectors else ""
|
|
210
215
|
description = (
|
|
211
216
|
"Search for available tools by describing what you need. "
|
|
212
217
|
"Returns matching tool names, descriptions, and parameter schemas. "
|
|
213
218
|
"Use the returned parameter schemas to know exactly what to pass "
|
|
214
|
-
"when calling tool_execute."
|
|
219
|
+
f"when calling tool_execute.{connector_line}"
|
|
215
220
|
)
|
|
216
221
|
parameters = ToolParameters(
|
|
217
222
|
type="object",
|
|
@@ -259,14 +264,14 @@ def _create_search_tool(api_key: str) -> _SearchTool:
|
|
|
259
264
|
return tool
|
|
260
265
|
|
|
261
266
|
|
|
262
|
-
def _create_execute_tool(api_key: str) -> _ExecuteTool:
|
|
267
|
+
def _create_execute_tool(api_key: str, connectors: str = "") -> _ExecuteTool:
|
|
263
268
|
name = "tool_execute"
|
|
269
|
+
connector_line = f" Available connectors: {connectors}." if connectors else ""
|
|
264
270
|
description = (
|
|
265
271
|
"Execute a tool by name with the given parameters. "
|
|
266
272
|
"Use tool_search first to find available tools. "
|
|
267
273
|
"The parameters field must match the parameter schema returned "
|
|
268
|
-
"by tool_search. Pass parameters as a nested object matching "
|
|
269
|
-
"the schema structure."
|
|
274
|
+
f"by tool_search. Pass parameters as a nested object matching the schema structure.{connector_line}"
|
|
270
275
|
)
|
|
271
276
|
parameters = ToolParameters(
|
|
272
277
|
type="object",
|
|
@@ -415,6 +420,7 @@ class _StackOneRpcTool(StackOneTool):
|
|
|
415
420
|
api_key: str,
|
|
416
421
|
base_url: str,
|
|
417
422
|
account_id: str | None,
|
|
423
|
+
timeout: float = 60.0,
|
|
418
424
|
) -> None:
|
|
419
425
|
execute_config = ExecuteConfig(
|
|
420
426
|
method="POST",
|
|
@@ -423,6 +429,7 @@ class _StackOneRpcTool(StackOneTool):
|
|
|
423
429
|
headers={},
|
|
424
430
|
body_type="json",
|
|
425
431
|
parameter_locations=dict(_RPC_PARAMETER_LOCATIONS),
|
|
432
|
+
timeout=timeout,
|
|
426
433
|
)
|
|
427
434
|
super().__init__(
|
|
428
435
|
description=description,
|
|
@@ -555,6 +562,7 @@ class StackOneToolSet:
|
|
|
555
562
|
base_url: str | None = None,
|
|
556
563
|
search: SearchConfig | None = None,
|
|
557
564
|
execute: ExecuteToolsConfig | None = None,
|
|
565
|
+
timeout: float | None = None,
|
|
558
566
|
) -> None:
|
|
559
567
|
"""Initialize StackOne tools with authentication
|
|
560
568
|
|
|
@@ -570,7 +578,10 @@ class StackOneToolSet:
|
|
|
570
578
|
Per-call options always override these defaults.
|
|
571
579
|
execute: Execution configuration. Controls default account scoping
|
|
572
580
|
for tool execution. Pass ``{"account_ids": ["acc-1"]}`` to scope
|
|
573
|
-
|
|
581
|
+
tools to specific accounts.
|
|
582
|
+
timeout: Request timeout in seconds for tool execution HTTP calls.
|
|
583
|
+
Default: 60. Takes precedence over ``execute.timeout`` if set.
|
|
584
|
+
Increase for slow providers (e.g. Workday).
|
|
574
585
|
|
|
575
586
|
Raises:
|
|
576
587
|
ToolsetConfigError: If no API key is provided or found in environment
|
|
@@ -584,10 +595,12 @@ class StackOneToolSet:
|
|
|
584
595
|
self.api_key: str = api_key_value
|
|
585
596
|
self.account_id = account_id
|
|
586
597
|
self.base_url = base_url or DEFAULT_BASE_URL
|
|
587
|
-
self._account_ids: list[str] = []
|
|
598
|
+
self._account_ids: list[str] = execute.get("account_ids", []) if execute else []
|
|
588
599
|
self._semantic_client: SemanticSearchClient | None = None
|
|
589
600
|
self._search_config: SearchConfig | None = search
|
|
590
601
|
self._execute_config: ExecuteToolsConfig | None = execute
|
|
602
|
+
execute_timeout = execute.get("timeout") if execute else None
|
|
603
|
+
self._timeout: float = timeout if timeout is not None else (execute_timeout or 60.0)
|
|
591
604
|
self._tools_cache: Tools | None = None
|
|
592
605
|
|
|
593
606
|
def set_accounts(self, account_ids: list[str]) -> StackOneToolSet:
|
|
@@ -645,10 +658,20 @@ class StackOneToolSet:
|
|
|
645
658
|
if account_ids:
|
|
646
659
|
self._account_ids = account_ids
|
|
647
660
|
|
|
648
|
-
|
|
661
|
+
# Discover available connectors for dynamic descriptions
|
|
662
|
+
connectors_str = ""
|
|
663
|
+
try:
|
|
664
|
+
all_tools = self.fetch_tools(account_ids=self._account_ids)
|
|
665
|
+
connectors = sorted(all_tools.get_connectors())
|
|
666
|
+
if connectors:
|
|
667
|
+
connectors_str = ", ".join(connectors)
|
|
668
|
+
except Exception:
|
|
669
|
+
logger.debug("Could not discover connectors for tool descriptions")
|
|
670
|
+
|
|
671
|
+
search_tool = _create_search_tool(self.api_key, connectors=connectors_str)
|
|
649
672
|
search_tool._toolset = self
|
|
650
673
|
|
|
651
|
-
execute_tool = _create_execute_tool(self.api_key)
|
|
674
|
+
execute_tool = _create_execute_tool(self.api_key, connectors=connectors_str)
|
|
652
675
|
execute_tool._toolset = self
|
|
653
676
|
|
|
654
677
|
return Tools([search_tool, execute_tool])
|
|
@@ -933,6 +956,21 @@ class StackOneToolSet:
|
|
|
933
956
|
matched_tools = [t for t in all_tools if t.name in seen_names]
|
|
934
957
|
matched_tools.sort(key=lambda t: action_order.get(t.name, float("inf")))
|
|
935
958
|
|
|
959
|
+
# Auto mode: if semantic returned results but none matched MCP tools, fall back to local
|
|
960
|
+
if effective_search == "auto" and len(matched_tools) == 0:
|
|
961
|
+
logger.warning(
|
|
962
|
+
"Semantic search returned %d results but none matched MCP tools, "
|
|
963
|
+
"falling back to local search",
|
|
964
|
+
len(all_results),
|
|
965
|
+
)
|
|
966
|
+
return self._local_search(
|
|
967
|
+
query,
|
|
968
|
+
all_tools,
|
|
969
|
+
connector=connector,
|
|
970
|
+
top_k=effective_top_k,
|
|
971
|
+
min_similarity=effective_min_sim,
|
|
972
|
+
)
|
|
973
|
+
|
|
936
974
|
return Tools(matched_tools)
|
|
937
975
|
|
|
938
976
|
except SemanticSearchError as e:
|
|
@@ -1177,6 +1215,7 @@ class StackOneToolSet:
|
|
|
1177
1215
|
api_key=self.api_key,
|
|
1178
1216
|
base_url=self.base_url,
|
|
1179
1217
|
account_id=account_id,
|
|
1218
|
+
timeout=self._timeout,
|
|
1180
1219
|
)
|
|
1181
1220
|
|
|
1182
1221
|
def _normalize_schema_properties(self, schema: dict[str, Any]) -> dict[str, Any]:
|
|
@@ -959,3 +959,81 @@ class TestSemanticSearchDeduplication:
|
|
|
959
959
|
assert results[0].id == "breathehr_1.0.0_breathehr_list_employees_global"
|
|
960
960
|
# Sorted by score descending
|
|
961
961
|
assert results[0].similarity_score == 0.95
|
|
962
|
+
|
|
963
|
+
|
|
964
|
+
class TestZeroMatchFallback:
|
|
965
|
+
"""Tests for fallback when semantic results don't match MCP tools."""
|
|
966
|
+
|
|
967
|
+
@patch.object(SemanticSearchClient, "search")
|
|
968
|
+
@patch("stackone_ai.toolset._fetch_mcp_tools")
|
|
969
|
+
def test_auto_mode_falls_back_when_no_tools_match(
|
|
970
|
+
self,
|
|
971
|
+
mock_fetch: MagicMock,
|
|
972
|
+
mock_search: MagicMock,
|
|
973
|
+
) -> None:
|
|
974
|
+
"""Auto mode falls back to local search when semantic results don't match any MCP tools."""
|
|
975
|
+
from stackone_ai import StackOneToolSet
|
|
976
|
+
from stackone_ai.toolset import _McpToolDefinition
|
|
977
|
+
|
|
978
|
+
# Semantic returns results with IDs that won't match MCP tool names
|
|
979
|
+
mock_search.return_value = SemanticSearchResponse(
|
|
980
|
+
results=[
|
|
981
|
+
SemanticSearchResult(
|
|
982
|
+
id="unknown_1.0.0_nonexistent_action_global",
|
|
983
|
+
similarity_score=0.95,
|
|
984
|
+
),
|
|
985
|
+
],
|
|
986
|
+
total_count=1,
|
|
987
|
+
query="manage employees",
|
|
988
|
+
)
|
|
989
|
+
|
|
990
|
+
mock_fetch.return_value = [
|
|
991
|
+
_McpToolDefinition(
|
|
992
|
+
name="bamboohr_create_employee",
|
|
993
|
+
description="Creates a new employee in BambooHR",
|
|
994
|
+
input_schema={"type": "object", "properties": {}},
|
|
995
|
+
),
|
|
996
|
+
]
|
|
997
|
+
|
|
998
|
+
toolset = StackOneToolSet(api_key="test-key", search={"method": "auto"})
|
|
999
|
+
tools = toolset.search_tools("manage employees", top_k=5)
|
|
1000
|
+
|
|
1001
|
+
# Should fall back to local search and return results (not empty)
|
|
1002
|
+
assert len(tools) > 0
|
|
1003
|
+
|
|
1004
|
+
@patch.object(SemanticSearchClient, "search")
|
|
1005
|
+
@patch("stackone_ai.toolset._fetch_mcp_tools")
|
|
1006
|
+
def test_semantic_mode_does_not_fall_back(
|
|
1007
|
+
self,
|
|
1008
|
+
mock_fetch: MagicMock,
|
|
1009
|
+
mock_search: MagicMock,
|
|
1010
|
+
) -> None:
|
|
1011
|
+
"""Semantic mode returns empty results when no tools match, does not fall back."""
|
|
1012
|
+
from stackone_ai import StackOneToolSet
|
|
1013
|
+
from stackone_ai.toolset import _McpToolDefinition
|
|
1014
|
+
|
|
1015
|
+
# Semantic returns results with IDs that won't match MCP tool names
|
|
1016
|
+
mock_search.return_value = SemanticSearchResponse(
|
|
1017
|
+
results=[
|
|
1018
|
+
SemanticSearchResult(
|
|
1019
|
+
id="unknown_1.0.0_nonexistent_action_global",
|
|
1020
|
+
similarity_score=0.95,
|
|
1021
|
+
),
|
|
1022
|
+
],
|
|
1023
|
+
total_count=1,
|
|
1024
|
+
query="manage employees",
|
|
1025
|
+
)
|
|
1026
|
+
|
|
1027
|
+
mock_fetch.return_value = [
|
|
1028
|
+
_McpToolDefinition(
|
|
1029
|
+
name="bamboohr_create_employee",
|
|
1030
|
+
description="Creates a new employee in BambooHR",
|
|
1031
|
+
input_schema={"type": "object", "properties": {}},
|
|
1032
|
+
),
|
|
1033
|
+
]
|
|
1034
|
+
|
|
1035
|
+
toolset = StackOneToolSet(api_key="test-key", search={"method": "auto"})
|
|
1036
|
+
tools = toolset.search_tools("manage employees", search="semantic", top_k=5)
|
|
1037
|
+
|
|
1038
|
+
# Semantic mode should return empty, not fall back
|
|
1039
|
+
assert len(tools) == 0
|