|
59 | 59 | * <li>{@code message} - Required. The message to send to the agent. |
60 | 60 | * </ul> |
61 | 61 | * |
62 | | - * <p>Additionally, custom parameters defined in {@link SubAgentConfig} are exposed in the |
63 | | - * tool schema. At runtime, these parameters are extracted (prioritizing {@link ToolExecutionContext} |
64 | | - * over LLM input) and securely injected into the sub-agent's message {@code metadata}. |
| 62 | + * <p>Additionally, the tool supports two categories of external parameters defined in |
| 63 | + * {@link SubAgentConfig}: |
| 64 | + * <ul> |
| 65 | + * <li><b>System Parameters:</b> Extracted strictly from {@link ToolExecutionContext}. These are |
| 66 | + * invisible to the LLM and completely tamper-proof. |
| 67 | + * <li><b>Custom Parameters:</b> Exposed in the tool schema for LLM inference. At runtime, these |
| 68 | + * are extracted with {@link ToolExecutionContext} taking precedence over LLM input. |
| 69 | + * </ul> |
| 70 | + * Both types of parameters are securely injected into the sub-agent's message {@code metadata}. |
65 | 71 | */ |
66 | 72 | public class SubAgentTool implements AgentTool { |
67 | 73 |
|
@@ -124,7 +130,8 @@ public Mono<ToolResultBlock> callAsync(ToolCallParam param) { |
124 | 130 | * <ul> |
125 | 131 | * <li>Session ID generation for new conversations |
126 | 132 | * <li>Agent state loading for continued sessions |
127 | | - * <li>Extraction of custom parameters from ToolExecutionContext and LLM input |
| 133 | + * <li>Extraction of external parameters with strict priority: |
| 134 | + * {@link ToolExecutionContext} (highest) > LLM input (lowest) |
128 | 135 | * <li>Secure injection of custom parameters into the message metadata |
129 | 136 | * <li>Message execution (streaming or non-streaming based on config) |
130 | 137 | * <li>Agent state persistence after execution |
@@ -152,47 +159,41 @@ private Mono<ToolResultBlock> executeConversation(ToolCallParam param) { |
152 | 159 | return Mono.just(ToolResultBlock.error("Message is required")); |
153 | 160 | } |
154 | 161 |
|
155 | | - // Extract custom external parameters (filter out system built-in keys) |
| 162 | + // Extract external parameters |
156 | 163 | Map<String, Object> finalMetadata = new HashMap<>(); |
| 164 | + ToolExecutionContext context = param.getContext(); |
| 165 | + |
| 166 | + // 1. Extract system parameters (Securely injected, invisible to LLM) |
| 167 | + Set<String> declaredSystemParams = config.getSystemParameters(); |
| 168 | + if (declaredSystemParams != null && !declaredSystemParams.isEmpty()) { |
| 169 | + for (String paramName : declaredSystemParams) { |
| 170 | + Object ctxValue = extractFromContext(context, paramName); |
| 171 | + if (ctxValue != null) { |
| 172 | + finalMetadata.put(paramName, ctxValue); |
| 173 | + } else { |
| 174 | + logger.warn( |
| 175 | + "System parameter '{}' was declared but not provided in" |
| 176 | + + " ToolExecutionContext", |
| 177 | + paramName); |
| 178 | + } |
| 179 | + } |
| 180 | + } |
| 181 | + |
| 182 | + // 2. Extract custom parameters (LLM inferred, Context overrides LLM) |
157 | 183 | Map<String, Map<String, Object>> declaredCustomParams = |
158 | 184 | config.getCustomParameters(); |
159 | | - |
160 | 185 | if (declaredCustomParams != null && !declaredCustomParams.isEmpty()) { |
161 | 186 | for (String paramName : declaredCustomParams.keySet()) { |
162 | 187 | boolean injectedFromContext = false; |
163 | 188 |
|
164 | 189 | // Prioritize obtaining from ToolExecutionContext |
165 | | - ToolExecutionContext context = param.getContext(); |
166 | | - if (context != null) { |
167 | | - // sequentially try the standard JSON types that could be passed |
168 | | - // to the LLM |
169 | | - Object ctxValue = context.get(paramName, String.class); |
170 | | - if (ctxValue == null) { |
171 | | - ctxValue = context.get(paramName, Integer.class); |
172 | | - } |
173 | | - if (ctxValue == null) { |
174 | | - ctxValue = context.get(paramName, Boolean.class); |
175 | | - } |
176 | | - if (ctxValue == null) { |
177 | | - ctxValue = context.get(paramName, Double.class); |
178 | | - } |
179 | | - if (ctxValue == null) { |
180 | | - ctxValue = context.get(paramName, Map.class); |
181 | | - } |
182 | | - if (ctxValue == null) { |
183 | | - ctxValue = context.get(paramName, List.class); |
184 | | - } |
185 | | - if (ctxValue == null) { |
186 | | - ctxValue = context.get(paramName, Object.class); |
187 | | - } |
188 | | - |
189 | | - if (ctxValue != null) { |
190 | | - finalMetadata.put(paramName, ctxValue); |
191 | | - injectedFromContext = true; |
192 | | - } |
| 190 | + Object ctxValue = extractFromContext(context, paramName); |
| 191 | + if (ctxValue != null) { |
| 192 | + finalMetadata.put(paramName, ctxValue); |
| 193 | + injectedFromContext = true; |
193 | 194 | } |
194 | 195 |
|
195 | | - // Attempt to retrieve from input |
| 196 | + // Attempt to retrieve from LLM input if not injected by context |
196 | 197 | if (!injectedFromContext && input.containsKey(paramName)) { |
197 | 198 | finalMetadata.put(paramName, input.get(paramName)); |
198 | 199 | } |
@@ -248,6 +249,39 @@ private Mono<ToolResultBlock> executeConversation(ToolCallParam param) { |
248 | 249 | }); |
249 | 250 | } |
250 | 251 |
|
| 252 | + /** |
| 253 | + * Extracts a value from the ToolExecutionContext by trying standard JSON types. |
| 254 | + * |
| 255 | + * @param context The execution context |
| 256 | + * @param paramName The name of the parameter to extract |
| 257 | + * @return The extracted value, or null if not found |
| 258 | + */ |
| 259 | + private Object extractFromContext(ToolExecutionContext context, String paramName) { |
| 260 | + if (context == null) { |
| 261 | + return null; |
| 262 | + } |
| 263 | + Object ctxValue = context.get(paramName, String.class); |
| 264 | + if (ctxValue == null) { |
| 265 | + ctxValue = context.get(paramName, Integer.class); |
| 266 | + } |
| 267 | + if (ctxValue == null) { |
| 268 | + ctxValue = context.get(paramName, Boolean.class); |
| 269 | + } |
| 270 | + if (ctxValue == null) { |
| 271 | + ctxValue = context.get(paramName, Double.class); |
| 272 | + } |
| 273 | + if (ctxValue == null) { |
| 274 | + ctxValue = context.get(paramName, Map.class); |
| 275 | + } |
| 276 | + if (ctxValue == null) { |
| 277 | + ctxValue = context.get(paramName, List.class); |
| 278 | + } |
| 279 | + if (ctxValue == null) { |
| 280 | + ctxValue = context.get(paramName, Object.class); |
| 281 | + } |
| 282 | + return ctxValue; |
| 283 | + } |
| 284 | + |
251 | 285 | /** |
252 | 286 | * Loads agent state from the session storage. |
253 | 287 | * |
|
0 commit comments