stackone-ai 2.4.0__tar.gz → 2.5.1__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.
Files changed (178) hide show
  1. stackone_ai-2.5.1/.release-please-manifest.json +3 -0
  2. {stackone_ai-2.4.0 → stackone_ai-2.5.1}/CHANGELOG.md +14 -0
  3. {stackone_ai-2.4.0 → stackone_ai-2.5.1}/PKG-INFO +1 -1
  4. stackone_ai-2.5.1/examples/agent_tool_search.py +202 -0
  5. {stackone_ai-2.4.0 → stackone_ai-2.5.1}/examples/crewai_integration.py +1 -1
  6. {stackone_ai-2.4.0 → stackone_ai-2.5.1}/examples/langchain_integration.py +1 -1
  7. {stackone_ai-2.4.0 → stackone_ai-2.5.1}/examples/openai_integration.py +2 -2
  8. {stackone_ai-2.4.0 → stackone_ai-2.5.1}/examples/search_tool_example.py +2 -2
  9. {stackone_ai-2.4.0 → stackone_ai-2.5.1}/examples/semantic_search_example.py +7 -7
  10. {stackone_ai-2.4.0 → stackone_ai-2.5.1}/pyproject.toml +1 -1
  11. {stackone_ai-2.4.0 → stackone_ai-2.5.1}/stackone_ai/__init__.py +3 -2
  12. {stackone_ai-2.4.0 → stackone_ai-2.5.1}/stackone_ai/models.py +14 -2
  13. {stackone_ai-2.4.0 → stackone_ai-2.5.1}/stackone_ai/semantic_search.py +14 -22
  14. {stackone_ai-2.4.0 → stackone_ai-2.5.1}/stackone_ai/toolset.py +394 -27
  15. stackone_ai-2.5.1/tests/test_agent_tools.py +536 -0
  16. {stackone_ai-2.4.0 → stackone_ai-2.5.1}/tests/test_semantic_search.py +156 -131
  17. {stackone_ai-2.4.0 → stackone_ai-2.5.1}/uv.lock +1 -1
  18. stackone_ai-2.4.0/.release-please-manifest.json +0 -3
  19. {stackone_ai-2.4.0 → stackone_ai-2.5.1}/.claude/rules/development-workflow.md +0 -0
  20. {stackone_ai-2.4.0 → stackone_ai-2.5.1}/.claude/rules/examples-standards.md +0 -0
  21. {stackone_ai-2.4.0 → stackone_ai-2.5.1}/.claude/rules/git-workflow.md +0 -0
  22. {stackone_ai-2.4.0 → stackone_ai-2.5.1}/.claude/rules/nix-workflow.md +0 -0
  23. {stackone_ai-2.4.0 → stackone_ai-2.5.1}/.claude/rules/no-relative-imports.md +0 -0
  24. {stackone_ai-2.4.0 → stackone_ai-2.5.1}/.claude/rules/package-installation.md +0 -0
  25. {stackone_ai-2.4.0 → stackone_ai-2.5.1}/.claude/rules/release-please-standards.md +0 -0
  26. {stackone_ai-2.4.0 → stackone_ai-2.5.1}/.claude/rules/uv-scripts.md +0 -0
  27. {stackone_ai-2.4.0 → stackone_ai-2.5.1}/.cursor/rules/development-workflow.mdc +0 -0
  28. {stackone_ai-2.4.0 → stackone_ai-2.5.1}/.cursor/rules/examples-standards.mdc +0 -0
  29. {stackone_ai-2.4.0 → stackone_ai-2.5.1}/.cursor/rules/git-workflow.mdc +0 -0
  30. {stackone_ai-2.4.0 → stackone_ai-2.5.1}/.cursor/rules/no-relative-imports.mdc +0 -0
  31. {stackone_ai-2.4.0 → stackone_ai-2.5.1}/.cursor/rules/package-installation.mdc +0 -0
  32. {stackone_ai-2.4.0 → stackone_ai-2.5.1}/.cursor/rules/release-please-standards.mdc +0 -0
  33. {stackone_ai-2.4.0 → stackone_ai-2.5.1}/.cursor/rules/uv-scripts.mdc +0 -0
  34. {stackone_ai-2.4.0 → stackone_ai-2.5.1}/.envrc +0 -0
  35. {stackone_ai-2.4.0 → stackone_ai-2.5.1}/.github/actions/setup-nix/action.yaml +0 -0
  36. {stackone_ai-2.4.0 → stackone_ai-2.5.1}/.github/dependabot.yaml +0 -0
  37. {stackone_ai-2.4.0 → stackone_ai-2.5.1}/.github/workflows/ci.yaml +0 -0
  38. {stackone_ai-2.4.0 → stackone_ai-2.5.1}/.github/workflows/nix-flake-update.yaml +0 -0
  39. {stackone_ai-2.4.0 → stackone_ai-2.5.1}/.github/workflows/nix-flake.yaml +0 -0
  40. {stackone_ai-2.4.0 → stackone_ai-2.5.1}/.github/workflows/release.yaml +0 -0
  41. {stackone_ai-2.4.0 → stackone_ai-2.5.1}/.gitignore +0 -0
  42. {stackone_ai-2.4.0 → stackone_ai-2.5.1}/.gitleaks.toml +0 -0
  43. {stackone_ai-2.4.0 → stackone_ai-2.5.1}/.gitmodules +0 -0
  44. {stackone_ai-2.4.0 → stackone_ai-2.5.1}/.mcp.json +0 -0
  45. {stackone_ai-2.4.0 → stackone_ai-2.5.1}/.release-please-config.json +0 -0
  46. {stackone_ai-2.4.0 → stackone_ai-2.5.1}/CLAUDE.md +0 -0
  47. {stackone_ai-2.4.0 → stackone_ai-2.5.1}/LICENSE +0 -0
  48. {stackone_ai-2.4.0 → stackone_ai-2.5.1}/README.md +0 -0
  49. {stackone_ai-2.4.0 → stackone_ai-2.5.1}/examples/file_uploads.py +0 -0
  50. {stackone_ai-2.4.0 → stackone_ai-2.5.1}/examples/index.py +0 -0
  51. {stackone_ai-2.4.0 → stackone_ai-2.5.1}/examples/stackone_account_ids.py +0 -0
  52. {stackone_ai-2.4.0 → stackone_ai-2.5.1}/examples/test_examples.py +0 -0
  53. {stackone_ai-2.4.0 → stackone_ai-2.5.1}/flake.lock +0 -0
  54. {stackone_ai-2.4.0 → stackone_ai-2.5.1}/flake.nix +0 -0
  55. {stackone_ai-2.4.0 → stackone_ai-2.5.1}/justfile +0 -0
  56. {stackone_ai-2.4.0 → stackone_ai-2.5.1}/stackone_ai/constants.py +0 -0
  57. {stackone_ai-2.4.0 → stackone_ai-2.5.1}/stackone_ai/feedback/__init__.py +0 -0
  58. {stackone_ai-2.4.0 → stackone_ai-2.5.1}/stackone_ai/feedback/tool.py +0 -0
  59. {stackone_ai-2.4.0 → stackone_ai-2.5.1}/stackone_ai/integrations/__init__.py +0 -0
  60. {stackone_ai-2.4.0 → stackone_ai-2.5.1}/stackone_ai/integrations/langgraph.py +0 -0
  61. {stackone_ai-2.4.0 → stackone_ai-2.5.1}/stackone_ai/local_search.py +0 -0
  62. {stackone_ai-2.4.0 → stackone_ai-2.5.1}/stackone_ai/py.typed +0 -0
  63. {stackone_ai-2.4.0 → stackone_ai-2.5.1}/stackone_ai/utils/__init__.py +0 -0
  64. {stackone_ai-2.4.0 → stackone_ai-2.5.1}/stackone_ai/utils/normalize.py +0 -0
  65. {stackone_ai-2.4.0 → stackone_ai-2.5.1}/stackone_ai/utils/tfidf_index.py +0 -0
  66. {stackone_ai-2.4.0 → stackone_ai-2.5.1}/tests/conftest.py +0 -0
  67. {stackone_ai-2.4.0 → stackone_ai-2.5.1}/tests/mocks/serve.ts +0 -0
  68. {stackone_ai-2.4.0 → stackone_ai-2.5.1}/tests/test_feedback.py +0 -0
  69. {stackone_ai-2.4.0 → stackone_ai-2.5.1}/tests/test_fetch_tools.py +0 -0
  70. {stackone_ai-2.4.0 → stackone_ai-2.5.1}/tests/test_integrations_langgraph.py +0 -0
  71. {stackone_ai-2.4.0 → stackone_ai-2.5.1}/tests/test_local_search.py +0 -0
  72. {stackone_ai-2.4.0 → stackone_ai-2.5.1}/tests/test_models.py +0 -0
  73. {stackone_ai-2.4.0 → stackone_ai-2.5.1}/tests/test_tfidf_index.py +0 -0
  74. {stackone_ai-2.4.0 → stackone_ai-2.5.1}/tests/test_tool_calling.py +0 -0
  75. {stackone_ai-2.4.0 → stackone_ai-2.5.1}/tests/test_toolset.py +0 -0
  76. {stackone_ai-2.4.0 → stackone_ai-2.5.1}/vendor/stackone-ai-node/.claude/rules/development-workflow.md +0 -0
  77. {stackone_ai-2.4.0 → stackone_ai-2.5.1}/vendor/stackone-ai-node/.claude/rules/file-operations.md +0 -0
  78. {stackone_ai-2.4.0 → stackone_ai-2.5.1}/vendor/stackone-ai-node/.claude/rules/git-workflow.md +0 -0
  79. {stackone_ai-2.4.0 → stackone_ai-2.5.1}/vendor/stackone-ai-node/.claude/rules/pnpm-usage.md +0 -0
  80. {stackone_ai-2.4.0 → stackone_ai-2.5.1}/vendor/stackone-ai-node/.claude/rules/typescript-patterns.md +0 -0
  81. {stackone_ai-2.4.0 → stackone_ai-2.5.1}/vendor/stackone-ai-node/.claude/rules/typescript-testing.md +0 -0
  82. {stackone_ai-2.4.0 → stackone_ai-2.5.1}/vendor/stackone-ai-node/.claude/skills/orama-integration/SKILL.md +0 -0
  83. {stackone_ai-2.4.0 → stackone_ai-2.5.1}/vendor/stackone-ai-node/.cursor/rules/development-workflow.mdc +0 -0
  84. {stackone_ai-2.4.0 → stackone_ai-2.5.1}/vendor/stackone-ai-node/.cursor/rules/file-operations.mdc +0 -0
  85. {stackone_ai-2.4.0 → stackone_ai-2.5.1}/vendor/stackone-ai-node/.cursor/rules/git-workflow.mdc +0 -0
  86. {stackone_ai-2.4.0 → stackone_ai-2.5.1}/vendor/stackone-ai-node/.cursor/rules/orama-integration.mdc +0 -0
  87. {stackone_ai-2.4.0 → stackone_ai-2.5.1}/vendor/stackone-ai-node/.cursor/rules/pnpm-usage.mdc +0 -0
  88. {stackone_ai-2.4.0 → stackone_ai-2.5.1}/vendor/stackone-ai-node/.cursor/rules/typescript-patterns.mdc +0 -0
  89. {stackone_ai-2.4.0 → stackone_ai-2.5.1}/vendor/stackone-ai-node/.cursor/rules/typescript-testing.mdc +0 -0
  90. {stackone_ai-2.4.0 → stackone_ai-2.5.1}/vendor/stackone-ai-node/.envrc +0 -0
  91. {stackone_ai-2.4.0 → stackone_ai-2.5.1}/vendor/stackone-ai-node/.git +0 -0
  92. {stackone_ai-2.4.0 → stackone_ai-2.5.1}/vendor/stackone-ai-node/.github/CODEOWNERS +0 -0
  93. {stackone_ai-2.4.0 → stackone_ai-2.5.1}/vendor/stackone-ai-node/.github/actions/setup-nix/action.yaml +0 -0
  94. {stackone_ai-2.4.0 → stackone_ai-2.5.1}/vendor/stackone-ai-node/.github/workflows/check-title.yaml +0 -0
  95. {stackone_ai-2.4.0 → stackone_ai-2.5.1}/vendor/stackone-ai-node/.github/workflows/ci.yaml +0 -0
  96. {stackone_ai-2.4.0 → stackone_ai-2.5.1}/vendor/stackone-ai-node/.github/workflows/claude.yaml +0 -0
  97. {stackone_ai-2.4.0 → stackone_ai-2.5.1}/vendor/stackone-ai-node/.github/workflows/dry-publish.yaml +0 -0
  98. {stackone_ai-2.4.0 → stackone_ai-2.5.1}/vendor/stackone-ai-node/.github/workflows/nix-flake.yaml +0 -0
  99. {stackone_ai-2.4.0 → stackone_ai-2.5.1}/vendor/stackone-ai-node/.github/workflows/release.yaml +0 -0
  100. {stackone_ai-2.4.0 → stackone_ai-2.5.1}/vendor/stackone-ai-node/.gitignore +0 -0
  101. {stackone_ai-2.4.0 → stackone_ai-2.5.1}/vendor/stackone-ai-node/.gitleaks.toml +0 -0
  102. {stackone_ai-2.4.0 → stackone_ai-2.5.1}/vendor/stackone-ai-node/.mcp.json +0 -0
  103. {stackone_ai-2.4.0 → stackone_ai-2.5.1}/vendor/stackone-ai-node/.oxfmtrc.jsonc +0 -0
  104. {stackone_ai-2.4.0 → stackone_ai-2.5.1}/vendor/stackone-ai-node/.oxlintrc.jsonc +0 -0
  105. {stackone_ai-2.4.0 → stackone_ai-2.5.1}/vendor/stackone-ai-node/AGENTS.md +0 -0
  106. {stackone_ai-2.4.0 → stackone_ai-2.5.1}/vendor/stackone-ai-node/CHANGELOG.md +0 -0
  107. {stackone_ai-2.4.0 → stackone_ai-2.5.1}/vendor/stackone-ai-node/CLAUDE.md +0 -0
  108. {stackone_ai-2.4.0 → stackone_ai-2.5.1}/vendor/stackone-ai-node/LICENSE +0 -0
  109. {stackone_ai-2.4.0 → stackone_ai-2.5.1}/vendor/stackone-ai-node/README.md +0 -0
  110. {stackone_ai-2.4.0 → stackone_ai-2.5.1}/vendor/stackone-ai-node/examples/README.md +0 -0
  111. {stackone_ai-2.4.0 → stackone_ai-2.5.1}/vendor/stackone-ai-node/examples/ai-sdk-integration.test.ts +0 -0
  112. {stackone_ai-2.4.0 → stackone_ai-2.5.1}/vendor/stackone-ai-node/examples/ai-sdk-integration.ts +0 -0
  113. {stackone_ai-2.4.0 → stackone_ai-2.5.1}/vendor/stackone-ai-node/examples/anthropic-integration.ts +0 -0
  114. {stackone_ai-2.4.0 → stackone_ai-2.5.1}/vendor/stackone-ai-node/examples/claude-agent-sdk-integration.test.ts +0 -0
  115. {stackone_ai-2.4.0 → stackone_ai-2.5.1}/vendor/stackone-ai-node/examples/claude-agent-sdk-integration.ts +0 -0
  116. {stackone_ai-2.4.0 → stackone_ai-2.5.1}/vendor/stackone-ai-node/examples/fetch-tools-debug.ts +0 -0
  117. {stackone_ai-2.4.0 → stackone_ai-2.5.1}/vendor/stackone-ai-node/examples/fetch-tools.test.ts +0 -0
  118. {stackone_ai-2.4.0 → stackone_ai-2.5.1}/vendor/stackone-ai-node/examples/fetch-tools.ts +0 -0
  119. {stackone_ai-2.4.0 → stackone_ai-2.5.1}/vendor/stackone-ai-node/examples/meta-tools.ts +0 -0
  120. {stackone_ai-2.4.0 → stackone_ai-2.5.1}/vendor/stackone-ai-node/examples/openai-integration.test.ts +0 -0
  121. {stackone_ai-2.4.0 → stackone_ai-2.5.1}/vendor/stackone-ai-node/examples/openai-integration.ts +0 -0
  122. {stackone_ai-2.4.0 → stackone_ai-2.5.1}/vendor/stackone-ai-node/examples/openai-responses-integration.test.ts +0 -0
  123. {stackone_ai-2.4.0 → stackone_ai-2.5.1}/vendor/stackone-ai-node/examples/openai-responses-integration.ts +0 -0
  124. {stackone_ai-2.4.0 → stackone_ai-2.5.1}/vendor/stackone-ai-node/examples/package.json +0 -0
  125. {stackone_ai-2.4.0 → stackone_ai-2.5.1}/vendor/stackone-ai-node/examples/tanstack-ai-integration.test.ts +0 -0
  126. {stackone_ai-2.4.0 → stackone_ai-2.5.1}/vendor/stackone-ai-node/examples/tanstack-ai-integration.ts +0 -0
  127. {stackone_ai-2.4.0 → stackone_ai-2.5.1}/vendor/stackone-ai-node/examples/tsconfig.json +0 -0
  128. {stackone_ai-2.4.0 → stackone_ai-2.5.1}/vendor/stackone-ai-node/flake.lock +0 -0
  129. {stackone_ai-2.4.0 → stackone_ai-2.5.1}/vendor/stackone-ai-node/flake.nix +0 -0
  130. {stackone_ai-2.4.0 → stackone_ai-2.5.1}/vendor/stackone-ai-node/knip.config.ts +0 -0
  131. {stackone_ai-2.4.0 → stackone_ai-2.5.1}/vendor/stackone-ai-node/lefthook.yaml +0 -0
  132. {stackone_ai-2.4.0 → stackone_ai-2.5.1}/vendor/stackone-ai-node/mocks/handlers.example-api.ts +0 -0
  133. {stackone_ai-2.4.0 → stackone_ai-2.5.1}/vendor/stackone-ai-node/mocks/handlers.mcp.ts +0 -0
  134. {stackone_ai-2.4.0 → stackone_ai-2.5.1}/vendor/stackone-ai-node/mocks/handlers.openai.ts +0 -0
  135. {stackone_ai-2.4.0 → stackone_ai-2.5.1}/vendor/stackone-ai-node/mocks/handlers.stackone-ai.ts +0 -0
  136. {stackone_ai-2.4.0 → stackone_ai-2.5.1}/vendor/stackone-ai-node/mocks/handlers.stackone-rpc.ts +0 -0
  137. {stackone_ai-2.4.0 → stackone_ai-2.5.1}/vendor/stackone-ai-node/mocks/handlers.ts +0 -0
  138. {stackone_ai-2.4.0 → stackone_ai-2.5.1}/vendor/stackone-ai-node/mocks/handlers.utils.ts +0 -0
  139. {stackone_ai-2.4.0 → stackone_ai-2.5.1}/vendor/stackone-ai-node/mocks/mcp-server.ts +0 -0
  140. {stackone_ai-2.4.0 → stackone_ai-2.5.1}/vendor/stackone-ai-node/mocks/node.ts +0 -0
  141. {stackone_ai-2.4.0 → stackone_ai-2.5.1}/vendor/stackone-ai-node/package.json +0 -0
  142. {stackone_ai-2.4.0 → stackone_ai-2.5.1}/vendor/stackone-ai-node/pnpm-lock.yaml +0 -0
  143. {stackone_ai-2.4.0 → stackone_ai-2.5.1}/vendor/stackone-ai-node/pnpm-workspace.yaml +0 -0
  144. {stackone_ai-2.4.0 → stackone_ai-2.5.1}/vendor/stackone-ai-node/src/consts.ts +0 -0
  145. {stackone_ai-2.4.0 → stackone_ai-2.5.1}/vendor/stackone-ai-node/src/feedback.test.ts +0 -0
  146. {stackone_ai-2.4.0 → stackone_ai-2.5.1}/vendor/stackone-ai-node/src/feedback.ts +0 -0
  147. {stackone_ai-2.4.0 → stackone_ai-2.5.1}/vendor/stackone-ai-node/src/headers.test.ts +0 -0
  148. {stackone_ai-2.4.0 → stackone_ai-2.5.1}/vendor/stackone-ai-node/src/headers.ts +0 -0
  149. {stackone_ai-2.4.0 → stackone_ai-2.5.1}/vendor/stackone-ai-node/src/index.ts +0 -0
  150. {stackone_ai-2.4.0 → stackone_ai-2.5.1}/vendor/stackone-ai-node/src/mcp-client.test.ts +0 -0
  151. {stackone_ai-2.4.0 → stackone_ai-2.5.1}/vendor/stackone-ai-node/src/mcp-client.ts +0 -0
  152. {stackone_ai-2.4.0 → stackone_ai-2.5.1}/vendor/stackone-ai-node/src/requestBuilder.test.ts +0 -0
  153. {stackone_ai-2.4.0 → stackone_ai-2.5.1}/vendor/stackone-ai-node/src/requestBuilder.ts +0 -0
  154. {stackone_ai-2.4.0 → stackone_ai-2.5.1}/vendor/stackone-ai-node/src/rpc-client.test.ts +0 -0
  155. {stackone_ai-2.4.0 → stackone_ai-2.5.1}/vendor/stackone-ai-node/src/rpc-client.ts +0 -0
  156. {stackone_ai-2.4.0 → stackone_ai-2.5.1}/vendor/stackone-ai-node/src/schema.ts +0 -0
  157. {stackone_ai-2.4.0 → stackone_ai-2.5.1}/vendor/stackone-ai-node/src/tool.test-d.ts +0 -0
  158. {stackone_ai-2.4.0 → stackone_ai-2.5.1}/vendor/stackone-ai-node/src/tool.test.ts +0 -0
  159. {stackone_ai-2.4.0 → stackone_ai-2.5.1}/vendor/stackone-ai-node/src/tool.ts +0 -0
  160. {stackone_ai-2.4.0 → stackone_ai-2.5.1}/vendor/stackone-ai-node/src/toolsets.test-d.ts +0 -0
  161. {stackone_ai-2.4.0 → stackone_ai-2.5.1}/vendor/stackone-ai-node/src/toolsets.test.ts +0 -0
  162. {stackone_ai-2.4.0 → stackone_ai-2.5.1}/vendor/stackone-ai-node/src/toolsets.ts +0 -0
  163. {stackone_ai-2.4.0 → stackone_ai-2.5.1}/vendor/stackone-ai-node/src/types.ts +0 -0
  164. {stackone_ai-2.4.0 → stackone_ai-2.5.1}/vendor/stackone-ai-node/src/utils/array.test.ts +0 -0
  165. {stackone_ai-2.4.0 → stackone_ai-2.5.1}/vendor/stackone-ai-node/src/utils/array.ts +0 -0
  166. {stackone_ai-2.4.0 → stackone_ai-2.5.1}/vendor/stackone-ai-node/src/utils/error-stackone-api.test.ts +0 -0
  167. {stackone_ai-2.4.0 → stackone_ai-2.5.1}/vendor/stackone-ai-node/src/utils/error-stackone-api.ts +0 -0
  168. {stackone_ai-2.4.0 → stackone_ai-2.5.1}/vendor/stackone-ai-node/src/utils/error-stackone.test.ts +0 -0
  169. {stackone_ai-2.4.0 → stackone_ai-2.5.1}/vendor/stackone-ai-node/src/utils/error-stackone.ts +0 -0
  170. {stackone_ai-2.4.0 → stackone_ai-2.5.1}/vendor/stackone-ai-node/src/utils/tfidf-index.test.ts +0 -0
  171. {stackone_ai-2.4.0 → stackone_ai-2.5.1}/vendor/stackone-ai-node/src/utils/tfidf-index.ts +0 -0
  172. {stackone_ai-2.4.0 → stackone_ai-2.5.1}/vendor/stackone-ai-node/src/utils/try-import.test.ts +0 -0
  173. {stackone_ai-2.4.0 → stackone_ai-2.5.1}/vendor/stackone-ai-node/src/utils/try-import.ts +0 -0
  174. {stackone_ai-2.4.0 → stackone_ai-2.5.1}/vendor/stackone-ai-node/src/utils/type.ts +0 -0
  175. {stackone_ai-2.4.0 → stackone_ai-2.5.1}/vendor/stackone-ai-node/tsconfig.json +0 -0
  176. {stackone_ai-2.4.0 → stackone_ai-2.5.1}/vendor/stackone-ai-node/tsdown.config.ts +0 -0
  177. {stackone_ai-2.4.0 → stackone_ai-2.5.1}/vendor/stackone-ai-node/vitest.config.ts +0 -0
  178. {stackone_ai-2.4.0 → stackone_ai-2.5.1}/vendor/stackone-ai-node/vitest.setup.ts +0 -0
