Skip to content

feat: add Evidence Bench computational-science connector#642

Draft
kstawiski wants to merge 2 commits into
a2aproject:mainfrom
kstawiski:evidence-bench-connector-a2a-v1
Draft

feat: add Evidence Bench computational-science connector#642
kstawiski wants to merge 2 commits into
a2aproject:mainfrom
kstawiski:evidence-bench-connector-a2a-v1

Conversation

@kstawiski

Copy link
Copy Markdown

Summary

Adds a small A2A v1.0 JSON-RPC connector sample for forwarding bounded computational-science tasks to a separately deployed Evidence Bench service. The connector keeps deployment credentials in environment variables, validates the upstream origin, defaults code and MCP use off, and returns bounded report/summary artifacts.

Validation

  • pytest: 7 passed
  • Ruff lint and format
  • mypy
  • Pyright: 0 errors / 0 warnings
  • gitleaks: no findings

Drafted in response to maintainer guidance in #639.

Closes #639

@gemini-code-assist gemini-code-assist Bot left a comment

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.

Code Review

This pull request introduces the Evidence Bench Connector, a thin A2A 1.0 connector that delegates scientific tasks to an external Evidence Bench service. It includes implementation for sanitizing messages, executing remote tasks, and serving the agent via Starlette. Feedback on the implementation suggests nesting the client teardown inside the HTTP client's context manager to prevent closing the client after its underlying HTTP client has already been closed.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines +130 to +180
client = None
try:
async with httpx.AsyncClient(
headers=headers,
timeout=timeout,
follow_redirects=False,
trust_env=False,
) as http_client:
card = await A2ACardResolver(
httpx_client=http_client,
base_url=self.base_url,
).get_agent_card()
_remote_jsonrpc_interface(card, self.base_url)
client = await create_client(
agent=card,
client_config=ClientConfig(
streaming=True,
httpx_client=http_client,
accepted_output_modes=['application/json', 'text/markdown'],
),
)
request = SendMessageRequest(
message=message,
configuration=SendMessageConfiguration(
accepted_output_modes=['application/json', 'text/markdown']
),
)
state = TaskState.TASK_STATE_UNSPECIFIED
artifacts: dict[str, Artifact] = {}
async for response in client.send_message(request):
payload = response.WhichOneof('payload')
if payload == 'task':
state = response.task.status.state
for artifact in response.task.artifacts:
_remember_artifact(artifacts, artifact)
elif payload == 'status_update':
state = response.status_update.status.state
elif payload == 'artifact_update':
_remember_artifact(
artifacts,
response.artifact_update.artifact,
)
_require_terminal_state(state)
return RemoteRunResult(state=state, artifacts=artifacts)
except ConnectorError:
raise
except Exception as exc:
raise ConnectorError('Evidence Bench A2A request failed.') from exc
finally:
if client is not None:
await client.close()

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.

medium

Currently, client.close() is called in the outer finally block, which executes after the async with httpx.AsyncClient(...) as http_client block has exited. Since client depends on http_client (passed via ClientConfig), attempting to close client after the underlying HTTP client has already been closed can lead to unexpected errors, warnings, or unhandled exceptions (e.g., trying to perform cleanup on a closed event loop or closed connection pool).

By nesting a try...finally block inside the async with block, we guarantee that client.close() is awaited while http_client is still active and open, ensuring clean resource teardown.

        try:
            async with httpx.AsyncClient(
                headers=headers,
                timeout=timeout,
                follow_redirects=False,
                trust_env=False,
            ) as http_client:
                card = await A2ACardResolver(
                    httpx_client=http_client,
                    base_url=self.base_url,
                ).get_agent_card()
                _remote_jsonrpc_interface(card, self.base_url)
                client = await create_client(
                    agent=card,
                    client_config=ClientConfig(
                        streaming=True,
                        httpx_client=http_client,
                        accepted_output_modes=['application/json', 'text/markdown'],
                    ),
                )
                try:
                    request = SendMessageRequest(
                        message=message,
                        configuration=SendMessageConfiguration(
                            accepted_output_modes=['application/json', 'text/markdown']
                        ),
                    )
                    state = TaskState.TASK_STATE_UNSPECIFIED
                    artifacts: dict[str, Artifact] = {}
                    async for response in client.send_message(request):
                        payload = response.WhichOneof('payload')
                        if payload == 'task':
                            state = response.task.status.state
                            for artifact in response.task.artifacts:
                                _remember_artifact(artifacts, artifact)
                        elif payload == 'status_update':
                            state = response.status_update.status.state
                        elif payload == 'artifact_update':
                            _remember_artifact(
                                artifacts,
                                response.artifact_update.artifact,
                            )
                    _require_terminal_state(state)
                    return RemoteRunResult(state=state, artifacts=artifacts)
                finally:
                    await client.close()
        except ConnectorError:
            raise
        except Exception as exc:
            raise ConnectorError('Evidence Bench A2A request failed.') from exc

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.

Fixed in 4c19bea: the connector client is now closed in an inner finally block while the injected httpx.AsyncClient is still active. The focused connector suite remains green (7 pytest cases, Ruff, mypy, and Pyright).

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.

Proposal: Evidence Bench evidence-gated computational-science A2A sample

1 participant