Handle deterministic agents without LLM creds
All checks were successful
build / build (push) Successful in 11s
All checks were successful
build / build (push) Successful in 11s
This commit is contained in:
@@ -49,20 +49,28 @@ 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.
|
||||||
|
|
||||||
The default starter is the current a2a-pack DeepAgents scaffold. It declares
|
The default starter is the current a2a-pack DeepAgents scaffold. For agents
|
||||||
``LLMProvisioning.PLATFORM``, reads the caller's saved LLM credential from
|
that call an LLM, it declares ``LLMProvisioning.PLATFORM``, reads the caller's
|
||||||
``ctx.llm``, resolves the model through ``create_a2a_deep_agent``, and wires a small
|
saved LLM credential from ``ctx.llm``, resolves the model through
|
||||||
tool-calling DeepAgent with the A2A DeepAgents model middleware
|
``create_a2a_deep_agent``, and wires a small tool-calling DeepAgent with the
|
||||||
middleware. Do not recreate this from memory: call ``init_agent_template`` first
|
A2A DeepAgents model middleware. Do not recreate this from memory: call
|
||||||
|
``init_agent_template`` first
|
||||||
and modify the generated files.
|
and modify the generated files.
|
||||||
|
|
||||||
Default hosted generated/user agents to ``LLMProvisioning.PLATFORM`` and
|
For hosted generated/user agents that call an LLM, default to
|
||||||
``caller_pays_llm=True`` so main-agent handoffs forward the caller's saved LLM
|
``LLMProvisioning.PLATFORM`` and ``caller_pays_llm=True`` so main-agent
|
||||||
credential for the callee. The generated agent must still read ``ctx.llm`` and must never
|
handoffs forward the caller's saved LLM credential for the callee. The
|
||||||
read ``A2A_LITELLM_KEY``, ``OPENAI_API_KEY``, LiteLLM master keys, or provider
|
generated agent must still read ``ctx.llm`` and must never read
|
||||||
keys directly. ``LLMProvisioning.CALLER_PROVIDED`` is still acceptable for
|
``A2A_LITELLM_KEY``, ``OPENAI_API_KEY``, LiteLLM master keys, or provider keys
|
||||||
explicit BYOK wording. Always make missing ``ctx.llm.api_key`` a clear setup/config result before constructing
|
directly. ``LLMProvisioning.CALLER_PROVIDED`` is still acceptable for explicit
|
||||||
a DeepAgents model.
|
BYOK wording. Always make missing ``ctx.llm.api_key`` a clear setup/config
|
||||||
|
result before constructing a DeepAgents model.
|
||||||
|
|
||||||
|
For explicitly deterministic/local-logic agents that do not call an LLM, remove
|
||||||
|
the starter's ``LLMProvisioning`` import and class ``llm_provisioning``
|
||||||
|
declaration, do not read ``ctx.llm``, and set
|
||||||
|
``Pricing(..., caller_pays_llm=False, ...)``. A no-LLM agent must be callable
|
||||||
|
without requiring the user's LLM credential.
|
||||||
``Pricing`` accepts only ``price_per_call_usd``, ``caller_pays_llm``, and
|
``Pricing`` accepts only ``price_per_call_usd``, ``caller_pays_llm``, and
|
||||||
``notes``. Never set ``runtime.pricing.compute`` or
|
``notes``. Never set ``runtime.pricing.compute`` or
|
||||||
``runtime.pricing.total_usd`` in generated source; those are derived later by
|
``runtime.pricing.total_usd`` in generated source; those are derived later by
|
||||||
|
|||||||
@@ -31,6 +31,12 @@ 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
|
secrets directly. If `ctx.llm.api_key` is empty, return a clear setup/config
|
||||||
result before constructing a model or calling `create_a2a_deep_agent`.
|
result before constructing a model or calling `create_a2a_deep_agent`.
|
||||||
|
|
||||||
|
For explicitly deterministic/local-logic agents that do not call an LLM, omit
|
||||||
|
the concrete class `llm_provisioning` declaration, remove unused
|
||||||
|
`LLMProvisioning` imports, do not read `ctx.llm`, and set
|
||||||
|
`Pricing(..., caller_pays_llm=False, ...)`. A no-LLM agent must be callable
|
||||||
|
without requiring the user's LLM credential.
|
||||||
|
|
||||||
`Pricing` only accepts `price_per_call_usd`, `caller_pays_llm`, and `notes`.
|
`Pricing` only accepts `price_per_call_usd`, `caller_pays_llm`, and `notes`.
|
||||||
Do not put `runtime.pricing.compute`, `runtime.pricing.total_usd`, `compute=`,
|
Do not put `runtime.pricing.compute`, `runtime.pricing.total_usd`, `compute=`,
|
||||||
or `total_usd=` in generated `agent.py`; the platform derives those values from
|
or `total_usd=` in generated `agent.py`; the platform derives those values from
|
||||||
|
|||||||
@@ -30,14 +30,19 @@ Check these items:
|
|||||||
arguments.
|
arguments.
|
||||||
- The public schema is small and stable. No unbounded command strings, hidden
|
- The public schema is small and stable. No unbounded command strings, hidden
|
||||||
prompt fragments, or provider credentials as public inputs.
|
prompt fragments, or provider credentials as public inputs.
|
||||||
- The implementation reads `ctx.llm` for both `CALLER_PROVIDED` and
|
- LLM-backed implementations read `ctx.llm` for both `CALLER_PROVIDED` and
|
||||||
`PLATFORM` LLM provisioning. `PLATFORM_OR_CALLER_PROVIDED` is acceptable
|
`PLATFORM` LLM provisioning. `PLATFORM_OR_CALLER_PROVIDED` is acceptable
|
||||||
only for trusted platform/meta agents and must also read `ctx.llm`.
|
only for trusted platform/meta agents and must also read `ctx.llm`.
|
||||||
- Hosted generated/user agents should default to
|
- Hosted generated/user agents that call an LLM should default to
|
||||||
`llm_provisioning = LLMProvisioning.PLATFORM` and
|
`llm_provisioning = LLMProvisioning.PLATFORM` and
|
||||||
`Pricing(..., caller_pays_llm=True, ...)`. `LLMProvisioning.CALLER_PROVIDED`
|
`Pricing(..., caller_pays_llm=True, ...)`. `LLMProvisioning.CALLER_PROVIDED`
|
||||||
is acceptable for explicit BYOK wording. In both modes, the code must use
|
is acceptable for explicit BYOK wording. In both modes, the code must use
|
||||||
`ctx.llm` and never read LiteLLM/provider keys directly.
|
`ctx.llm` and never read LiteLLM/provider keys directly.
|
||||||
|
- Deterministic/local-logic agents that do not call an LLM omit the concrete
|
||||||
|
`llm_provisioning` declaration, remove unused `LLMProvisioning` imports, do
|
||||||
|
not read `ctx.llm`, and use `Pricing(..., caller_pays_llm=False, ...)`.
|
||||||
|
Reject no-LLM agents that would require a user's LLM credential before
|
||||||
|
invoking deterministic skills.
|
||||||
- `Pricing` contains only `price_per_call_usd`, `caller_pays_llm`, and `notes`.
|
- `Pricing` contains only `price_per_call_usd`, `caller_pays_llm`, and `notes`.
|
||||||
Reject `runtime.pricing.compute`, `runtime.pricing.total_usd`, `compute=`, or
|
Reject `runtime.pricing.compute`, `runtime.pricing.total_usd`, `compute=`, or
|
||||||
`total_usd=` in `agent.py`; those are derived platform fields.
|
`total_usd=` in `agent.py`; those are derived platform fields.
|
||||||
|
|||||||
@@ -185,16 +185,23 @@ class BuilderPromptTests(unittest.TestCase):
|
|||||||
self.assertIn(f"a2a-pack>={A2A_PACK_MIN_VERSION}", requirements)
|
self.assertIn(f"a2a-pack>={A2A_PACK_MIN_VERSION}", requirements)
|
||||||
self.assertIn("a2a-pack>={A2A_PACK_MIN_VERSION}", tools_source)
|
self.assertIn("a2a-pack>={A2A_PACK_MIN_VERSION}", tools_source)
|
||||||
|
|
||||||
def test_prompt_defaults_generated_agents_to_user_llm_credentials(self) -> None:
|
def test_prompt_routes_llm_and_no_llm_agents_differently(self) -> None:
|
||||||
self.assertIn("LLMProvisioning.PLATFORM", SYSTEM_PROMPT)
|
self.assertIn("LLMProvisioning.PLATFORM", SYSTEM_PROMPT)
|
||||||
self.assertIn("caller_pays_llm=True", 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("handoffs forward the caller's saved LLM credential", SYSTEM_PROMPT)
|
||||||
self.assertIn("ctx.llm.api_key", SYSTEM_PROMPT)
|
self.assertIn("ctx.llm.api_key", SYSTEM_PROMPT)
|
||||||
|
self.assertIn("deterministic/local-logic agents", SYSTEM_PROMPT)
|
||||||
|
self.assertIn("caller_pays_llm=False", SYSTEM_PROMPT)
|
||||||
|
self.assertIn("A no-LLM agent must be callable", SYSTEM_PROMPT)
|
||||||
|
self.assertIn("without requiring the user's LLM credential", SYSTEM_PROMPT)
|
||||||
|
|
||||||
files = _builder_skill_files()
|
files = _builder_skill_files()
|
||||||
self.assertIn("LLMProvisioning.PLATFORM", files["a2apack-agent-authoring/SKILL.md"])
|
self.assertIn("LLMProvisioning.PLATFORM", files["a2apack-agent-authoring/SKILL.md"])
|
||||||
self.assertIn("caller_pays_llm=True", files["a2apack-agent-authoring/SKILL.md"])
|
self.assertIn("caller_pays_llm=True", files["a2apack-agent-authoring/SKILL.md"])
|
||||||
|
self.assertIn("caller_pays_llm=False", files["a2apack-agent-authoring/SKILL.md"])
|
||||||
self.assertIn("ctx.llm.api_key", files["agent-quality-review/SKILL.md"])
|
self.assertIn("ctx.llm.api_key", files["agent-quality-review/SKILL.md"])
|
||||||
|
self.assertIn("Deterministic/local-logic agents", files["agent-quality-review/SKILL.md"])
|
||||||
|
self.assertIn("require a user's LLM credential", files["agent-quality-review/SKILL.md"])
|
||||||
|
|
||||||
def test_prompt_rejects_platform_derived_pricing_fields(self) -> None:
|
def test_prompt_rejects_platform_derived_pricing_fields(self) -> None:
|
||||||
self.assertIn("runtime.pricing.compute", SYSTEM_PROMPT)
|
self.assertIn("runtime.pricing.compute", SYSTEM_PROMPT)
|
||||||
|
|||||||
Reference in New Issue
Block a user