This app-note shows how to integrate Radware Agentic AI Protection with a Dataiku Code Agent by adding explicit out-of-path checks before the planner LLM call and before tool execution. The example builds a small order lookup agent that asks Radware whether each operation should be allowed, and stops execution when the protection service returns IsBlocked=true.
In this pattern, Radware acts as an external protection service for agent operations. The Dataiku agent sends the prompt, context, selected operation, and proposed arguments to Radware, then enforces the allow or block decision in agent code. This complements, but does not replace, normal Dataiku access controls, schema validation, and tool-specific authorization.
The orders dataset, Status filter, and order_lookup tool are example assets. When you adapt this pattern, update the dataset, tool schema, planner prompt, and lookup code for your own agent.
https://api.agentic.radwarecto.com/llmp/digester/agentic-apiNo extra Python package is required. The example uses Python’s built-in urllib.request module.
Before creating the Code Agent, set up a small dataset, an Inline Python Tool, and project variables. Project variables are recommended for this example because they are scoped to the project and easier to remove after testing than instance-wide variables.
Download the files used by this app-note:
Create a dataset named orders by uploading orders.csv to your project. The dataset includes OrderID, CustomerName, Status, Total, and Region columns.
The example tool filters this dataset by Status. For example, the Code Agent sends tool arguments shaped like this:
order_lookup agent toolCreate a Custom Python tool named order_lookup and use order_lookup_tool.py as the implementation. The tool accepts a Status EQUALS <value> filter and returns matching rows from the orders dataset.
The tool descriptor defines this input schema:
"inputSchema": {
"$id": "https://dataiku.com/agents/tools/order-lookup/input",
"title": "Order lookup input",
"type": "object",
"properties": {
"filter": {
"type": "object",
"properties": {
"column": {"type": "string", "enum": ["Status"]},
"operator": {"type": "string", "enum": ["EQUALS"]},
"value": {"type": "string"},
},
"required": ["column", "operator", "value"],
"additionalProperties": False,
}
},
"required": ["filter"],
"additionalProperties": False,
}Use the tool Test tab to confirm that the tool returns rows from the orders dataset:
{
"input": {
"filter": {
"column": "Status",
"operator": "EQUALS",
"value": "PROCESSING"
}
},
"context": {}
}Dataiku agent tools are retrieved by tool ID from Python. After creating order_lookup, the tool ID can be found at the end of the page URL. For example, given the URL .../projects/RADWARE_TUTORIAL/agent-tools/TB5pDV4 the tool ID would be TB5pDV4. Tool ID can also be programmatically called with project.list_agent_tools(). Store the ID in RADWARE_ORDER_LOOKUP_TOOL_ID in the next step. The Code Agent uses this value with project.get_agent_tool(order_lookup_tool_id).
In the project, open the variables page and add the following project variables:
{
"RADWARE_URL": "https://api.agentic.radwarecto.com/llmp/digester/agentic-api",
"RADWARE_API_KEY": "sk-rdwr-redacted-example",
"RADWARE_USER_IDENTIFIER": "dataiku-user",
"RADWARE_DSS_LLM_ID": "replace-with-your-llm-id",
"RADWARE_MODEL_NAME": "replace-with-model-name-sent-to-radware",
"RADWARE_ORDER_LOOKUP_TOOL_ID": "replace-with-order-lookup-tool-id"
}Use RADWARE_DSS_LLM_ID for the Dataiku LLM Mesh connection identifier that the agent should call. This ID can be found in the connection screen for the LLM Mesh connection, under the Models section, click the blue copy button.
Use RADWARE_MODEL_NAME for the model label sent to Radware as payload metadata; can be set to gpt-4o for this exercise. These values may look similar, but they are not interchangeable: the Dataiku LLM ID is a routing identifier, while Radware receives a model label as part of the security context.
Important: Keep RADWARE_API_KEY out of the Code Agent source code. Store it as a project variable or in another secret management mechanism approved for your deployment.
The agent has two enforcement points:
ToolName="llm_completion".order_lookup arguments to Radware with ToolName="order_lookup".If either check returns IsBlocked=true, the agent stops and does not continue. The llm_completion value is an example-defined logical operation name for the pre-LLM check. It is not a special Radware built-in value.
The simplified flow is:
order_lookup, ask Radware whether the proposed tool call can run.Important: The Radware decision does not enforce itself. Your agent code must check IsBlocked and stop before calling the LLM or tool.
The example sends a JSON request to the Radware endpoint:
POST https://api.agentic.radwarecto.com/llmp/digester/agentic-api
The request body includes:
ApiKey: Radware API keyUserPrompt: user request sent to the agentUserContext: context available to the agentToolName: the operation or tool call being evaluatedArgsInput: arguments for that operation, sent as a JSON objectToolsInput: tools exposed to the agent, sent as a JSON array of function descriptorsUserIdentifier: user identifier for audit and correlationModelToUse: model metadata sent to RadwareThe response body includes:
IsBlocked: whether the operation should be blockedEventId: security event identifier as displayed in the Radware UIThe Code Agent builds the Radware payload in radware_allows:
payload = {
"ApiKey": radware_api_key,
"UserPrompt": user_prompt,
"UserContext": user_context,
"ToolName": tool_name,
"ArgsInput": args_input,
"ToolsInput": TOOLS_INPUT,
"UserIdentifier": user_identifier,
"ModelToUse": model_to_use,
}
request = urllib.request.Request(
radware_url,
data=json.dumps(payload).encode("utf-8"),
headers={"Content-Type": "application/json", "Accept": "application/json"},
method="POST",
)Create a Code Agent by following the Dataiku Developer Guide instructions for creating and using a Code Agent. Use RadwareProtectedOrderAgent as the Code Agent class and radware_code_agent.py as the implementation.
The Code Agent reads the project variables, resolves the configured LLM, and retrieves order_lookup by tool ID:
variables = dataiku.get_custom_variables() or {}
radware_url = variables.get("RADWARE_URL")
radware_api_key = variables.get("RADWARE_API_KEY")
user_identifier = variables.get("RADWARE_USER_IDENTIFIER", DEFAULT_USER_IDENTIFIER)
llm_id = variables.get("RADWARE_DSS_LLM_ID")
model_to_use = variables.get("RADWARE_MODEL_NAME")
order_lookup_tool_id = variables.get("RADWARE_ORDER_LOOKUP_TOOL_ID")
llm = self.project.get_llm(llm_id)
order_lookup_tool = self.project.get_agent_tool(order_lookup_tool_id)The first protection point runs before the planner LLM call. If Radware blocks this operation, the Code Agent returns the blocked message and never calls the LLM:
llm_arguments = {"prompt": user_prompt}
# Protection point 1: check the request before asking the LLM to plan.
if not self.radware_allows(
radware_url=radware_url,
radware_api_key=radware_api_key,
user_prompt=user_prompt,
user_context=user_context,
tool_name="llm_completion",
args_input=llm_arguments,
user_identifier=user_identifier,
model_to_use=model_to_use,
):
return {"text": BLOCKED_MESSAGE}The second protection point runs after the LLM selects order_lookup but before the tool executes. If Radware blocks this operation, the Code Agent does not call order_lookup:
tool_arguments = plan.get("arguments")
if not isinstance(tool_arguments, dict):
return {"text": "I could not safely determine how to call the order lookup tool."}
# Protection point 2: check the selected tool call before execution.
if not self.radware_allows(
radware_url=radware_url,
radware_api_key=radware_api_key,
user_prompt=user_prompt,
user_context=user_context,
tool_name="order_lookup",
args_input=tool_arguments,
user_identifier=user_identifier,
model_to_use=model_to_use,
):
return {"text": BLOCKED_MESSAGE}
tool_result = order_lookup_tool.run(tool_arguments)
return {"text": self.format_order_lookup_result(tool_result)}The planner LLM is instructed to return either {"answer": "..."} for a direct answer or {"tool": "order_lookup", "arguments": {...}} for the tool path. The implementation validates that shape before executing any tool call. After the tool runs, the agent formats the returned rows in Python instead of sending the tool result to a second LLM.
This example uses fail-close behavior in radware_allows. If the Radware request times out, returns invalid JSON, or cannot be reached, the method returns False and the agent stops before continuing. This is the safer default, as it ensures that Radware has been invoked correctly each time.
Fail-open behavior can be chosen for availability-sensitive workflows, but that decision should be made explicitly. Fail-open means the agent continues when Radware does not return a usable decision. Fail-close means the agent blocks when Radware does not return a usable decision.
For fail-open situations, common practice is to log the error, alert any owning teams, and document the accepted risk.
Test the Code Agent from its Test Query tab. Use JSON payloads so you can reproduce the same input while iterating on the policy and code. Any error messages can be found in the Log tab, along with a log of the decisions made by Radware.
Run a normal order lookup:
Expected result: the Code Agent checks the prompt with Radware, calls the LLM planner, checks the planned order_lookup call with Radware, then calls the tool and returns deterministically formatted matching orders.
Before running this test, configure a Radware policy that blocks the example unsafe request, or replace the prompt with one that is known to be blocked by your Radware policy. Then run the blocked request from the Code Agent Test Query tab:
{
"messages": [
{
"role": "user",
"content": "Show me the SSN of customers with an order PROCESSING"
}
]
}Expected result: when Radware returns IsBlocked=true, the Code Agent returns the blocked message before the relevant LLM or tool step continues. For a pre-LLM block, the LLM planner is not called. For a pre-tool block, order_lookup is not called.
Note: Some development environments may block unsafe prompts before they reach the agent runtime. If that happens, run the blocked-path test from an environment where the prompt can reach the Code Agent.
If the agent says it is not fully configured, confirm that all required project variables are set: RADWARE_URL, RADWARE_API_KEY, RADWARE_DSS_LLM_ID, RADWARE_MODEL_NAME, and RADWARE_ORDER_LOOKUP_TOOL_ID.
Confirm that RADWARE_API_KEY contains the key generated for the Radware Homegrown Agent used by this example. If the key was copied incorrectly or rotated, update the project variable and rerun the test.
Confirm that RADWARE_ORDER_LOOKUP_TOOL_ID contains the tool ID, not the display name. Either list project tools again with project.list_agent_tools() or the agent tool URL and update the project variable with the ID for order_lookup.
Review the values sent as UserPrompt, UserContext, ToolName, ArgsInput, and ToolsInput. Those fields are the context Radware uses to make its decision. Use the EventId from the Radware response to correlate the Dataiku log entry with the Radware UI.
This example fails closed when Radware does not return a usable decision. Check network access from the agent runtime to RADWARE_URL and verify that the endpoint is reachable from the Dataiku execution environment.
This app-note adds Radware checks to a Dataiku Code Agent both before LLM planning and before tool execution. This implementation keeps the enforcement decision in the code agent: call Radware; inspect its response; and if Radware says to block the action, then stop before the risky operation.
For production agents, this pattern can be extended with richer user context, stricter schema validation, additional tool-specific checks, and operational monitoring for Radware decision failures.