Skip to content

feat(agentic-ai): support fromAi() FEEL function to define ad-hoc tools schema#4635

Merged
maff merged 19 commits intomainfrom
feature/4634-fromAi-feel-function-schema
Apr 28, 2025
Merged

feat(agentic-ai): support fromAi() FEEL function to define ad-hoc tools schema#4635
maff merged 19 commits intomainfrom
feature/4634-fromAi-feel-function-schema

Conversation

@maff
Copy link
Member

@maff maff commented Apr 23, 2025

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 documentation block to individual fromAi() function calls which define the metadata/schema of the input properties.

Instead of parsing a JSON schema from the documentation element, the tools schema resolver now looks for fromAi FEEL function calls in input and output mappings and collects them to generate the tool schema. The first argument to the fromAi() function needs to reference the injected variable in the toolCall object which contains all LLM-generated inputs.

In addition, this PR renames toolToCall/toolsToCall to toolCall/toolCalls (reflected in the e2e tests).

Note: 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.

Examples

A task with an input mapping containing fromAi(toolCall.myVariable, "This is our first variable") will result in the following JSON schema. If multiple fromAi calls 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):

fromAi(toolCall.userId)

Or more complex ones, including a schema:

fromAi(toolCall.anEnumValue, "An enum value", "string", { enum: ["A", "B", "C"] })

An optional last parameter options supports 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.myVariable which will be a pattern implemented by this connector. The engine implementation of fromAi does 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 myVariable is derived from the fromAi function call.

Schema extraction logic

As all parameters besides the value are optional, the schema is built in the following way:

  • If the schema property is defined, its value be used as the initial schema. Otherwise, an empty JSON schema object is initialized to {}.
  • If the type property is set, it will be applied to the schema ({"type": "number"}), potentially overwriting a type which was directly defined on the schema object.
  • If no type is set after reading schema and type properties, a default value of {"type": "string"} will be set.
  • If the description property 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:

{
  first: fromAi(toolCall.first, "The first parameter", "number"),
  second: fromAi(toolCall.second, "The second parameter", "string", { enum: ["A", "B", "C"] }),
  allParams: [toolCall.first, toolCall.second]
}

The following will raise an incident due to the repeated usage of the same value ❌

{
  first: fromAi(toolCall.first, "The first parameter", "number"),
  first2: fromAi(toolCall.first, "Testme")
}

Related issues

Based on #4548.
Depends on camunda/camunda#31259 to implement fromAi() support in the engine ✅

closes #4634

Checklist

  • PR has a milestone or the no milestone label.

@maff maff changed the base branch from main to feature/4545-4547-ad-hoc-tools-schema-ai-agent-connectors April 23, 2025 09:05
@maff maff self-assigned this Apr 23, 2025
@maff maff added this to the 8.8.0-alpha4 milestone Apr 23, 2025
@maff maff force-pushed the feature/4634-fromAi-feel-function-schema branch 2 times, most recently from 3b92c41 to 5a042e5 Compare April 23, 2025 14:07
@maff maff requested a review from Copilot April 23, 2025 14:11
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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) {

@maff maff force-pushed the feature/4634-fromAi-feel-function-schema branch from 5a042e5 to 994fd52 Compare April 23, 2025 14:17
import scala.Product;
import scala.jdk.javaapi.CollectionConverters;

class FeelFunctionInvocationExtractor {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Base automatically changed from feature/4545-4547-ad-hoc-tools-schema-ai-agent-connectors to main April 23, 2025 14:59
@maff maff force-pushed the feature/4634-fromAi-feel-function-schema branch from 994fd52 to bb2afa4 Compare April 23, 2025 15:24
import java.util.List;
import java.util.Map;

public interface AdHocToolSchemaGenerator {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@maff maff marked this pull request as ready for review April 23, 2025 15:35
@maff maff requested a review from a team as a code owner April 23, 2025 15:35
@maff maff force-pushed the feature/4634-fromAi-feel-function-schema branch from bb2afa4 to f87c0d0 Compare April 25, 2025 08:31
throws JsonProcessingException {
assertThat(agentResponse).isNotNull();
assertThat(agentResponse.toolsToCall()).isEmpty();
assertThat(agentResponse.toolCalls()).isEmpty();
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We renamed toolsToCall to toolCalls throughout the integration.

}
},
{
"name": "A_Complex_Tool",
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Showcases extracting a complex schema from nested properties.

image

private AdHocToolDefinition mapActivityToToolDefinition(FlowNode element) {
final var documentation = getDocumentation(element);

if (!documentation.isBlank() && documentation.trim().startsWith("{")) {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No JSON parsing on the documentation anymore - instead we extract fromAi function calls from input & output mappings.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@maff maff requested a review from gbetances089 April 25, 2025 09:04
@maff maff force-pushed the feature/4634-fromAi-feel-function-schema branch from f87c0d0 to d15410e Compare April 28, 2025 06:56
…c ai connector as it is already defined in parent
@maff maff added this pull request to the merge queue Apr 28, 2025
Merged via the queue into main with commit 45c3d7f Apr 28, 2025
15 checks passed
@maff maff deleted the feature/4634-fromAi-feel-function-schema branch April 28, 2025 08:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Agentic AI: define ad-hoc tools schema via transparent fromAi function instead of a JSON schema

4 participants