ag-ui-protocol / ag-ui-protocol/ag-ui

[Bug]: ADK function calls with omitted IDs or arguments are incorrectly converted

Abierto
#2,240 1 comentario 0 reacciones 0 asignados Ver en GitHub
Lenguaje dominante
Python
Estrellas
15.9k
Forks
1.4k
Merge medio
1 d 17 h
PR fusionados (30 d)
163

Descripción

# [Bug]: ADK function calls with omitted IDs or arguments are incorrectly converted

## Pre-flight Checklist

- [x] I have searched [existing issues](https://github.com/ag-ui-protocol/ag-ui/issues) and this hasn't been reported yet.
- [x] I am using the **latest** version AG-UI.

## Describe the Bug

`convert_adk_event_to_ag_ui_message` incorrectly handles two optional fields on `google.genai.types.FunctionCall`:

1. When `FunctionCall.id` is `None`, the complete assistant event is dropped.
2. When `FunctionCall.args` is `None`, the resulting AG-UI argument string is `"None"`, which is not valid JSON.

Both problems come from checking whether the GenAI model attributes exist rather than checking their values. Real `types.FunctionCall` objects always have the optional `id` and `args` attributes, with each field defaulting to `None`.

The ID conversion currently uses:

```python
id=getattr(part.function_call, "id", event.id)
```

Because the `id` attribute exists, this returns `None` rather than `event.id`. Constructing an AG-UI `ToolCall` then raises a validation error because `ToolCall.id` must be a string. The converter's broad exception handler catches the error and returns `None`, silently dropping the entire event.

The argument conversion currently uses:

```python
arguments=(
serialize_tool_args(part.function_call.args)
if hasattr(part.function_call, "args")
else "{}"
)
```

Because the `args` attribute exists, `serialize_tool_args(None)` is called and returns `"None"`. Downstream clients expecting JSON fail when they parse the arguments.

Affected file:

`integrations/adk-middleware/python/src/ag_ui_adk/utils/converters.py`

Affected function:

`convert_adk_event_to_ag_ui_message`

## Steps to Reproduce

### Case 1: omitted function-call ID

1. Run:

```python
from google.adk.events import Event
from google.genai import types

from ag_ui_adk.utils.converters import convert_adk_event_to_ag_ui_message

event = Event(
id="event-123",
author="model",
content=types.Content(
role="model",
parts=[
types.Part(
function_call=types.FunctionCall(
name="get_weather",
args={"city": "Cairo"},
# id defaults to None
)
)
],
),
)

print(event.content.parts[0].function_call.id)
print(convert_adk_event_to_ag_ui_message(event))
```

2. Observe:

```text
None
None
```

3. The converter logs:

```text
Error converting ADK event event-123: 1 validation error for ToolCall
id
Input should be a valid string
```

### Case 2: omitted function-call arguments

1. Run:

```python
import json

from google.adk.events import Event
from google.genai import types

from ag_ui_adk.utils.converters import convert_adk_event_to_ag_ui_message

event = Event(
id="event-456",
author="model",
content=types.Content(
role="model",
parts=[
types.Part(
function_call=types.FunctionCall(
id="call-456",
name="get_current_time",
# args defaults to None
)
)
],
),
)

message = convert_adk_event_to_ag_ui_message(event)
arguments = message.tool_calls[0].function.arguments

print(repr(arguments))
json.loads(arguments)
```

2. Observe:

```text
'None'
```

3. Parsing fails with:

```text
json.decoder.JSONDecodeError: Expecting value: line 1 column 1 (char 0)
```

## Expected Behavior

When `FunctionCall.id` is `None`, the converter should use the containing event ID as its existing fallback intends:

```python
message.tool_calls[0].id == "event-123"
```

When `FunctionCall.args` is `None`, the converter should emit an empty JSON object:

```python
message.tool_calls[0].function.arguments == "{}"
json.loads(message.tool_calls[0].function.arguments) == {}
```

In both cases, the assistant event and function call should be preserved, and the generated AG-UI tool call should be valid.

## Environment

```text
Repository: ag-ui-protocol/ag-ui
Component: integrations/adk-middleware/python
ag-ui-adk: 0.7.0
ag-ui-protocol: 0.1.19
google-adk: 1.35.0
google-genai: 1.75.0
Python: 3.13.7
OS: Windows
```

## Screenshots

Not applicable.

## Logs & Errors

```shell
Error converting ADK event event-123: 1 validation error for ToolCall
id
Input should be a valid string [type=string_type, input_value=None, input_type=NoneType]

json.decoder.JSONDecodeError: Expecting value: line 1 column 1 (char 0)
```

## Additional Context

The converter already contains tests named:

- `test_convert_function_call_without_id`
- `test_convert_function_call_without_args`

However, both tests use `MagicMock` and delete the respective attribute. That exercises objects where the attributes do not exist, unlike real `google.genai.types.FunctionCall` instances, where the attributes exist and contain `None`.

A minimal fix is:

```python
function_call = part.function_call
tool_call_id = getattr(function_call, "id", None) or event.id
args = getattr(function_call, "args", None)

tool_calls.append(
ToolCall(
id=tool_call_id,
type="function",
function=FunctionCall(
name=function_call.name,
arguments=serialize_tool_args(
args if args is not None else {}
),
),
)
)
```

Regression tests should construct real `types.FunctionCall` objects:

- One with `id=None`, asserting that the event ID is used.
- One with `args=None`, asserting that the result is `"{}"` and parses as JSON.

Guía de contribución

Abrir la guía de contribución

Evaluación

Este issue todavía no se ha evaluado.

Recibe los nuevos issues en tu correo

Un resumen breve de issues de GitHub para principiantes.