@@ -0,0 +1,3 @@
1
+ {
2
+ ".": "2.5.1"
3
+ }
@@ -1,5 +1,19 @@
1
1
  # Changelog
2
2
 
3
+ ## [2.5.1](https://github.com/StackOneHQ/stackone-ai-python/compare/stackone-ai-v2.5.0...stackone-ai-v2.5.1) (2026-03-26)
4
+
5
+
6
+ ### Bug Fixes
7
+
8
+ * **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))
9
+
10
+ ## [2.5.0](https://github.com/StackOneHQ/stackone-ai-python/compare/stackone-ai-v2.4.0...stackone-ai-v2.5.0) (2026-03-25)
11
+
12
+
13
+ ### Features
14
+
15
+ * **search-tools:** LLM-driven search and execute and new API ([#151](https://github.com/StackOneHQ/stackone-ai-python/issues/151)) ([a5e5723](https://github.com/StackOneHQ/stackone-ai-python/commit/a5e5723689d702ce4d176194a0d6a43486bcdff7))
16
+
3
17
  ## [2.4.0](https://github.com/StackOneHQ/stackone-ai-python/compare/stackone-ai-v2.3.1...stackone-ai-v2.4.0) (2026-03-06)
4
18
 
5
19
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: stackone-ai
3
- Version: 2.4.0
3
+ Version: 2.5.1
4
4
  Summary: agents performing actions on your SaaS
5
5
  Author-email: StackOne <support@stackone.com>
6
6
  License-File: LICENSE
@@ -0,0 +1,202 @@
1
+ """Search and execute example: LLM-driven tool discovery and execution.
2
+
3
+ There are two ways to give tools to an LLM:
4
+
5
+ 1. ``toolset.openai()`` — fetches ALL tools and converts them to OpenAI format.
6
+ Token cost scales with the number of tools in your catalog.
7
+
8
+ 2. ``toolset.openai(mode="search_and_execute")`` — returns just 2 tools
9
+ (tool_search + tool_execute). The LLM discovers and runs tools on-demand,
10
+ keeping token usage constant regardless of catalog size.
11
+
12
+ This example demonstrates approach 2 with two patterns:
13
+ - Raw client (OpenAI): manual agent loop with ``toolset.execute()``
14
+ - LangChain: framework handles tool execution automatically
15
+
16
+ Prerequisites:
17
+ - STACKONE_API_KEY environment variable
18
+ - STACKONE_ACCOUNT_ID environment variable
19
+ - OPENAI_API_KEY environment variable
20
+
21
+ Run with:
22
+ uv run python examples/agent_tool_search.py
23
+ """
24
+
25
+ from __future__ import annotations
26
+
27
+ import json
28
+ import os
29
+
30
+ try:
31
+ from dotenv import load_dotenv
32
+
33
+ load_dotenv()
34
+ except ModuleNotFoundError:
35
+ pass
36
+
37
+ from stackone_ai import StackOneToolSet
38
+
39
+
40
+ def example_openai() -> None:
41
+ """Raw client: OpenAI.
42
+
43
+ Shows: init toolset -> get OpenAI tools -> manual agent loop with toolset.execute().
44
+ """
45
+ print("=" * 60)
46
+ print("Example 1: Raw client (OpenAI) — manual execution")
47
+ print("=" * 60)
48
+ print()
49
+
50
+ try:
51
+ from openai import OpenAI
52
+ except ImportError:
53
+ print("Skipped: pip install openai")
54
+ print()
55
+ return
56
+
57
+ if not os.getenv("OPENAI_API_KEY"):
58
+ print("Skipped: Set OPENAI_API_KEY to run this example.")
59
+ print()
60
+ return
61
+
62
+ # 1. Init toolset
63
+ account_id = os.getenv("STACKONE_ACCOUNT_ID")
64
+ toolset = StackOneToolSet(
65
+ account_id=account_id,
66
+ search={"method": "semantic", "top_k": 3},
67
+ execute={"account_ids": [account_id]} if account_id else None,
68
+ )
69
+
70
+ # 2. Get tools in OpenAI format
71
+ openai_tools = toolset.openai(mode="search_and_execute")
72
+
73
+ # 3. Create OpenAI client and run agent loop
74
+ client = OpenAI()
75
+ messages: list[dict] = [
76
+ {
77
+ "role": "system",
78
+ "content": (
79
+ "You are a helpful scheduling assistant. Use tool_search to find relevant tools, "
80
+ "then tool_execute to run them. Always read the parameter schemas from tool_search "
81
+ "results carefully. If a tool needs a user URI, first search for and call a "
82
+ '"get current user" tool to obtain it. If a tool execution fails, try different '
83
+ "parameters or a different tool."
84
+ ),
85
+ },
86
+ {"role": "user", "content": "List my upcoming Calendly events for the next week."},
87
+ ]
88
+
89
+ for _step in range(10):
90
+ response = client.chat.completions.create(
91
+ model="gpt-5.4",
92
+ messages=messages,
93
+ tools=openai_tools,
94
+ tool_choice="auto",
95
+ )
96
+
97
+ choice = response.choices[0]
98
+
99
+ # 4. If no tool calls, print final answer and stop
100
+ if not choice.message.tool_calls:
101
+ print(f"Answer: {choice.message.content}")
102
+ break
103
+
104
+ # 5. Execute tool calls manually and feed results back
105
+ messages.append(choice.message.model_dump(exclude_none=True))
106
+ for tool_call in choice.message.tool_calls:
107
+ print(f" -> {tool_call.function.name}({tool_call.function.arguments})")
108
+ result = toolset.execute(tool_call.function.name, tool_call.function.arguments)
109
+ messages.append(
110
+ {
111
+ "role": "tool",
112
+ "tool_call_id": tool_call.id,
113
+ "content": json.dumps(result),
114
+ }
115
+ )
116
+
117
+ print()
118
+
119
+
120
+ def example_langchain() -> None:
121
+ """Framework: LangChain with auto-execution.
122
+
123
+ Shows: init toolset -> get LangChain tools -> bind to model -> framework executes tools.
124
+ No toolset.execute() needed — the framework calls _run() on tools automatically.
125
+ """
126
+ print("=" * 60)
127
+ print("Example 2: LangChain — framework handles execution")
128
+ print("=" * 60)
129
+ print()
130
+
131
+ try:
132
+ from langchain_core.messages import AIMessage, HumanMessage, SystemMessage, ToolMessage
133
+ from langchain_openai import ChatOpenAI
134
+ except ImportError:
135
+ print("Skipped: pip install langchain-openai")
136
+ print()
137
+ return
138
+
139
+ if not os.getenv("OPENAI_API_KEY"):
140
+ print("Skipped: Set OPENAI_API_KEY to run this example.")
141
+ print()
142
+ return
143
+
144
+ # 1. Init toolset
145
+ account_id = os.getenv("STACKONE_ACCOUNT_ID")
146
+ toolset = StackOneToolSet(
147
+ account_id=account_id,
148
+ search={"method": "semantic", "top_k": 3},
149
+ execute={"account_ids": [account_id]} if account_id else None,
150
+ )
151
+
152
+ # 2. Get tools in LangChain format and bind to model
153
+ langchain_tools = toolset.langchain(mode="search_and_execute")
154
+ tools_by_name = {tool.name: tool for tool in langchain_tools}
155
+ model = ChatOpenAI(model="gpt-5.4").bind_tools(langchain_tools)
156
+
157
+ # 3. Run agent loop
158
+ messages = [
159
+ SystemMessage(
160
+ content=(
161
+ "You are a helpful scheduling assistant. Use tool_search to find relevant tools, "
162
+ "then tool_execute to run them. Always read the parameter schemas from tool_search "
163
+ "results carefully. If a tool needs a user URI, first search for and call a "
164
+ '"get current user" tool to obtain it. If a tool execution fails, try different '
165
+ "parameters or a different tool."
166
+ ),
167
+ ),
168
+ HumanMessage(content="List my upcoming Calendly events for the next week."),
169
+ ]
170
+
171
+ for _step in range(10):
172
+ response: AIMessage = model.invoke(messages)
173
+
174
+ # 4. If no tool calls, print final answer and stop
175
+ if not response.tool_calls:
176
+ print(f"Answer: {response.content}")
177
+ break
178
+
179
+ # 5. Framework-compatible execution — invoke LangChain tools directly
180
+ messages.append(response)
181
+ for tool_call in response.tool_calls:
182
+ print(f" -> {tool_call['name']}({json.dumps(tool_call['args'])})")
183
+ tool = tools_by_name[tool_call["name"]]
184
+ result = tool.invoke(tool_call["args"])
185
+ messages.append(ToolMessage(content=json.dumps(result), tool_call_id=tool_call["id"]))
186
+
187
+ print()
188
+
189
+
190
+ def main() -> None:
191
+ """Run all examples."""
192
+ api_key = os.getenv("STACKONE_API_KEY")
193
+ if not api_key:
194
+ print("Set STACKONE_API_KEY to run these examples.")
195
+ return
196
+
197
+ example_openai()
198
+ example_langchain()
199
+
200
+
201
+ if __name__ == "__main__":
202
+ main()
@@ -34,7 +34,7 @@ def crewai_integration():
34
34
  goal=f"What is the employee with the id {employee_id}?",
35
35
  backstory="With over 10 years of experience in HR and employee management, "
36
36
  "you excel at finding patterns in complex datasets.",
37
- llm="gpt-4o-mini",
37
+ llm="gpt-5.4",
38
38
  tools=langchain_tools,
39
39
  max_iter=2,
40
40
  )
@@ -33,7 +33,7 @@ def langchain_integration() -> None:
33
33
  assert hasattr(tool, "args_schema"), "Expected tool to have args_schema"
34
34
 
35
35
  # Create model with tools
36
- model = ChatOpenAI(model="gpt-4o-mini")
36
+ model = ChatOpenAI(model="gpt-5.4")
37
37
  model_with_tools = model.bind_tools(langchain_tools)
38
38
 
39
39
  result = model_with_tools.invoke(f"Can you get me information about employee with ID: {employee_id}?")
@@ -53,7 +53,7 @@ def openai_integration() -> None:
53
53
  ]
