This commit is contained in:
10
README.md
10
README.md
@@ -47,7 +47,7 @@ Three platform-managed bits land on the inner skill via `RunContext`:
|
||||
| Field | Source | Required? |
|
||||
|---|---|---|
|
||||
| `ctx.workspace.bucket` | grant minted by the orchestrator | yes |
|
||||
| `ctx.llm` | caller-selected LLM creds when available, otherwise a platform-scoped LiteLLM grant (Card declares `llm_provisioning=platform_or_caller_provided`) | yes |
|
||||
| `ctx.llm` | caller's saved LLM credential, routed by the platform (Card declares `llm_provisioning=platform`) | yes |
|
||||
| `ctx.cp_jwt` | caller's CP JWT (Card declares `wants_cp_jwt=True`) | yes — used by `cp_deploy_tarball` |
|
||||
|
||||
The user opts into all three when they pick `agent-builder` from the
|
||||
@@ -55,11 +55,9 @@ marketplace — the dashboard already surfaces these on the agent card.
|
||||
|
||||
## Pricing
|
||||
|
||||
`$0.10 / call` during the current pricing model. The builder uses your
|
||||
selected LLM creds when present and falls back to a scoped platform grant.
|
||||
The deployed agent is yours forever; subsequent invocations of a generated LLM
|
||||
agent default to scoped platform LLM grants unless you explicitly generate a
|
||||
BYOK/caller-paid agent.
|
||||
`$0.10 / call` during the current pricing model. The builder uses your saved
|
||||
LLM credential. The deployed agent is yours forever; subsequent invocations of
|
||||
a generated LLM agent also use the caller's saved LLM credential via `ctx.llm`.
|
||||
|
||||
## How to call it
|
||||
|
||||
|
||||
11
agent.py
11
agent.py
@@ -10,8 +10,8 @@ This agent only works when invoked through the platform orchestrator,
|
||||
because it needs three things forwarded from the caller:
|
||||
|
||||
1. ``ctx.workspace.bucket`` — the user's MinIO bucket
|
||||
2. ``ctx.llm`` — caller-selected LLM credentials when available, otherwise
|
||||
a platform-scoped LiteLLM grant token for the builder's own DeepAgents loop
|
||||
2. ``ctx.llm`` — the caller's saved LLM credential for the builder's own
|
||||
DeepAgents loop
|
||||
3. ``ctx.cp_jwt`` — the user's CP JWT, so cp_deploy_tarball can
|
||||
POST to /v1/agents/from-tarball as the user.
|
||||
"""
|
||||
@@ -53,17 +53,16 @@ class AgentBuilder(A2AAgent[BuilderConfig, NoAuth]):
|
||||
config_model = BuilderConfig
|
||||
auth_model = NoAuth
|
||||
tools_used = ("deepagents", "langgraph", "a2a-pack", "litellm", "microsandbox")
|
||||
llm_provisioning = LLMProvisioning.PLATFORM_OR_CALLER_PROVIDED
|
||||
llm_provisioning = LLMProvisioning.PLATFORM
|
||||
wants_cp_jwt = True
|
||||
workspace_access = WorkspaceAccess.dynamic(
|
||||
allowed_modes=(WorkspaceMode.READ_ONLY, WorkspaceMode.READ_WRITE_OVERLAY),
|
||||
)
|
||||
pricing = Pricing(
|
||||
price_per_call_usd=0.10,
|
||||
caller_pays_llm=False,
|
||||
caller_pays_llm=True,
|
||||
notes=(
|
||||
"Uses caller-selected LLM credentials when available; otherwise "
|
||||
"uses a platform-scoped LiteLLM grant for the builder's own "
|
||||
"Uses the caller's saved LLM credential for the builder's own "
|
||||
"reasoning loop. Deployed agents remain yours."
|
||||
),
|
||||
)
|
||||
|
||||
@@ -30,9 +30,9 @@ class BuilderContext:
|
||||
sandbox: Any | None = None
|
||||
grant_token: str | None = None
|
||||
# LLM creds forwarded by the orchestrator according to this agent's Card.
|
||||
# The builder accepts caller-selected creds and falls back to platform
|
||||
# grants. Hosted generated agents should usually use platform grants too.
|
||||
# Falls back to settings for local development.
|
||||
# The builder uses the caller's saved LLM credential, usually proxied
|
||||
# through LiteLLM by the control plane. Falls back to settings only for
|
||||
# local development.
|
||||
llm_base_url: str | None = None
|
||||
llm_api_key: str | None = None
|
||||
llm_model: str | None = None
|
||||
@@ -49,28 +49,26 @@ user's workspace at ``agents/<name>/`` and then deploy it through the
|
||||
control plane.
|
||||
|
||||
The default starter is the current a2a-pack DeepAgents scaffold. It declares
|
||||
``LLMProvisioning.PLATFORM``, reads the scoped LLM grant from ``ctx.llm``,
|
||||
builds a ``ChatOpenAI`` model from those credentials, and wires a small
|
||||
``LLMProvisioning.PLATFORM``, reads the caller's saved LLM credential 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.
|
||||
|
||||
Default hosted generated/user agents to ``LLMProvisioning.PLATFORM`` and
|
||||
``caller_pays_llm=False`` so main-agent handoffs mint a scoped A2A LiteLLM grant
|
||||
for the callee. The generated agent must still read ``ctx.llm`` and must never
|
||||
``caller_pays_llm=True`` so main-agent handoffs forward the caller's saved LLM
|
||||
credential for the callee. The generated agent must still read ``ctx.llm`` and must never
|
||||
read ``A2A_LITELLM_KEY``, ``OPENAI_API_KEY``, LiteLLM master keys, or provider
|
||||
keys directly. If the user explicitly asks for BYOK or caller-paid inference,
|
||||
use ``LLMProvisioning.CALLER_PROVIDED`` with ``caller_pays_llm=True`` and make
|
||||
missing ``ctx.llm.api_key`` a clear setup/config result before constructing
|
||||
keys directly. ``LLMProvisioning.CALLER_PROVIDED`` is still acceptable for
|
||||
explicit BYOK wording. Always make missing ``ctx.llm.api_key`` a clear setup/config result before constructing
|
||||
``ChatOpenAI``.
|
||||
``Pricing`` accepts only ``price_per_call_usd``, ``caller_pays_llm``, and
|
||||
``notes``. Never set ``runtime.pricing.compute`` or
|
||||
``runtime.pricing.total_usd`` in generated source; those are derived later by
|
||||
the control plane/dashboard from declared ``Resources`` and billing policy.
|
||||
``LLMProvisioning.PLATFORM_OR_CALLER_PROVIDED`` is reserved for trusted
|
||||
platform/meta agents that should prefer caller-selected credentials and fall
|
||||
back to a scoped platform grant; do not use it for ordinary generated agents
|
||||
unless the user explicitly asks for that mixed billing mode.
|
||||
``LLMProvisioning.PLATFORM_OR_CALLER_PROVIDED`` is retained only for backwards
|
||||
compatibility; do not use it for ordinary generated agents unless the user
|
||||
explicitly asks for that legacy mixed mode.
|
||||
|
||||
You have packaged DeepAgents skills loaded from ``/.agent-builder/skills/``.
|
||||
Use ``deepagent-agent-design`` before designing generated agent internals,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: a2apack-agent-authoring
|
||||
description: Author production a2a-pack agents with clear public @skill schemas, runtime declarations, platform LLM grants, optional caller-provided LLMs, workspace grants, pricing, resources, and secure platform capabilities. Use when writing or reviewing agent.py for an A2A agent.
|
||||
description: Author production a2a-pack agents with clear public @skill schemas, runtime declarations, caller-funded LLM credentials, optional agent BYOK, workspace grants, pricing, resources, and secure platform capabilities. Use when writing or reviewing agent.py for an A2A agent.
|
||||
---
|
||||
# A2A Pack Agent Authoring
|
||||
|
||||
@@ -22,12 +22,11 @@ Use `A2AAgent` class attributes for runtime and marketplace behavior:
|
||||
caller's control-plane token.
|
||||
|
||||
For hosted generated/user-owned agents that call an LLM, default to
|
||||
`LLMProvisioning.PLATFORM` with `Pricing(..., caller_pays_llm=False, ...)` so
|
||||
main-agent handoffs mint a scoped A2A LiteLLM grant for the callee. Use
|
||||
`LLMProvisioning.CALLER_PROVIDED` only when the user explicitly wants BYOK or
|
||||
caller-paid inference. Reserve `LLMProvisioning.PLATFORM_OR_CALLER_PROVIDED`
|
||||
for trusted platform/meta agents that must work with either the caller's
|
||||
selected creds or a platform fallback grant. In every mode, read `ctx.llm`;
|
||||
`LLMProvisioning.PLATFORM` with `Pricing(..., caller_pays_llm=True, ...)` so
|
||||
main-agent handoffs forward the caller's saved LLM credential for the callee.
|
||||
`LLMProvisioning.CALLER_PROVIDED` is still acceptable for explicit BYOK wording.
|
||||
Reserve `LLMProvisioning.PLATFORM_OR_CALLER_PROVIDED` for legacy compatibility.
|
||||
In every mode, read `ctx.llm`;
|
||||
never read `A2A_LITELLM_KEY`, `OPENAI_API_KEY`, provider keys, or platform
|
||||
secrets directly. If `ctx.llm.api_key` is empty, return a clear setup/config
|
||||
result before constructing `ChatOpenAI`.
|
||||
@@ -79,8 +78,8 @@ class ResearchAgent(A2AAgent[ResearchConfig, NoAuth]):
|
||||
llm_provisioning = LLMProvisioning.PLATFORM
|
||||
pricing = Pricing(
|
||||
price_per_call_usd=0.05,
|
||||
caller_pays_llm=False,
|
||||
notes="Uses a scoped platform LLM grant through ctx.llm.",
|
||||
caller_pays_llm=True,
|
||||
notes="Uses the caller's saved LLM credential through ctx.llm.",
|
||||
)
|
||||
resources = Resources(cpu="1", memory="1Gi", max_runtime_seconds=900)
|
||||
egress = EgressPolicy(allow_hosts=("api.openai.com",))
|
||||
|
||||
@@ -35,10 +35,9 @@ Check these items:
|
||||
only for trusted platform/meta agents and must also read `ctx.llm`.
|
||||
- Hosted generated/user agents should default to
|
||||
`llm_provisioning = LLMProvisioning.PLATFORM` and
|
||||
`Pricing(..., caller_pays_llm=False, ...)`. Use
|
||||
`LLMProvisioning.CALLER_PROVIDED` only when the user explicitly wants BYOK or
|
||||
caller-paid inference. In both modes, the code must use `ctx.llm` and never
|
||||
read LiteLLM/provider keys directly.
|
||||
`Pricing(..., caller_pays_llm=True, ...)`. `LLMProvisioning.CALLER_PROVIDED`
|
||||
is acceptable for explicit BYOK wording. In both modes, the code must use
|
||||
`ctx.llm` and never read LiteLLM/provider keys directly.
|
||||
- `Pricing` contains only `price_per_call_usd`, `caller_pays_llm`, and `notes`.
|
||||
Reject `runtime.pricing.compute`, `runtime.pricing.total_usd`, `compute=`, or
|
||||
`total_usd=` in `agent.py`; those are derived platform fields.
|
||||
|
||||
@@ -32,16 +32,15 @@ class BuilderPromptTests(unittest.TestCase):
|
||||
self.assertIn("stream=True", agent_source)
|
||||
self.assertIn('grant_write_prefixes=("agents/{name}/",)', agent_source)
|
||||
|
||||
def test_outer_builder_accepts_user_creds_or_platform_llm_grants(self) -> None:
|
||||
def test_outer_builder_uses_user_llm_credentials(self) -> None:
|
||||
agent_source = Path(__file__).resolve().parents[1].joinpath("agent.py").read_text()
|
||||
|
||||
self.assertIn(
|
||||
"llm_provisioning = LLMProvisioning.PLATFORM_OR_CALLER_PROVIDED",
|
||||
"llm_provisioning = LLMProvisioning.PLATFORM",
|
||||
agent_source,
|
||||
)
|
||||
self.assertIn("caller_pays_llm=False", agent_source)
|
||||
self.assertIn("platform-scoped LiteLLM grant", agent_source)
|
||||
self.assertIn("caller-selected LLM credentials", agent_source)
|
||||
self.assertIn("caller_pays_llm=True", agent_source)
|
||||
self.assertIn("caller's saved LLM credential", agent_source)
|
||||
|
||||
def test_builder_defaults_raise_timeout_budget(self) -> None:
|
||||
config_source = Path(__file__).resolve().parents[1].joinpath(
|
||||
@@ -60,15 +59,15 @@ class BuilderPromptTests(unittest.TestCase):
|
||||
self.assertIn(f"a2a-pack>={A2A_PACK_MIN_VERSION}", requirements)
|
||||
self.assertIn("a2a-pack>={A2A_PACK_MIN_VERSION}", tools_source)
|
||||
|
||||
def test_prompt_defaults_generated_agents_to_platform_llm_grants(self) -> None:
|
||||
def test_prompt_defaults_generated_agents_to_user_llm_credentials(self) -> None:
|
||||
self.assertIn("LLMProvisioning.PLATFORM", SYSTEM_PROMPT)
|
||||
self.assertIn("caller_pays_llm=False", SYSTEM_PROMPT)
|
||||
self.assertIn("main-agent handoffs mint a scoped A2A LiteLLM grant", SYSTEM_PROMPT)
|
||||
self.assertIn("caller_pays_llm=True", SYSTEM_PROMPT)
|
||||
self.assertIn("main-agent handoffs forward the caller's saved LLM", SYSTEM_PROMPT)
|
||||
self.assertIn("ctx.llm.api_key", SYSTEM_PROMPT)
|
||||
|
||||
files = _builder_skill_files()
|
||||
self.assertIn("LLMProvisioning.PLATFORM", files["a2apack-agent-authoring/SKILL.md"])
|
||||
self.assertIn("caller_pays_llm=False", files["a2apack-agent-authoring/SKILL.md"])
|
||||
self.assertIn("caller_pays_llm=True", files["a2apack-agent-authoring/SKILL.md"])
|
||||
self.assertIn("ctx.llm.api_key", files["agent-quality-review/SKILL.md"])
|
||||
|
||||
def test_prompt_rejects_platform_derived_pricing_fields(self) -> None:
|
||||
|
||||
Reference in New Issue
Block a user