diff --git a/Dockerfile b/Dockerfile index ea22d08..3679162 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,6 +4,7 @@ WORKDIR /app COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt +RUN python -c "import importlib.resources as r, a2a_pack; t = r.files('a2a_pack.cli.templates').joinpath('agent.py.tmpl').read_text(); missing = [m for m in ('LLMProvisioning.CALLER_PROVIDED', 'ctx.llm', 'create_deep_agent', 'wrap_model_call') if m not in t]; assert not missing, f'a2a-pack {a2a_pack.__version__} has stale init template; missing {missing}'" COPY . . diff --git a/agent_builder/builder.py b/agent_builder/builder.py index 2eacce5..965ab0c 100644 --- a/agent_builder/builder.py +++ b/agent_builder/builder.py @@ -30,37 +30,19 @@ Given a user description, you write a complete agent project under the user's workspace at ``agents//`` and then deploy it through the control plane. -What an a2a-pack agent looks like: +The default starter is the current a2a-pack DeepAgents scaffold. It declares +``LLMProvisioning.CALLER_PROVIDED``, reads caller LLM credentials from +``ctx.llm``, builds a ``ChatOpenAI`` model from those credentials, and wires a +small tool-calling DeepAgent with ``create_deep_agent`` plus +``wrap_model_call`` middleware. Do not recreate this from memory: call +``init_agent_template`` first and modify the generated files. -```python -from pydantic import BaseModel -from a2a_pack import A2AAgent, NoAuth, RunContext, skill - - -class Config(BaseModel): - pass - - -class (A2AAgent[Config, NoAuth]): - name = "" - description = "..." - version = "0.1.0" - config_model = Config - auth_model = NoAuth - - @skill(description="...", tags=["..."]) - async def (self, ctx: RunContext[NoAuth], ...) -> dict: - await ctx.emit_progress("...") - return {"ok": True, "...": "..."} -``` - -You ALSO need an ``a2a.yaml`` like: +The generated ``a2a.yaml`` looks like: ```yaml name: version: 0.1.0 entrypoint: agent: -description: expose: public: true ``` diff --git a/agent_builder/tools.py b/agent_builder/tools.py index 327ea84..03a4ea6 100644 --- a/agent_builder/tools.py +++ b/agent_builder/tools.py @@ -128,7 +128,7 @@ def build_tools(ctx: ToolContext) -> list[Any]: try: files = _render_a2a_init_template(name, description=description) prefix = _agent_prefix(name) - except (OSError, ValueError) as exc: + except (OSError, RuntimeError, ValueError) as exc: return json.dumps({"error": str(exc)}) written: list[dict[str, Any]] = [] for path, content in files.items(): @@ -519,7 +519,7 @@ def _render_a2a_init_template( ) -> dict[str, str]: _validate_name(name) class_name = _class_name(name) - return { + files = { "agent.py": _render_sdk_template( "agent.py.tmpl", name=name, @@ -533,6 +533,44 @@ def _render_a2a_init_template( ), "requirements.txt": _render_sdk_template("requirements.txt.tmpl"), } + _assert_current_a2a_init_template(files) + return files + + +def _assert_current_a2a_init_template(files: dict[str, str]) -> None: + required_markers = { + "agent.py": ( + "LLMProvisioning.CALLER_PROVIDED", + "ctx.llm", + "create_deep_agent", + "wrap_model_call", + "ChatOpenAI", + ), + "requirements.txt": ( + "deepagents>=0.5.0", + "langchain-openai", + ), + } + missing: list[str] = [] + for path, markers in required_markers.items(): + content = files.get(path, "") + for marker in markers: + if marker not in content: + missing.append(f"{path}:{marker}") + if not missing: + return + + try: + import a2a_pack + + version = getattr(a2a_pack, "__version__", "unknown") + except Exception: # noqa: BLE001 + version = "unknown" + raise RuntimeError( + "installed a2a-pack init template is stale; expected the 0.1.3+ " + "DeepAgents caller-LLM scaffold. " + f"a2a_pack={version}; missing markers: {', '.join(missing)}" + ) def _render_sdk_template(template: str, /, **vars: str) -> str: diff --git a/requirements.txt b/requirements.txt index 10f3c8c..02a658c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,4 @@ +a2a-pack>=0.1.3 httpx>=0.27 boto3>=1.34 deepagents>=0.5.0 diff --git a/tests/test_template_init.py b/tests/test_template_init.py index e29a92b..7c901de 100644 --- a/tests/test_template_init.py +++ b/tests/test_template_init.py @@ -2,7 +2,10 @@ from __future__ import annotations import unittest -from agent_builder.tools import _render_a2a_init_template +from agent_builder.tools import ( + _assert_current_a2a_init_template, + _render_a2a_init_template, +) class TemplateInitTests(unittest.TestCase): @@ -25,3 +28,10 @@ class TemplateInitTests(unittest.TestCase): def test_render_a2a_init_template_rejects_invalid_names(self) -> None: with self.assertRaises(ValueError): _render_a2a_init_template("Bad Name") + + def test_stale_sdk_template_is_rejected(self) -> None: + with self.assertRaisesRegex(RuntimeError, "stale"): + _assert_current_a2a_init_template({ + "agent.py": "from a2a_pack import A2AAgent", + "requirements.txt": "httpx>=0.27", + })