54
54
 
55
55
  response = client.chat.completions.create(
56
- model="gpt-4o-mini",
56
+ model="gpt-5.4",
57
57
  messages=messages,
58
58
  tools=openai_tools,
59
59
  tool_choice="auto",
@@ -81,7 +81,7 @@ def openai_integration() -> None:
81
81
 
82
82
  # Verify the final response
83
83
  final_response = client.chat.completions.create(
84
- model="gpt-4o-mini",
84
+ model="gpt-5.4",
85
85
  messages=messages,
86
86
  tools=openai_tools,
87
87
  tool_choice="auto",
@@ -198,7 +198,7 @@ def example_with_openai():
198
198
 
199
199
  # Create a chat completion with discovered tools
200
200
  response = client.chat.completions.create(
201
- model="gpt-4",
201
+ model="gpt-5.4",
202
202
  messages=[
203
203
  {
204
204
  "role": "system",
@@ -246,7 +246,7 @@ def example_with_langchain():
246
246
  print(f" - {tool.name}: {tool.description}")
247
247
 
248
248
  # Create LangChain agent
249
- llm = ChatOpenAI(model="gpt-4", temperature=0)
249
+ llm = ChatOpenAI(model="gpt-5.4", temperature=0)
250
250
 
251
251
  prompt = ChatPromptTemplate.from_messages(
252
252
  [
@@ -132,7 +132,7 @@ def example_search_action_names():
132
132
  # Show the limited results
133
133
  print(f"Top {len(results_limited)} matches from the full catalog:")
134
134
  for r in results_limited:
135
- print(f" [{r.similarity_score:.2f}] {r.action_name} ({r.connector_key})")
135
+ print(f" [{r.similarity_score:.2f}] {r.id}")
136
136
  print(f" {r.description}")
137
137
  print()
138
138
 
@@ -143,7 +143,7 @@ def example_search_action_names():
143
143
  filtered = toolset.search_action_names(query, account_ids=_account_ids, top_k=5)
144
144
  print(f" Filtered to {len(filtered)} matches (only your connectors):")
145
145
  for r in filtered:
146
- print(f" [{r.similarity_score:.2f}] {r.action_name} ({r.connector_key})")
146
+ print(f" [{r.similarity_score:.2f}] {r.id}")
147
147
  else:
148
148
  print("Tip: Set STACKONE_ACCOUNT_ID to see results filtered to your linked connectors.")
149
149
 
@@ -197,7 +197,7 @@ def example_search_tools_with_connector():
197
197
  print("=" * 60)
198
198
  print()
199
199
 
200
- toolset = StackOneToolSet()
200
+ toolset = StackOneToolSet(search={})
201
201
 
202
202
  query = "book a meeting"
203
203
  connector = "calendly"
@@ -230,7 +230,7 @@ def example_search_tool_agent_loop():
230
230
  print("=" * 60)
231
231
  print()
232
232
 
233
- toolset = StackOneToolSet()
233
+ toolset = StackOneToolSet(search={})
234
234
 
235
235
  print("Step 1: Fetching tools from your linked accounts via MCP...")
236
236
  all_tools = toolset.fetch_tools(account_ids=_account_ids)
@@ -281,7 +281,7 @@ def example_openai_agent_loop():
281
281
 
282
282
  if openai_key:
283
283
  client = OpenAI()
284
- model = "gpt-4o-mini"
284
+ model = "gpt-5.4"
285
285
  provider = "OpenAI"
286
286
  elif google_key:
287
287
  client = OpenAI(
@@ -298,7 +298,7 @@ def example_openai_agent_loop():
298
298
  print(f"Using {provider} ({model})")
299
299
  print()
300
300
 
301
- toolset = StackOneToolSet()
301
+ toolset = StackOneToolSet(search={})
302
302
 
303
303
  query = "list upcoming events"
304
304
  print(f'Step 1: Discovering tools for "{query}" via semantic search...')
@@ -358,7 +358,7 @@ def example_langchain_semantic():
358
358
  print()
359
359
  return
360
360
 
361
- toolset = StackOneToolSet()
361
+ toolset = StackOneToolSet(search={})
362
362
 
363
363
  query = "remove a user from the team"
364
364
  print(f'Step 1: Searching for "{query}" via semantic search...')
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "stackone-ai"
3
- version = "2.4.0"
3
+ version = "2.5.1"
4
4
  description = "agents performing actions on your SaaS"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.10"
@@ -7,12 +7,13 @@ from stackone_ai.semantic_search import (
7
7
  SemanticSearchResponse,
8
8
  SemanticSearchResult,
9
9
  )
10
- from stackone_ai.toolset import SearchConfig, SearchMode, SearchTool, StackOneToolSet
10
+ from stackone_ai.toolset import ExecuteToolsConfig, SearchConfig, SearchMode, SearchTool, StackOneToolSet
11
11
 
12
12
  __all__ = [
13
13
  "StackOneToolSet",
14
14
  "StackOneTool",
15
15
  "Tools",
16
+ "ExecuteToolsConfig",
16
17
  "SearchConfig",
17
18
  "SearchMode",
18
19
  "SearchTool",
@@ -22,4 +23,4 @@ __all__ = [
22
23
  "SemanticSearchResponse",
23
24
  "SemanticSearchError",
24
25
  ]
25
- __version__ = "2.4.0"
26
+ __version__ = "2.5.1"
@@ -414,21 +414,33 @@ class StackOneTool(BaseModel):
414
414
 
415
415
  for name, details in self.parameters.properties.items():
416
416
  python_type: type = str # Default to str
417
+ is_nullable = False
417
418
  if isinstance(details, dict):
418
419
  type_str = details.get("type", "string")
420
+ is_nullable = details.get("nullable", False)
419
421
  if type_str == "number":
420
422
  python_type = float
421
423
  elif type_str == "integer":
422
424
  python_type = int
423
425
  elif type_str == "boolean":
424
426
  python_type = bool
427
+ elif type_str == "object":
428
+ python_type = dict
429
+ elif type_str == "array":
430
+ python_type = list
425
431
 
426
- field = Field(description=details.get("description", ""))
432
+ if is_nullable:
433
+ field = Field(default=None, description=details.get("description", ""))
434
+ else:
435
+ field = Field(description=details.get("description", ""))
427
436
  else:
428
437
  field = Field(description="")
429
438
 
430
439
  schema_props[name] = field
431
- annotations[name] = python_type
440
+ if is_nullable:
441
+ annotations[name] = python_type | None
442
+ else:
443
+ annotations[name] = python_type
432
444
 
433
445
  # Create the schema class with proper annotations
434
446
  schema_class = type(
@@ -12,18 +12,15 @@ Each path trades off between speed, filtering, and completeness.
12
12
  This is the primary method used when integrating with OpenAI, LangChain, or CrewAI.
13
13
  The internal flow is:
14
14
 
15
- 1. Fetch ALL tools from linked accounts via MCP (uses account_ids to scope the request)
16
- 2. Extract available connectors from the fetched tools (e.g. {bamboohr, hibob})
17
- 3. Search EACH connector in parallel via the semantic search API (/actions/search)
18
- 4. Collect results, sort by relevance score, apply top_k if specified
19
- 5. Match semantic results back to the fetched tool definitions
20
- 6. Return Tools sorted by relevance score
15
+ 1. Fetch tools from linked accounts via MCP (provides connectors and tool schemas)
16
+ 2. Search EACH connector in parallel via the semantic search API (/actions/search)
17
+ 3. Match search results to MCP tool definitions
18
+ 4. Deduplicate, sort by relevance score, apply top_k
19
+ 5. Return Tools sorted by relevance score
21
20
 
22
21
  Key point: only the user's own connectors are searched — no wasted results
23
- from connectors the user doesn't have. Tools are fetched first, semantic
24
- search runs second, and only tools that exist in the user's linked
25
- accounts AND match the semantic query are returned. This prevents
26
- suggesting tools the user cannot execute.
22
+ from connectors the user doesn't have. Tool schemas come from MCP (source
23
+ of truth), while the search API provides relevance ranking.
27
24
 
28
25
  If the semantic API is unavailable, the SDK falls back to a local
29
26
  BM25 + TF-IDF hybrid search over the fetched tools (unless
@@ -33,10 +30,9 @@ BM25 + TF-IDF hybrid search over the fetched tools (unless
33
30
  2. ``search_action_names(query)`` — Lightweight discovery
34
31
  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
35
32
 
36
- Queries the semantic API directly and returns action name metadata
37
- (name, connector, score, description) **without** fetching full tool
38
- definitions. This is useful for previewing results before committing
39
- to a full fetch.
33
+ Queries the semantic API directly and returns action IDs with
34
+ similarity scores, **without** building full tool objects. Useful
35
+ for previewing results before committing to a full fetch.
40
36
 
41
37
  When ``account_ids`` are provided, each connector is searched in
42
38
  parallel (same as ``search_tools``). Without ``account_ids``, results
@@ -71,12 +67,8 @@ class SemanticSearchError(Exception):
71
67
  class SemanticSearchResult(BaseModel):
72
68
  """Single result from semantic search API."""
73
69
 
74
- action_name: str
75
- connector_key: str
70
+ id: str
76
71
  similarity_score: float
77
- label: str
78
- description: str
79
- project_id: str = "global"
80
72
 
81
73
 
82
74
  class SemanticSearchResponse(BaseModel):
@@ -99,7 +91,7 @@ class SemanticSearchClient:
99
91
  client = SemanticSearchClient(api_key="sk-xxx")
100
92
  response = client.search("create employee", connector="bamboohr", top_k=5)
101
93
  for result in response.results:
102
- print(f"{result.action_name}: {result.similarity_score:.2f}")
94
+ print(f"{result.action_id}: {result.similarity_score:.2f}")
103
95
  """
104
96
 
105
97
  def __init__(
@@ -152,7 +144,7 @@ class SemanticSearchClient:
152
144
  Example:
153
145
  response = client.search("onboard a new team member", top_k=5)
154
146
  for result in response.results:
155
- print(f"{result.action_name}: {result.similarity_score:.2f}")
147
+ print(f"{result.action_id}: {result.similarity_score:.2f}")
156
148
  """
157
149
  url = f"{self.base_url}/actions/search"
158
150
  headers = {
@@ -210,4 +202,4 @@ class SemanticSearchClient:
210
202
  )
211
203
  """
212
204
  response = self.search(query, connector, top_k, project_id, min_similarity=min_similarity)
213
- return [r.action_name for r in response.results]
205
+ return [r.id for r in response.results]