fix: require current a2a init scaffold
All checks were successful
build / build (push) Successful in 26s
All checks were successful
build / build (push) Successful in 26s
This commit is contained in:
@@ -4,6 +4,7 @@ WORKDIR /app
|
|||||||
|
|
||||||
COPY requirements.txt .
|
COPY requirements.txt .
|
||||||
RUN pip install --no-cache-dir -r 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 . .
|
COPY . .
|
||||||
|
|
||||||
|
|||||||
@@ -30,37 +30,19 @@ Given a user description, you write a complete agent project under the
|
|||||||
user's workspace at ``agents/<name>/`` and then deploy it through the
|
user's workspace at ``agents/<name>/`` and then deploy it through the
|
||||||
control plane.
|
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
|
The generated ``a2a.yaml`` looks like:
|
||||||
from pydantic import BaseModel
|
|
||||||
from a2a_pack import A2AAgent, NoAuth, RunContext, skill
|
|
||||||
|
|
||||||
|
|
||||||
class <Name>Config(BaseModel):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
class <Name>(A2AAgent[<Name>Config, NoAuth]):
|
|
||||||
name = "<slug>"
|
|
||||||
description = "..."
|
|
||||||
version = "0.1.0"
|
|
||||||
config_model = <Name>Config
|
|
||||||
auth_model = NoAuth
|
|
||||||
|
|
||||||
@skill(description="...", tags=["..."])
|
|
||||||
async def <skill>(self, ctx: RunContext[NoAuth], ...) -> dict:
|
|
||||||
await ctx.emit_progress("...")
|
|
||||||
return {"ok": True, "...": "..."}
|
|
||||||
```
|
|
||||||
|
|
||||||
You ALSO need an ``a2a.yaml`` like:
|
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
name: <slug>
|
name: <slug>
|
||||||
version: 0.1.0
|
version: 0.1.0
|
||||||
entrypoint: agent:<Name>
|
entrypoint: agent:<Name>
|
||||||
description: <one line>
|
|
||||||
expose:
|
expose:
|
||||||
public: true
|
public: true
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -128,7 +128,7 @@ def build_tools(ctx: ToolContext) -> list[Any]:
|
|||||||
try:
|
try:
|
||||||
files = _render_a2a_init_template(name, description=description)
|
files = _render_a2a_init_template(name, description=description)
|
||||||
prefix = _agent_prefix(name)
|
prefix = _agent_prefix(name)
|
||||||
except (OSError, ValueError) as exc:
|
except (OSError, RuntimeError, ValueError) as exc:
|
||||||
return json.dumps({"error": str(exc)})
|
return json.dumps({"error": str(exc)})
|
||||||
written: list[dict[str, Any]] = []
|
written: list[dict[str, Any]] = []
|
||||||
for path, content in files.items():
|
for path, content in files.items():
|
||||||
@@ -519,7 +519,7 @@ def _render_a2a_init_template(
|
|||||||
) -> dict[str, str]:
|
) -> dict[str, str]:
|
||||||
_validate_name(name)
|
_validate_name(name)
|
||||||
class_name = _class_name(name)
|
class_name = _class_name(name)
|
||||||
return {
|
files = {
|
||||||
"agent.py": _render_sdk_template(
|
"agent.py": _render_sdk_template(
|
||||||
"agent.py.tmpl",
|
"agent.py.tmpl",
|
||||||
name=name,
|
name=name,
|
||||||
@@ -533,6 +533,44 @@ def _render_a2a_init_template(
|
|||||||
),
|
),
|
||||||
"requirements.txt": _render_sdk_template("requirements.txt.tmpl"),
|
"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:
|
def _render_sdk_template(template: str, /, **vars: str) -> str:
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
a2a-pack>=0.1.3
|
||||||
httpx>=0.27
|
httpx>=0.27
|
||||||
boto3>=1.34
|
boto3>=1.34
|
||||||
deepagents>=0.5.0
|
deepagents>=0.5.0
|
||||||
|
|||||||
@@ -2,7 +2,10 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import unittest
|
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):
|
class TemplateInitTests(unittest.TestCase):
|
||||||
@@ -25,3 +28,10 @@ class TemplateInitTests(unittest.TestCase):
|
|||||||
def test_render_a2a_init_template_rejects_invalid_names(self) -> None:
|
def test_render_a2a_init_template_rejects_invalid_names(self) -> None:
|
||||||
with self.assertRaises(ValueError):
|
with self.assertRaises(ValueError):
|
||||||
_render_a2a_init_template("Bad Name")
|
_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",
|
||||||
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user