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

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

Offen
#2,240 1 Kommentar 0 Reaktionen 0 zugewiesene Personen Auf GitHub ansehen
Vorherrschende Sprache
Python
Sterne
15.9k
Forks
1.4k
Ø Merge
1 T. 17 Std.
Gemergte PRs (30 T.)
163

Beschreibung

# [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.

Beitragsleitfaden

Beitragsleitfaden öffnen

Bewertung

Dieses Issue wurde noch nicht bewertet.

Neue Issues direkt in Ihr Postfach

Eine kurze Übersicht über anfängerfreundliche GitHub-Issues.