fix: align agent-builder with longer pack timeouts
All checks were successful
build / build (push) Successful in 27s

This commit is contained in:
robert
2026-05-20 19:41:22 -03:00
parent 4c8cd8ab2f
commit 1411a445e1
6 changed files with 37 additions and 4 deletions

View File

@@ -17,8 +17,10 @@ because it needs three things forwarded from the caller:
""" """
from __future__ import annotations from __future__ import annotations
import asyncio
import json import json
import re import re
from contextlib import suppress
from typing import Any from typing import Any
from pydantic import BaseModel from pydantic import BaseModel
@@ -65,6 +67,7 @@ class AgentBuilder(A2AAgent[BuilderConfig, NoAuth]):
"should do. Returns the live URL when ready." "should do. Returns the live URL when ready."
), ),
tags=["builder", "scaffold", "deepagents", "meta"], tags=["builder", "scaffold", "deepagents", "meta"],
stream=True,
) )
async def build( async def build(
self, self,
@@ -127,6 +130,13 @@ class AgentBuilder(A2AAgent[BuilderConfig, NoAuth]):
final_state: dict[str, Any] = {} final_state: dict[str, Any] = {}
deployed_url: str | None = None deployed_url: str | None = None
last_reply: str = "" last_reply: str = ""
async def _heartbeat() -> None:
while True:
await asyncio.sleep(20)
await ctx.emit_progress(f"agent-builder still working on {name!r}")
heartbeat_task = asyncio.create_task(_heartbeat())
try: try:
async for event in graph.astream_events( async for event in graph.astream_events(
{"messages": [{"role": "user", "content": user_msg}]}, {"messages": [{"role": "user", "content": user_msg}]},
@@ -156,6 +166,10 @@ class AgentBuilder(A2AAgent[BuilderConfig, NoAuth]):
final_state = out final_state = out
except Exception as exc: # noqa: BLE001 except Exception as exc: # noqa: BLE001
return {"error": f"build graph failed: {type(exc).__name__}: {exc}"} return {"error": f"build graph failed: {type(exc).__name__}: {exc}"}
finally:
heartbeat_task.cancel()
with suppress(asyncio.CancelledError):
await heartbeat_task
for msg in final_state.get("messages") or []: for msg in final_state.get("messages") or []:
content = msg.get("content") if isinstance(msg, dict) else getattr(msg, "content", None) content = msg.get("content") if isinstance(msg, dict) else getattr(msg, "content", None)

View File

@@ -9,6 +9,7 @@ from dataclasses import dataclass
class Settings: class Settings:
sandbox_url: str sandbox_url: str
sandbox_timeout_s: int sandbox_timeout_s: int
deploy_wait_timeout_s: int
litellm_url: str litellm_url: str
litellm_key: str litellm_key: str
litellm_model: str litellm_model: str
@@ -24,7 +25,8 @@ def load_settings() -> Settings:
sandbox_url=os.environ.get( sandbox_url=os.environ.get(
"SANDBOX_URL", "http://sandbox.sandbox.svc.cluster.local:8000" "SANDBOX_URL", "http://sandbox.sandbox.svc.cluster.local:8000"
), ),
sandbox_timeout_s=int(os.environ.get("SANDBOX_TIMEOUT_S", "240")), sandbox_timeout_s=int(os.environ.get("SANDBOX_TIMEOUT_S", "1200")),
deploy_wait_timeout_s=int(os.environ.get("DEPLOY_WAIT_TIMEOUT_S", "900")),
litellm_url=os.environ.get( litellm_url=os.environ.get(
"A2A_LITELLM_URL", "http://litellm.llm.svc.cluster.local:4000" "A2A_LITELLM_URL", "http://litellm.llm.svc.cluster.local:4000"
), ),

View File

@@ -38,7 +38,7 @@ class ToolContext:
async def _wait_for_live_card( async def _wait_for_live_card(
url: str | None, url: str | None,
expected_version: str | None, expected_version: str | None,
timeout_s: float = 180.0, timeout_s: float = 900.0,
) -> tuple[bool, dict[str, Any]]: ) -> tuple[bool, dict[str, Any]]:
"""Poll ``{url}/.well-known/agent-card`` until ``version`` matches """Poll ``{url}/.well-known/agent-card`` until ``version`` matches
``expected_version`` or ``timeout_s`` elapses. Returns ``expected_version`` or ``timeout_s`` elapses. Returns
@@ -435,7 +435,11 @@ def build_tools(ctx: ToolContext) -> list[Any]:
# so the LLM can verify the input_schema actually matches the # so the LLM can verify the input_schema actually matches the
# code it wrote — catches the schema/code skew that causes # code it wrote — catches the schema/code skew that causes
# downstream 400s on call_agent. # downstream 400s on call_agent.
live, live_card = await _wait_for_live_card(url_, version_) live, live_card = await _wait_for_live_card(
url_,
version_,
timeout_s=settings.deploy_wait_timeout_s,
)
out: dict[str, Any] = { out: dict[str, Any] = {
"ok": live, "ok": live,
"name": name_, "name": name_,

View File

@@ -32,6 +32,10 @@ spec:
value: registry.a2acloud.io/agents/agent-builder:f7da050814bf8856fb2df0949d8140b34d379864 value: registry.a2acloud.io/agents/agent-builder:f7da050814bf8856fb2df0949d8140b34d379864
- name: SANDBOX_URL - name: SANDBOX_URL
value: http://sandbox.sandbox.svc.cluster.local:8000 value: http://sandbox.sandbox.svc.cluster.local:8000
- name: SANDBOX_TIMEOUT_S
value: "1200"
- name: DEPLOY_WAIT_TIMEOUT_S
value: "900"
- name: A2A_LITELLM_URL - name: A2A_LITELLM_URL
value: http://litellm.llm.svc.cluster.local:4000 value: http://litellm.llm.svc.cluster.local:4000
- name: A2A_LITELLM_KEY - name: A2A_LITELLM_KEY

View File

@@ -1,4 +1,4 @@
a2a-pack>=0.1.12 a2a-pack>=0.1.14
httpx>=0.27 httpx>=0.27
boto3>=1.34 boto3>=1.34
deepagents>=0.5.0 deepagents>=0.5.0

View File

@@ -21,6 +21,15 @@ class BuilderPromptTests(unittest.TestCase):
"config={\"recursion_limit\": DEEPAGENTS_RECURSION_LIMIT}", "config={\"recursion_limit\": DEEPAGENTS_RECURSION_LIMIT}",
agent_source, agent_source,
) )
self.assertIn("stream=True", agent_source)
def test_builder_defaults_raise_timeout_budget(self) -> None:
config_source = Path(__file__).resolve().parents[1].joinpath(
"agent_builder/config.py"
).read_text()
self.assertIn('sandbox_timeout_s=int(os.environ.get("SANDBOX_TIMEOUT_S", "1200"))', config_source)
self.assertIn('deploy_wait_timeout_s=int(os.environ.get("DEPLOY_WAIT_TIMEOUT_S", "900"))', config_source)
def test_prompt_requires_resource_mirror_for_heavy_agents(self) -> None: def test_prompt_requires_resource_mirror_for_heavy_agents(self) -> None:
self.assertIn("declare resources in BOTH places", SYSTEM_PROMPT) self.assertIn("declare resources in BOTH places", SYSTEM_PROMPT)