4b6817381b
CI (OpenClaw E2E) / openclaw test (push) Has been cancelled
CI / coverage-report (push) Has been cancelled
CI / test-kubernetes (push) Has been cancelled
CI / should-run-thorough (push) Has been cancelled
CI / test-thorough (cloudwatch-demo) (push) Has been cancelled
CI / test-thorough (flink-ecs) (push) Has been cancelled
CI / test-thorough (upstream-lambda) (push) Has been cancelled
CI / test-thorough (prefect-ecs-fargate) (push) Has been cancelled
Release / build-binaries (zip, opensre.exe, onefile, windows-latest, windows-x64) (push) Has been cancelled
Benchmark image — build + push to ECR (any adapter) / build + push (push) Has been cancelled
CI / quality (ubuntu-latest) (push) Has been cancelled
CI / test (tools-runtime) (push) Has been cancelled
CI / test (e2e-general) (push) Has been cancelled
CI / test (cli-runtime) (push) Has been cancelled
CI / test (e2e-provider-and-openclaw) (push) Has been cancelled
CI / test (integrations-and-misc) (push) Has been cancelled
Release / verify (push) Has been cancelled
Release / build-python-dist (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, macos-15-intel, darwin-x64) (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, macos-latest, darwin-arm64) (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, ubuntu-22.04, linux-x64) (push) Has been cancelled
Release / publish-release (push) Has been cancelled
Release / publish-main-release (push) Has been cancelled
Interactive Shell Live (PR + post-merge) / turn-checks (no-LLM) (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
Interactive Shell Live (PR + post-merge) / turn-live shard ${{ matrix.shard_index }} (push) Has been cancelled
Release / prepare (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, ubuntu-22.04-arm, linux-arm64) (push) Has been cancelled
Synthetic Deterministic Tests / Synthetic offline (deterministic) (push) Has been cancelled
166 lines
5.0 KiB
Python
166 lines
5.0 KiB
Python
"""Unit tests for ``Agent.run`` validation and run-input resolution."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import Any
|
|
|
|
import pytest
|
|
|
|
from core.agent import Agent
|
|
from core.agent.run_io import AgentRunInput
|
|
from core.agent_harness.prompts import PromptEnvelope
|
|
from core.agent_harness.turns.turn_snapshot import TurnSnapshot
|
|
from core.llm.types import AgentLLMResponse
|
|
from core.types import AgentTool
|
|
|
|
|
|
class _NoToolLLM:
|
|
def tool_schemas(self, _tools: list[Any]) -> list[dict[str, Any]]:
|
|
return []
|
|
|
|
def invoke(
|
|
self,
|
|
_messages: list[dict[str, Any]],
|
|
*,
|
|
system: str | None = None,
|
|
tools: list[dict[str, Any]] | None = None,
|
|
) -> AgentLLMResponse:
|
|
_ = (system, tools)
|
|
return AgentLLMResponse(content="ok", tool_calls=[], raw_content=None)
|
|
|
|
@staticmethod
|
|
def build_assistant_message(content: str, tool_calls: list[object]) -> dict[str, object]:
|
|
return {"role": "assistant", "content": content, "tool_calls": tool_calls}
|
|
|
|
@staticmethod
|
|
def build_tool_result_message(
|
|
_tool_calls: list[object], _results: list[object]
|
|
) -> dict[str, object]:
|
|
return {"role": "tool", "content": "[]"}
|
|
|
|
|
|
def _tool() -> AgentTool:
|
|
return AgentTool(
|
|
name="inspect",
|
|
description="inspect",
|
|
input_schema={"type": "object", "properties": {}, "additionalProperties": False},
|
|
execute=lambda _payload, _ctx: {"ok": True},
|
|
)
|
|
|
|
|
|
def _runtime_request() -> TurnSnapshot:
|
|
tool = _tool()
|
|
return TurnSnapshot(
|
|
text="turn",
|
|
conversation_messages=(),
|
|
configured_integrations=(),
|
|
configured_integrations_known=True,
|
|
last_state=None,
|
|
last_synthetic_observation_path=None,
|
|
reasoning_effort=None,
|
|
system_prompt=PromptEnvelope.from_text("runtime system"),
|
|
available_tools=(tool,),
|
|
active_tools=(tool,),
|
|
resolved_integrations={"github": {"configured": True}},
|
|
max_iterations=2,
|
|
)
|
|
|
|
|
|
def test_run_requires_initial_messages_or_runtime_request() -> None:
|
|
agent = Agent(system="sys", tools=[], resolved_integrations={}, max_iterations=1)
|
|
|
|
with pytest.raises(ValueError, match="requires initial_messages or runtime_request"):
|
|
agent.run()
|
|
|
|
|
|
def test_run_with_initial_messages_requires_system_at_construction() -> None:
|
|
agent = Agent(tools=[], resolved_integrations={}, max_iterations=1)
|
|
|
|
with pytest.raises(ValueError, match="system= must be set"):
|
|
agent.run([{"role": "user", "content": "hello"}])
|
|
|
|
|
|
def test_run_with_initial_messages_requires_max_iterations_at_construction() -> None:
|
|
agent = Agent(system="sys", tools=[], resolved_integrations={})
|
|
|
|
with pytest.raises(ValueError, match="max_iterations= must be set"):
|
|
agent.run([{"role": "user", "content": "hello"}])
|
|
|
|
|
|
def test_build_run_input_from_runtime_request_uses_construction_llm() -> None:
|
|
llm = _NoToolLLM()
|
|
agent = Agent(
|
|
llm=llm,
|
|
system="ignored",
|
|
tools=[],
|
|
resolved_integrations={},
|
|
max_iterations=1,
|
|
)
|
|
ctx = _runtime_request()
|
|
|
|
run_input = agent._build_run_input(None, ctx)
|
|
|
|
assert isinstance(run_input, AgentRunInput)
|
|
assert run_input.llm is llm
|
|
assert run_input.system == "runtime system"
|
|
|
|
|
|
def test_build_run_input_from_messages_uses_construction_config() -> None:
|
|
llm = _NoToolLLM()
|
|
tool = _tool()
|
|
agent = Agent(
|
|
llm=llm,
|
|
system="construction system",
|
|
tools=[tool],
|
|
resolved_integrations={"sentry": {"configured": True}},
|
|
max_iterations=3,
|
|
tool_resources={"marker": "instance"},
|
|
)
|
|
|
|
run_input = agent._build_run_input([{"role": "user", "content": "hello"}], None)
|
|
|
|
assert run_input.llm is llm
|
|
assert run_input.system == "construction system"
|
|
assert [t.name for t in run_input.tools] == ["inspect"]
|
|
assert run_input.resolved == {"sentry": {"configured": True}}
|
|
assert run_input.tool_resources == {"marker": "instance"}
|
|
assert run_input.max_iterations == 3
|
|
|
|
|
|
def test_get_llm_caches_process_wide_client(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
calls = {"count": 0}
|
|
|
|
def _factory() -> _NoToolLLM:
|
|
calls["count"] += 1
|
|
return _NoToolLLM()
|
|
|
|
monkeypatch.setattr("core.llm.factory.get_llm", lambda _role: _factory())
|
|
agent = Agent(system="sys", tools=[], resolved_integrations={}, max_iterations=1)
|
|
|
|
first = agent._get_llm()
|
|
second = agent._get_llm()
|
|
|
|
assert first is second
|
|
assert calls["count"] == 1
|
|
|
|
|
|
def test_runtime_request_path_uses_explicit_construction_llm_not_process_default(
|
|
monkeypatch: pytest.MonkeyPatch,
|
|
) -> None:
|
|
explicit = _NoToolLLM()
|
|
default = _NoToolLLM()
|
|
monkeypatch.setattr("core.llm.factory.get_llm", lambda _role: default)
|
|
|
|
agent = Agent(
|
|
llm=explicit,
|
|
system="ignored",
|
|
tools=[],
|
|
resolved_integrations={},
|
|
max_iterations=1,
|
|
)
|
|
|
|
run_input = agent._build_run_input(None, _runtime_request())
|
|
|
|
assert run_input.llm is explicit
|
|
assert run_input.llm is not default
|