Skip to content

Refactor built-in AppFunctions to internal tools and make them actually working - #46

Open
ksemenova wants to merge 4 commits into
mainfrom
make_af_internal_tools
Open

Refactor built-in AppFunctions to internal tools and make them actually working#46
ksemenova wants to merge 4 commits into
mainfrom
make_af_internal_tools

Conversation

@ksemenova

@ksemenova ksemenova commented Jul 31, 2026

Copy link
Copy Markdown

Summary

This PR refactors 3 built-in AppFunctions to run as internal agent tools, cleans up unused service declarations, and fixes an argument conversion failure (ClassCastException) during integration with external apps.

Before

3946

After

3945

Key Changes

  1. Internal Tools Refactoring:
    • Moved geocodeAddress, getCurrentLocation, and generateImage from BaseBuiltInAppFunctionService to a new AgentInternalTools class.
    • Intercepted tool execution in AgentOrchestrator to execute in-process and simplified the flow to return Kotlin Result<String>.
    • Deleted BaseBuiltInAppFunctionService.kt and removed its service declaration from AndroidManifest.xml.
  2. Robust Argument Conversion:
    • Fixed ClassCastException where the LLM passes a raw String URI to a custom object or array parameter (e.g., ChatApp's Attachment).
    • Introduced automatic fallback wrapping of String values into the target object's primary Uri or String property inside ConvertInputToAppFunctionDataUseCase.toAppFunctionData().
    • Added unit tests covering both singular and list-based auto-wrapping scenarios.

…ent conversion fallback

- Moved `geocodeAddress`, `getCurrentLocation`, and `generateImage` to `AgentInternalTools`.
- Updated `AgentOrchestrator` to intercept and execute internal tools in-process, returning `Result<String>`.
- Removed `BuiltInAppFunctionService` and cleaned up `AndroidManifest.xml`.
- Resolved argument mapping `ClassCastException` by adding a fallback to wrap string inputs (e.g., URIs) into custom serializable object/array parameters in `ConvertInputToAppFunctionDataUseCase`.
- Refactored argument conversion to use a unified `toAppFunctionData` helper.
@ksemenova
ksemenova requested a review from a team as a code owner July 31, 2026 02:16
}

private fun convertObject(
private fun toAppFunctionData(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I'm not quite sure why this is needed. The Uri handling is done separately already, what was the issue this is trying to resolve?

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

and also why can't those internal tools to be implemented as app functions? :)

I thought they were "actually working" before the change...

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

To clarify:

  • We expect 3P apps to expose AppFunctions to the agent (not vice versa), and exposing sensitive data like location publicly is not recommended.
  • The previous AFs were not working as shown on the screenshot above. Possibly because the agent was not able to discover and register them at runtime. I moved these methods out of the AppFunction framework into Agent's internal tools. We now load these 3 tools statically from code (agentInternalTools.getInternalToolsMetadata()) and append them directly to the LLM tool list, bypassing the system's AppFunction indexing entirely. The model is now guaranteed to see these tools.
  • When I moved AFs to internal tools I was able to call them succesfully. Then, to test multiple tools calls in one prompt, I asked to generate an image and send it to my my contact in ChatApp (that has send_message AF) and I've got this error: Error: Failed to convert arguments for com.example.chatapp.appfunctions.AppFunctions#send java.lang.ClassCastException: java.lang.String cannot be cast to java.util.Map. This fix resolves a ClassCastException that occurs when the LLM passes a raw String URI to a tool parameter expecting a custom wrapper object (like ChatApp's Attachment), instead of constructing the full nested object structure.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Error: Failed to convert arguments for com.example.chatapp.appfunctions.AppFunctions#send java.lang.ClassCastException: java.lang.String cannot be cast to java.util.Map. This fix resolves a ClassCastException that occurs when the LLM passes a raw String URI to a tool parameter expecting a custom wrapper object (like ChatApp's Attachment), instead of constructing the full nested object structure.

I see. Thanks for the context.

However, I think the fix should be doing

if (dataType.qualifiedName == "android.net.Uri" && value is String) {
                        val uriPropertyName = dataType.properties.keys.firstOrNull() ?: "uri"
                        val uriData =
                            AppFunctionData.Builder(dataType, components)
                                .setString(uriPropertyName, value)
                                .build()
                        builder.setAppFunctionData(name, uriData)
                    }

in setArrayValue when the type is AppFunctionObjectTypeMetadata or AppFunctionReferenceTypeMetadata.

Because the issue is that we didn't handle that scenario when the value is List.

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.

4 participants