Repository navigation
Conversation
There was a problem hiding this comment.
Code Review
This pull request introduces RendererFunctionCalls to allow agents to call renderer functions and wait for their results, managing timeouts, concurrent call caps, and disposal. It also adds the TOO_MANY_PENDING RPC error code to a2ui_core and includes comprehensive unit, conformance, and end-to-end tests. The review feedback suggests retaining explicit type annotations on loop variables when iterating over non-literal collections in renderer_function_calls.dart and renderer_function_calls_conformance_test.dart to comply with the project's style guidelines.
af13dcc to
1d69e36
Compare
1d69e36 to
935fa91
Compare
Audit the agent functions feature blueprint against the Python, TS and Dart branches and fix the drift: - The version check runs first for every message kind, so a callAgentFunction with a missing or other version raises even with a functionCallId; a schema-failing call with a readable id still gets an INVALID_FUNCTION_CALL reply in all three SDKs. - The timeout option is agent_function_timeout (seconds) in Python, agentFunctionTimeoutMs in TypeScript and Duration agentFunctionTimeout in Dart; the sketch now uses a neutral name. - The callback signatures per SDK, including Dart's FutureOr<void> callbacks that still decline with false. - Only Python's a2ui.rpc re-exports the message classes; a validation error raised by a handler is INVALID_FUNCTION_CALL in Dart only. Replace the follow-up note with section 5, the agent calling renderer functions as implemented by #3939, #3940 and #3941: RendererFunctionCalls, its checks and error codes, timeouts, resolve returning whether it owned the response, malformed responses, disposal, the optional catalog checks, the in-memory limitation on stateless servers, and the ADK integration. Requirements, links, conformance (renderer_function_calls.yaml, 23 cases) and implementation steps follow. The Python agent codebase blueprint says renderer calls come in the follow-up #3939. Signed-off-by: Nan Yu <nanyu@google.com>
935fa91 to
f451496
Compare
Audit the agent functions feature blueprint against the Python, TS and Dart branches and fix the drift: - The version check runs first for every message kind, so a callAgentFunction with a missing or other version raises even with a functionCallId; a schema-failing call with a readable id still gets an INVALID_FUNCTION_CALL reply in all three SDKs. - The timeout option is agent_function_timeout (seconds) in Python, agentFunctionTimeoutMs in TypeScript and Duration agentFunctionTimeout in Dart; the sketch now uses a neutral name. - The callback signatures per SDK, including Dart's FutureOr<void> callbacks that still decline with false. - Only Python's a2ui.rpc re-exports the message classes; a validation error raised by a handler is INVALID_FUNCTION_CALL in Dart only. Replace the follow-up note with section 5, the agent calling renderer functions as implemented by #3939, #3940 and #3941: RendererFunctionCalls, its checks and error codes, timeouts, resolve returning whether it owned the response, malformed responses, disposal, the optional catalog checks, the in-memory limitation on stateless servers, and the ADK integration. Requirements, links, conformance (renderer_function_calls.yaml, 23 cases) and implementation steps follow. The Python agent codebase blueprint says renderer calls come in the follow-up #3939. Signed-off-by: Nan Yu <nanyu@google.com>
Audit the agent functions feature blueprint against the Python, TS and Dart branches and fix the drift: - The version check runs first for every message kind, so a callAgentFunction with a missing or other version raises even with a functionCallId; a schema-failing call with a readable id still gets an INVALID_FUNCTION_CALL reply in all three SDKs. - The timeout option is agent_function_timeout (seconds) in Python, agentFunctionTimeoutMs in TypeScript and Duration agentFunctionTimeout in Dart; the sketch now uses a neutral name. - The callback signatures per SDK, including Dart's FutureOr<void> callbacks that still decline with false. - Only Python's a2ui.rpc re-exports the message classes; a validation error raised by a handler is INVALID_FUNCTION_CALL in Dart only. Replace the follow-up note with section 5, the agent calling renderer functions as implemented by #3939, #3940 and #3941: RendererFunctionCalls, its checks and error codes, timeouts, resolve returning whether it owned the response, malformed responses, disposal, the optional catalog checks, the in-memory limitation on stateless servers, and the ADK integration. Requirements, links, conformance (renderer_function_calls.yaml, 23 cases) and implementation steps follow. The Python agent codebase blueprint says renderer calls come in the follow-up #3939. Signed-off-by: Nan Yu <nanyu@google.com>
f451496 to
47fdc16
Compare
Audit the agent functions feature blueprint against the Python, TS and Dart branches and fix the drift: - The version check runs first for every message kind, so a callAgentFunction with a missing or other version raises even with a functionCallId; a schema-failing call with a readable id still gets an INVALID_FUNCTION_CALL reply in all three SDKs. - The timeout option is agent_function_timeout (seconds) in Python, agentFunctionTimeoutMs in TypeScript and Duration agentFunctionTimeout in Dart; the sketch now uses a neutral name. - The callback signatures per SDK, including Dart's FutureOr<void> callbacks that still decline with false. - Only Python's a2ui.rpc re-exports the message classes; a validation error raised by a handler is INVALID_FUNCTION_CALL in Dart only. Replace the follow-up note with section 5, the agent calling renderer functions as implemented by #3939, #3940 and #3941: RendererFunctionCalls, its checks and error codes, timeouts, resolve returning whether it owned the response, malformed responses, disposal, the optional catalog checks, the in-memory limitation on stateless servers, and the ADK integration. Requirements, links, conformance (renderer_function_calls.yaml, 23 cases) and implementation steps follow. The Python agent codebase blueprint says renderer calls come in the follow-up #3939. Signed-off-by: Nan Yu <nanyu@google.com>
47fdc16 to
8d78baf
Compare
Audit the agent functions feature blueprint against the Python, TS and Dart branches and fix the drift: - The version check runs first for every message kind, so a callAgentFunction with a missing or other version raises even with a functionCallId; a schema-failing call with a readable id still gets an INVALID_FUNCTION_CALL reply in all three SDKs. - The timeout option is agent_function_timeout (seconds) in Python, agentFunctionTimeoutMs in TypeScript and Duration agentFunctionTimeout in Dart; the sketch now uses a neutral name. - The callback signatures per SDK, including Dart's FutureOr<void> callbacks that still decline with false. - Only Python's a2ui.rpc re-exports the message classes; a validation error raised by a handler is INVALID_FUNCTION_CALL in Dart only. Replace the follow-up note with section 5, the agent calling renderer functions as implemented by #3939, #3940 and #3941: RendererFunctionCalls, its checks and error codes, timeouts, resolve returning whether it owned the response, malformed responses, disposal, the optional catalog checks, the in-memory limitation on stateless servers, and the ADK integration. Requirements, links, conformance (renderer_function_calls.yaml, 23 cases) and implementation steps follow. The Python agent codebase blueprint says renderer calls come in the follow-up #3939. Signed-off-by: Nan Yu <nanyu@google.com>
8d78baf to
c6e6ba9
Compare
Audit the agent functions feature blueprint against the Python, TS and Dart branches and fix the drift: - The version check runs first for every message kind, so a callAgentFunction with a missing or other version raises even with a functionCallId; a schema-failing call with a readable id still gets an INVALID_FUNCTION_CALL reply in all three SDKs. - The timeout option is agent_function_timeout (seconds) in Python, agentFunctionTimeoutMs in TypeScript and Duration agentFunctionTimeout in Dart; the sketch now uses a neutral name. - The callback signatures per SDK, including Dart's FutureOr<void> callbacks that still decline with false. - Only Python's a2ui.rpc re-exports the message classes; a validation error raised by a handler is INVALID_FUNCTION_CALL in Dart only. Replace the follow-up note with section 5, the agent calling renderer functions as implemented by #3939, #3940 and #3941: RendererFunctionCalls, its checks and error codes, timeouts, resolve returning whether it owned the response, malformed responses, disposal, the optional catalog checks, the in-memory limitation on stateless servers, and the ADK integration. Requirements, links, conformance (renderer_function_calls.yaml, 23 cases) and implementation steps follow. The Python agent codebase blueprint says renderer calls come in the follow-up #3939. Signed-off-by: Nan Yu <nanyu@google.com>
c6e6ba9 to
7a30bd6
Compare
Audit the agent functions feature blueprint against the Python, TS and Dart branches and fix the drift: - The version check runs first for every message kind, so a callAgentFunction with a missing or other version raises even with a functionCallId; a schema-failing call with a readable id still gets an INVALID_FUNCTION_CALL reply in all three SDKs. - The timeout option is agent_function_timeout (seconds) in Python, agentFunctionTimeoutMs in TypeScript and Duration agentFunctionTimeout in Dart; the sketch now uses a neutral name. - The callback signatures per SDK, including Dart's FutureOr<void> callbacks that still decline with false. - Only Python's a2ui.rpc re-exports the message classes; a validation error raised by a handler is INVALID_FUNCTION_CALL in Dart only. Replace the follow-up note with section 5, the agent calling renderer functions as implemented by #3939, #3940 and #3941: RendererFunctionCalls, its checks and error codes, timeouts, resolve returning whether it owned the response, malformed responses, disposal, the optional catalog checks, the in-memory limitation on stateless servers, and the ADK integration. Requirements, links, conformance (renderer_function_calls.yaml, 23 cases) and implementation steps follow. The Python agent codebase blueprint says renderer calls come in the follow-up #3939. Signed-off-by: Nan Yu <nanyu@google.com>
7a30bd6 to
833a091
Compare
gspencergoog
left a comment
There was a problem hiding this comment.
This pull request adds RendererFunctionCalls in dart/a2ui_agent and RpcErrorCode.tooManyPending in dart/a2ui_core to build outbound v1.0 callRendererFunction messages, validate call inputs and renderer catalog targets synchronously in the shared cross-SDK order, correlate incoming rendererFunctionResponse envelopes, and manage timeouts and disposal. Static analysis, formatting, unit and end-to-end tests, and all 31 conformance cases from conformance/agent/renderer_function_calls.yaml pass, with one schema-alignment fix needed in _parseResponse (and its paired unit test) when resolve is called directly without RendererToAgentMessageProcessor.
🤖 Agent-generated
Signed-off-by: Nan Yu <nanyu@google.com>
…n call RendererFunctionCalls.resolve returns true when it settled or failed a pending call and false otherwise, so as onRendererFunctionResponse it declines responses it doesn't own and they reach the model. Signed-off-by: Nan Yu <nanyu@google.com>
RendererFunctionCalls takes the renderer's catalogs; call then refuses a function the catalogs don't declare (UNKNOWN_FUNCTION), and one that is rendererOnly or requires a user activation (INVALID_FUNCTION_CALL), before anything is sent. Two catalogs with one id are a catalog error. Signed-off-by: Nan Yu <nanyu@google.com>
…ormance runner Build args.catalogs in code as v1.0 catalogs, and treat a top-level expect_error as building the helper failing. Signed-off-by: Nan Yu <nanyu@google.com>
An agent function in the agent's catalog can't run on the renderer, so a call to it gets UNKNOWN_FUNCTION. Signed-off-by: Nan Yu <nanyu@google.com>
Pass functionCallId from a call step, and meet the case with args that aren't an object by showing Dart's typed call can't take them. Signed-off-by: Nan Yu <nanyu@google.com>
Tells whether a call with a functionCallId is awaiting a response, as is_pending and isPending do in Python and TypeScript. Signed-off-by: Nan Yu <nanyu@google.com>
…rocessor Signed-off-by: Nan Yu <nanyu@google.com>
RendererFunctionCalls.resolve now holds a response to the v1.0 FunctionResponse schema, as RendererToAgentMessageProcessor and the Python SDK do: version '1.0' on raw maps and typed messages, exactly one of value and error (a void function sends value: null; error: null is not an error), and no keys beyond the schema's. A response that breaks it fails its pending call with UNKNOWN_ERROR. An error whose code is empty, which the schema allows, now settles the call with that code instead of failing it as malformed. Also pin that a response after dispose settles nothing. Signed-off-by: Nan Yu <nanyu@google.com>
The renderer function calls conformance runner now requires an expect_error to be a non-empty map of the keys the suite uses (category, code and message on a case; code and message on a step), so a typo or an empty expectation fails instead of passing silently. message is matched as a literal substring. Signed-off-by: Nan Yu <nanyu@google.com>
Signed-off-by: Nan Yu <nanyu@google.com>
Match conformance_schema.json: a step's expect_error is a RendererCallError (required code, optional message) and a case's is a RendererCallsBuildError (required category, only CatalogError, optional message), with no other keys. Also cover a version '0.9' response as malformed. Signed-off-by: Nan Yu <nanyu@google.com>
Audit the agent functions feature blueprint against the Python, TS and Dart branches and fix the drift: - The version check runs first for every message kind, so a callAgentFunction with a missing or other version raises even with a functionCallId; a schema-failing call with a readable id still gets an INVALID_FUNCTION_CALL reply in all three SDKs. - The timeout option is agent_function_timeout (seconds) in Python, agentFunctionTimeoutMs in TypeScript and Duration agentFunctionTimeout in Dart; the sketch now uses a neutral name. - The callback signatures per SDK, including Dart's FutureOr<void> callbacks that still decline with false. - Only Python's a2ui.rpc re-exports the message classes; a validation error raised by a handler is INVALID_FUNCTION_CALL in Dart only. Replace the follow-up note with section 5, the agent calling renderer functions as implemented by #3939, #3940 and #3941: RendererFunctionCalls, its checks and error codes, timeouts, resolve returning whether it owned the response, malformed responses, disposal, the optional catalog checks, the in-memory limitation on stateless servers, and the ADK integration. Requirements, links, conformance (renderer_function_calls.yaml, 23 cases) and implementation steps follow. The Python agent codebase blueprint says renderer calls come in the follow-up #3939. Signed-off-by: Nan Yu <nanyu@google.com>
833a091 to
b40f00d
Compare
gspencergoog
left a comment
There was a problem hiding this comment.
This pull request adds RendererFunctionCalls in dart/a2ui_agent and RpcErrorCode.tooManyPending (TOO_MANY_PENDING) in dart/a2ui_core to construct outbound v1.0 callRendererFunction messages, validate call inputs and renderer catalog targets synchronously in the shared cross-SDK order, correlate incoming rendererFunctionResponse messages, and manage per-call timeouts and disposal. Both Round 1 review comments (M1 strict v1.0 FunctionResponse validation in _parseResponse and L1 void/malformed response unit test updates) and both earlier style threads have been verified as resolved at HEAD, and dart analyze --fatal-infos and dart test pass with zero issues.
🤖 Agent-generated
Stacked on #3861. Review the top nine commits:
feat(dart-agent): call renderer functions from the agent (#3535), followed by the review-fix and conformance commits coveringRendererFunctionCalls.resolvedeclining unknown responses, catalog target checks, the shared validation order,isPending, and routing conformance responses throughRendererToAgentMessageProcessor. Draft until #3861 lands and the open questions below are settled.Part of #3535.
Description
#3861 first shipped the agent's calls to renderer functions (
callRendererFunction) together with agent functions, as the Python stack did. The review on #3793 questioned whether in-memory pending calls fit stateless HTTP servers, where therendererFunctionResponsecan arrive in a later request on another replica. To keep agent functions moving, renderer function calls were split out of #3861 into this PR in every SDK (Python #3939, TypeScript #3940). The code is the code that was on #3861, renamed to fitRendererToAgentMessageProcessor, with the review fixes below.RendererFunctionCallsina2ui_agent, one per conversation.call({required catalogId, required name, args, surfaceId, timeout, functionCallId})checks the call and returns a record of theCallRendererFunctionMessageto send and aFutureof the result.resolvecompletes the matching future with the renderer's value, or with anA2uiRpcErrorcarrying the renderer's code. It returnstruewhen it settled or failed a pending call, andfalseotherwise (an unknown, late or duplicate response).A call times out after
defaultRendererCallTimeout(30 seconds, as on the renderer side) unless given another timeout;defaultTimeout: nulldisables the default. Unknown, duplicate and late responses are ignored.maxPendingcaps the calls pending at once (TOO_MANY_PENDING), anddisposefails the pending calls withCANCELLEDand refuses later calls withDISPOSED.a2ui_coregetsRpcErrorCode.tooManyPending(TOO_MANY_PENDING), andpackage:a2ui_agent/a2ui_agent.dartalso re-exportsCallRendererFunctionMessage.fake_asyncis a new dev dependency for the timer tests.How it plugs into the processor: pass
calls.resolvetoRendererToAgentMessageProcessorasonRendererFunctionResponse, so the processor validates the response and hands it over. Since callbacks may returnfalseto decline (from the second review of feat(python-agent): answer agent functions through RendererToAgentMessageProcessor #3793), a responseresolvedoesn't own, such as one to a call the model wrote, comes backhandled: falseand can go on to the model. The processor's API from feat(dart): agent functions and RendererToAgentMessageProcessor #3861 doesn't change:The README gets a "Calling renderer functions" section, and both CHANGELOGs list the API.
Review fixes from #3793, applied here as in Python:
A2uiRpcError(invalidFunctionCall), like an emptycatalogId.surfaceIdcounts as omitted: the message leaves it out.rendererFunctionResponsethat names a pending call fails that call right away withunknownError, instead of leaving it to time out. One that names no pending call is ignored.FakeAsync.pendingTimersis empty afterwards), the counterpart of Python'stimer.cancelled()assertion.Open design questions, kept for discussion on #3939:
maxPendingand theTOO_MANY_PENDINGcode, which the protocol doesn't define. The agent's own code starts these calls, so a limit may not be needed.rendererFunctionResponsein its ownonRendererFunctionResponsecallback instead.Catalog checks (after review on #3793, #3793 (comment)):
RendererFunctionCallstakes an optionalcatalogsargument. With it,call()checks the target before it builds a message, registers a pending call or starts a timer. An unknowncatalogId, or a name that isn't one of that catalog's renderer functions (agent functions included), getsUNKNOWN_FUNCTION. ArendererOnlyfunction (or one that leaves outallowedCallers) getsINVALID_FUNCTION_CALL, and so does one withrequiresUserActivation: true, since a call from the agent has no user gesture. Two catalogs with the same ID are a catalog error. Withoutcatalogs, nothing changes. The conformance runner supports the newcatalogscases. Checked locally againstrenderer_function_calls.yamlfrom #3939: 31/31 (30 cases and the non-empty check).call()runs its checks in the same order in all three SDKs:functionCallIdmax_pendingcatalogIdargschecks: a JSON object, identifier keys, and JSON values (no NaN or infinity)Only the instance default timeout can be "no limit".
isPending(functionCallId)inspects whether a call is awaiting a response without settling it, matching Python and TypeScript.Changes after the latest review: Review:
_parseResponsereads responses as strict v1.0 (version "1.0", exactly one ofvalueanderror, no extra keys), so a malformed response fails a pending call withUNKNOWN_ERRORas in Python and TypeScript; tests updated (a void response sendsvalue: null); the conformance runner enforces the closedRendererCallErrorandRendererCallsBuildErrorshapes.Tests
renderer_function_calls_test.dart: constructor checks, the message and its value, void and error results, call validation, duplicate IDs,maxPending, timeouts anddispose, the timer cancelled on settle, an empty name, an emptysurfaceId, malformed responses with and without a pending call,resolveplugged intoRendererToAgentMessageProcessor, whatresolvereturns, and a response it declines coming back unhandled from the processor.agent_function_validation_end_to_end_test.dartround-trips bothcallAgentFunctionandcallRendererFunctionbetween a coreMessageProcessorand the agent.agent_functions_test.dartina2ui_corechecks theTOO_MANY_PENDINGcode.renderer_function_calls_conformance_test.dartrunsconformance/agent/renderer_function_calls.yaml. That suite lands with feat(python-agent): call renderer functions from the agent #3939, so on this branch the runner registers one skipped test saying the suite isn't in the checkout. With the suite from feat(python-agent): call renderer functions from the agent #3939 copied into a copy of this branch, all 30 cases and the non-empty check pass (31 tests), delivering responses throughRendererToAgentMessageProcessorand checkingcalls.isPending.cd dart/a2ui_core && dart analyze --fatal-infos && dart test: clean, 2265 passed (20 skips, none in the agent function tests).cd dart/a2ui_agent && dart analyze --fatal-infos && dart test: clean, 1691 passed. The renderer calls conformance test registers one skipped placeholder untilrenderer_function_calls.yamllands with feat(python-agent): call renderer functions from the agent #3939.dart format --output=none --set-exit-if-changed .is clean in both packages.