Skip to content

Commit 460d0e5

Browse files
authored
Tool settings revamp (#111)
* Revamp tool settings * Make tags non-optional * Replace unittest.skipTest() with pytest.skip() * Match typehint for tool.tags with the LangChain StructuredTool * Remove defaults from ToolSettings * Fix tests, duplication in README * Update README
1 parent e278e44 commit 460d0e5

17 files changed

Lines changed: 410 additions & 328 deletions

File tree

.basedpyright/baseline.json

Lines changed: 0 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -38570,32 +38570,6 @@
3857038570
}
3857138571
}
3857238572
],
38573-
"./tests/system/test_ai_agentic_test_app.py": [
38574-
{
38575-
"code": "reportUnknownArgumentType",
38576-
"range": {
38577-
"startColumn": 31,
38578-
"endColumn": 40,
38579-
"lineCount": 1
38580-
}
38581-
},
38582-
{
38583-
"code": "reportUnknownArgumentType",
38584-
"range": {
38585-
"startColumn": 29,
38586-
"endColumn": 38,
38587-
"lineCount": 1
38588-
}
38589-
},
38590-
{
38591-
"code": "reportUnknownArgumentType",
38592-
"range": {
38593-
"startColumn": 31,
38594-
"endColumn": 40,
38595-
"lineCount": 1
38596-
}
38597-
}
38598-
],
3859938573
"./tests/system/test_apps/cre_app/bin/execute.py": [
3860038574
{
3860138575
"code": "reportMissingImports",

splunklib/ai/README.md

Lines changed: 80 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ from splunklib.ai import Agent, OpenAIModel
9090
model = OpenAIModel(
9191
model="llama3.2:3b",
9292
base_url="http://localhost:11434/v1",
93-
api_key="ollama",
93+
api_key="", # required but ignored
9494
)
9595

9696
async with Agent(model=model) as agent: ....
@@ -106,7 +106,7 @@ from splunklib.ai import Agent, AnthropicModel
106106
model = AnthropicModel(
107107
model="llama3.2:3b",
108108
base_url="http://localhost:11434",
109-
api_key="ollama",
109+
api_key="", # required but ignored
110110
)
111111

112112
async with Agent(model=model) as agent: ....
@@ -130,14 +130,11 @@ To enable the Agent to perform background or auxiliary tasks, it can be extended
130130
These tools provide the Agent with additional capabilities beyond text generation, such as executing
131131
actions, fetching data, or interacting with external systems.
132132

133-
The `use_mcp_tools` parameter controls whether MCP tools are exposed to the underlying LLM. When this flag
134-
is enabled, both local and remote MCP tools are loaded and made available for invocation by the model during execution.
135-
136-
This mechanism allows the Agent to dynamically decide when to use tools as part of its reasoning process,
137-
while keeping tool availability explicitly configurable.
133+
The `tool_settings` parameter controls which MCP tools are exposed to the underlying LLM. See [Tool filtering](#tool-filtering).
138134

139135
```py
140136
from splunklib.ai import Agent, OpenAIModel
137+
from splunklib.ai.tool_settings import ToolSettings
141138
from splunklib.client import connect
142139

143140
model = OpenAIModel(...)
@@ -147,7 +144,7 @@ async with Agent(
147144
model=model,
148145
system_prompt="Your name is Stefan",
149146
service=service,
150-
use_mcp_tools=True,
147+
tool_settings=ToolSettings(local=True),
151148
) as agent: ...
152149
```
153150

@@ -157,6 +154,25 @@ Remote tools are provided by the [Splunk MCP Server App](https://help.splunk.com
157154
When a Splunk instance has the MCP Server App installed and configured, the Agent automatically
158155
discovers and loads all tools that are enabled on the MCP server during construction.
159156

157+
To let an agent access remote tools, pass a `RemoteToolSettings` instance and all the appropriate tools whitelisted:
158+
159+
```py
160+
from splunklib.ai.tool_settings import RemoteToolSettings, ToolAllowlist, ToolSettings
161+
162+
async with Agent(
163+
model=model,
164+
service=service,
165+
system_prompt="...",
166+
tool_settings=ToolSettings(
167+
remote=RemoteToolSettings(
168+
allowlist=ToolAllowlist(names=["splunk_get_indexes"], tags=["tag1"])
169+
)
170+
),
171+
) as agent: ...
172+
```
173+
174+
See [Tool filtering](#tool-filtering) for more details.
175+
160176
### Local tools
161177

162178
Local tools are custom tools that you, as an app developer, can implement and expose to the Agent.
@@ -168,7 +184,7 @@ decorator, which is used to annotate Python functions that should be made availa
168184
Each annotated function becomes an invocable tool, with its signature and docstring used to define
169185
the tool’s interface and description.
170186

171-
Example `tool.py` implementation:
187+
Example `tools.py` implementation:
172188

173189
```py
174190
from splunklib.ai.registry import ToolRegistry
@@ -177,21 +193,40 @@ registry = ToolRegistry()
177193

178194
@registry.tool()
179195
def hello(name: str) -> str:
180-
"""Hello returns a hello message"""
196+
"""Returns a hello message"""
181197
return f"Hello {name}!"
182198

183199

184200
if __name__ == "__main__":
185201
registry.run()
186202
```
187203

204+
To let an agent access all local tools, set `local=True`. To enable only some tools, pass a `LocalToolSettings` instance:
205+
206+
```py
207+
from splunklib.ai.tool_settings import LocalToolSettings, ToolAllowlist, ToolSettings
208+
209+
async with Agent(
210+
model=model,
211+
service=service,
212+
system_prompt="...",
213+
tool_settings=ToolSettings(
214+
# local=True, # enable all local tools
215+
local=RemoteToolSettings(
216+
allowlist=ToolAllowlist(names=["tool1"], tags=["tag1"])
217+
)
218+
),
219+
) as agent: ...
220+
```
221+
222+
See [Tool filtering](#tool-filtering) for more details.
223+
188224
#### ToolContext
189225

190226
`ToolContext` is a special parameter type that tools may declare in their function signature.
191227
Unlike regular tool inputs, this parameter is not provided by the LLM. Instead, it is
192228
automatically injected by the runtime for every tool invocation.
193229

194-
195230
##### Service access
196231

197232
`ToolContext` provides access to the SDK’s `Service` object, allowing tools to perform
@@ -227,7 +262,6 @@ if __name__ == "__main__":
227262

228263
`ToolContext` exposes a `Logger` instance that can be used for logging within your tool implementation.
229264

230-
231265
```py
232266
from splunklib.ai.registry import ToolContext
233267

@@ -236,18 +270,19 @@ def tool(ctx: ToolContext) -> None:
236270
ctx.logger.info("executing tool")
237271

238272
```
273+
239274
In this example, the `Logger` instance is accessed via `ctx.logger` and used to emit an informational
240275
log message during tool execution.
241276

242277
These logs are forwarded to the `logger` passed to the `Agent` constructor.
243278

244279
### Tool filtering
245280

246-
Tools can be filtered, before these are made available to the LLM, via the `tool_filters` parameter.
281+
Remote tools must intentionally allowlisted before they are made available to the LLM.
247282

248283
```py
249-
from splunklib.ai.tool_filtering import ToolFilters
250284
from splunklib.ai import Agent, OpenAIModel
285+
from splunklib.ai.tool_settings import LocalToolSettings, RemoteToolSettings, ToolAllowlist, ToolSettings
251286
from splunklib.client import connect
252287

253288
model = OpenAIModel(...)
@@ -257,17 +292,35 @@ async with Agent(
257292
model=model,
258293
system_prompt="Your name is Stefan",
259294
service=service,
260-
use_mcp_tools=True,
261-
tool_filters=ToolFilters(
262-
allowed_names=["tool_name"], allowed_tags=["tag1", "tag2"]
295+
tool_settings=ToolSettings(
296+
local=True,
297+
remote=RemoteToolSettings(
298+
allowlist=ToolAllowlist(names=["tool_name"], tags=["tag1", "tag2"])
299+
),
263300
),
264301
) as agent: ...
265302
```
266303

304+
A `custom_predicate` can be used for more flexible filtering:
305+
306+
```py
307+
tool_settings=ToolSettings(
308+
local=LocalToolSettings(
309+
allowlist=ToolAllowlist(custom_predicate=lambda tool: tool.name.startswith("my_"))
310+
),
311+
)
312+
```
313+
314+
As a shorthand, pass `local=True` to load all local tools with no filtering:
315+
316+
```py
317+
tool_settings=ToolSettings(local=True)
318+
```
319+
267320
## Conversation stores
268321

269322
By default, each call to `agent.invoke` is stateless - the agent has no memory of previous interactions,
270-
unless you provide the previouis message history explicitly. A conversation store enables the agent to persist
323+
unless you provide the previous message history explicitly. A conversation store enables the agent to persist
271324
and recall message history across invocations.
272325

273326
### `InMemoryStore`
@@ -352,7 +405,7 @@ Each subagent can use a different model, allowing you to optimize for both capab
352405
```py
353406
from splunklib.ai import Agent, OpenAIModel
354407
from splunklib.ai.messages import HumanMessage
355-
from splunklib.ai.tool_filtering import ToolFilters
408+
from splunklib.ai.tool_settings import LocalToolSettings, ToolAllowlist, ToolSettings
356409
from splunklib.client import connect
357410

358411
model = OpenAIModel(...)
@@ -362,30 +415,28 @@ async with (
362415
Agent(
363416
model=highly_specialized_model,
364417
service=service,
365-
use_mcp_tools=True,
366418
system_prompt=(
367419
"You are a highly specialized debugging agent, your job is to provide as much"
368420
"details as possible to resolve issues."
369421
"You have access to debugging-related tools, which you should leverage for your job."
370422
),
371423
name="debugging_agent",
372424
description="Agent, that provided with logs will analyze and debug complex issues",
373-
tool_filters=ToolFilters(
374-
allowed_tags=["debugging"]
425+
tool_settings=ToolSettings(
426+
local=LocalToolSettings(allowlist=ToolAllowlist(tags=["debugging"]))
375427
),
376428
) as debugging_agent,
377429
Agent(
378430
model=low_cost_model,
379431
service=service,
380-
use_mcp_tools=True,
381-
system_prompt= (
432+
system_prompt=(
382433
"You are a log analyzer agent. Your job is to query logs, based on the details that you receive and"
383434
"return a summary of interesting logs, that can be used for further analysis."
384435
),
385436
name="log_analyzer_agent",
386437
description="Agent, that provided with a problem details will return logs, that could be related to the problem",
387-
tool_filters=ToolFilters(
388-
allowed_tags=["spl"]
438+
tool_settings=ToolSettings(
439+
local=LocalToolSettings(allowlist=ToolAllowlist(tags=["spl"]))
389440
),
390441
) as log_analyzer_agent,
391442
):
@@ -561,11 +612,11 @@ Class-based middleware:
561612
```py
562613
from typing import Any, override
563614
from splunklib.ai.middleware import (
564-
AgentMiddlewareHandler,
615+
AgentMiddlewareHandler,
565616
AgentRequest,
566617
ModelMiddlewareHandler,
567618
ModelRequest,
568-
ModelResponse,
619+
ModelResponse,
569620
SubagentMiddlewareHandler,
570621
SubagentRequest,
571622
SubagentResponse,
@@ -649,7 +700,7 @@ from splunklib.ai.middleware import (
649700
model_middleware,
650701
ModelMiddlewareHandler,
651702
ModelRequest,
652-
ModelResponse,
703+
ModelResponse,
653704
)
654705

655706
@model_middleware

0 commit comments

Comments
 (0)