feat(agentic-ai): support fromAi() FEEL function to define ad-hoc tools schema#4635
feat(agentic-ai): support fromAi() FEEL function to define ad-hoc tools schema#4635
Conversation
3b92c41 to
5a042e5
Compare
There was a problem hiding this comment.
Pull Request Overview
This PR introduces support for the FEEL function fromAi() to define ad-hoc tool schemas by refactoring the schema extraction logic from JSON documentation to dedicated function calls. Key changes include a record renaming from toolsToCall to toolCalls, updates to schema generation using extracted FEEL parameters, and adjustments in e2e tests to reflect the renaming and additional tool specifications.
Reviewed Changes
Copilot reviewed 20 out of 29 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
| AgentResponse.java | Renamed field and record from ToolToCall to ToolCall with associated metadata handling. |
| DefaultAiAgentRequestHandler.java | Updated extraction and state checking to use toolCalls instead of toolsToCall. |
| DefaultAdHocToolSchemaGenerator.java | Revised schema generation logic using FEEL input parameters. |
| CamundaClientAdHocToolsSchemaResolver.java | Modified documentation extraction with Optional and enhanced FEEL parameter extraction from I/O mappings. |
| AdHocToolsSchemaResponse.java | Changed the tool definition record to use a Map for the inputSchema instead of a custom JsonSchema record. |
| FeelInputParamExtractor.java and related FEEL classes | Implemented FEEL function invocation extraction for fromAi() and updated parameter conversion logic. |
| AiAgentTests.java | Updated test expectations to reflect the renaming and additional tool specification in the tools schema. |
Files not reviewed (9)
- connectors-e2e-test/connectors-e2e-test-agentic-ai/src/test/resources/agentic-ai-connectors.bpmn: Language not supported
- connectors-e2e-test/connectors-e2e-test-agentic-ai/src/test/resources/expected-schema-result.json: Language not supported
- connectors/agentic-ai/examples/ad-hoc-tools-schema/ad-hoc-tools-schema-resolver.bpmn: Language not supported
- connectors/agentic-ai/examples/ai-agent-chat-with-tools/ai-agent-chat-human-send-email-request.form: Language not supported
- connectors/agentic-ai/examples/ai-agent-chat-with-tools/ai-agent-chat-initial-request.form: Language not supported
- connectors/agentic-ai/examples/ai-agent-chat-with-tools/ai-agent-chat-user-feedback.form: Language not supported
- connectors/agentic-ai/examples/fraud-detection/fraud-detection-process-enter-tax-form.form: Language not supported
- connectors/agentic-ai/examples/fraud-detection/fraud-detection-process-send-email.form: Language not supported
- connectors/agentic-ai/examples/fraud-detection/fraud-detection-process-view-tax-details.form: Language not supported
Comments suppressed due to low confidence (2)
connectors-e2e-test/connectors-e2e-test-agentic-ai/src/test/java/io/camunda/connector/e2e/agenticai/AiAgentTests.java:392
- The updated test now expects five tool specification names, including "A_Complex_Tool". Please verify that this change accurately reflects the intended behavior of the tool specification extraction.
.containsExactly("GetDateAndTime", "SuperfluxProduct", "Search_The_Web", "A_Complex_Tool", "An_Event");
connectors/agentic-ai/src/main/java/io/camunda/connector/agenticai/adhoctoolsschema/resolver/CamundaClientAdHocToolsSchemaResolver.java:87
- The getDocumentation method now returns an Optional and filters out blank documentation. Please ensure that this change does not unintentionally bypass documentation processing for elements without explicit documentation.
private Optional<String> getDocumentation(FlowNode element) {
5a042e5 to
994fd52
Compare
| import scala.Product; | ||
| import scala.jdk.javaapi.CollectionConverters; | ||
|
|
||
| class FeelFunctionInvocationExtractor { |
There was a problem hiding this comment.
Finds function invocations for a given function name in an already parsed expression. The class is package-private as it is only used from the parameter extractor right now.
994fd52 to
bb2afa4
Compare
| import java.util.List; | ||
| import java.util.Map; | ||
|
|
||
| public interface AdHocToolSchemaGenerator { |
There was a problem hiding this comment.
FYI this is singular Tool Schema opposed to plural Tools Schema like the function as it is only handling the schema for a single tool.
bb2afa4 to
f87c0d0
Compare
| throws JsonProcessingException { | ||
| assertThat(agentResponse).isNotNull(); | ||
| assertThat(agentResponse.toolsToCall()).isEmpty(); | ||
| assertThat(agentResponse.toolCalls()).isEmpty(); |
There was a problem hiding this comment.
We renamed toolsToCall to toolCalls throughout the integration.
| } | ||
| }, | ||
| { | ||
| "name": "A_Complex_Tool", |
| private AdHocToolDefinition mapActivityToToolDefinition(FlowNode element) { | ||
| final var documentation = getDocumentation(element); | ||
|
|
||
| if (!documentation.isBlank() && documentation.trim().startsWith("{")) { |
There was a problem hiding this comment.
No JSON parsing on the documentation anymore - instead we extract fromAi function calls from input & output mappings.
There was a problem hiding this comment.
FYI I added the current working use cases to the examples folder so we can develop them in parallel with the implementation. If needed we can also do this on a dedicated PR. In terms of review I think we can ignore them.
…m input expressions
…om fromAi expressions instead of the documentation block Will take all fromAi() calls in input and output mappings and use them to create a schema.
… once with the same name
… from as first parameter
…aking the parameter name directly from the value read operation
…hem when building tools schema
This allows to test schema generation in isolation and to potentially implement a specialized version if needed.
…Ai() functionality
…tion invocation extractor
…nstant strings and add error test cases for FeelInputParamExtractor
f87c0d0 to
d15410e
Compare
…c ai connector as it is already defined in parent

Description
See #4634 for background. This PR moves the definition of inputs which need to be provided by an LLM in an agentic context from a JSON schema in the
documentationblock to individualfromAi()function calls which define the metadata/schema of the input properties.Instead of parsing a JSON schema from the
documentationelement, the tools schema resolver now looks forfromAiFEEL function calls in input and output mappings and collects them to generate the tool schema. The first argument to thefromAi()function needs to reference the injected variable in thetoolCallobject which contains all LLM-generated inputs.In addition, this PR renames
toolToCall/toolsToCalltotoolCall/toolCalls(reflected in the e2e tests).Note: I added the current working use cases to the
examplesfolder so we can develop them in parallel with the implementation. If needed we can also do this on a dedicated PR. In terms of review I think we can ignore them.Examples
A task with an input mapping containing
fromAi(toolCall.myVariable, "This is our first variable")will result in the following JSON schema. If multiplefromAicalls are used in the same task, they are all added to the task schema.{ "name": "MyTask", "description": "Some description.", "inputSchema": { "type": "object", "properties": { "myVariable": { "type": "string", "description": "This is our first variable" } }, "required": [ "myVariable" ] } }Mappings can define very simple inputs (defaulting to string):
Or more complex ones, including a schema:
An optional last parameter
optionssupports passing a map of options which are currently unused, but reserved for future use (e.g. to provide metadata, mark properties as optional, ...).Parameter name resolution
The examples above showcase the value being defined as
toolCall.myVariablewhich will be a pattern implemented by this connector. The engine implementation offromAidoes not restrict the value format, but the parsing implementation requires the value to be an object dereference as doing so allows us to use the field name as variable name.In the example above, a variable
myVariableis derived from thefromAifunction call.Schema extraction logic
As all parameters besides the value are optional, the schema is built in the following way:
schemaproperty is defined, its value be used as the initial schema. Otherwise, an empty JSON schema object is initialized to{}.typeproperty is set, it will be applied to the schema ({"type": "number"}), potentially overwriting a type which was directly defined on the schema object.schemaandtypeproperties, a default value of{"type": "string"}will be set.descriptionproperty is set, it will be applied to the schema ({"description": "My property"}), potentially overwriting a description which was directly defined on the schema object.This process is repeated for every parameter and combined to a task schema like in the example above. In this implementation, all properties are defined as required, but this can be adapted in a follow-up if needed.
Repeated usage of the same input value name
A
fromAi()function for a certain value can only be used once per task to avoid ambiguity, however as the value part accesses an existing value, a value can be directly referenced in case it is needed multiple times:The following will raise an incident due to the repeated usage of the same value ❌
Related issues
Based on #4548.
Depends on camunda/camunda#31259 to implement
fromAi()support in the engine ✅closes #4634
Checklist
no milestonelabel.