feat: add Evidence Bench computational-science connector#642
Conversation
There was a problem hiding this comment.
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.
| 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() |
There was a problem hiding this comment.
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 excThere was a problem hiding this comment.
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).
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
Drafted in response to maintainer guidance in #639.
Closes #639