- Add agents package: controller, runner, store, tools, actions, evals, policy - Wire agents into Devii service, session, registry, CLI, and dispatcher - Extend database schema and config for agent persistence - Add news and telegram bridge updates plus landing UI and docs - Cover agents, news, and telegram flows with unit tests
134 lines
3.6 KiB
Python
134 lines
3.6 KiB
Python
# retoor <retoor@molodetz.nl>
|
|
|
|
import json
|
|
|
|
from devplacepy.services.devii.agents.runner import AgentRunner
|
|
from devplacepy.services.devii.cost.tracker import CostTracker, Pricing
|
|
from tests.conftest import run_async
|
|
|
|
|
|
class _Ledger:
|
|
def __init__(self):
|
|
self.spent = 0.0
|
|
|
|
def spent_24h(self, owner_kind, owner_id):
|
|
return self.spent
|
|
|
|
|
|
class _Settings:
|
|
daily_limit_usd = 1.0
|
|
plan_required = True
|
|
verify_required = True
|
|
|
|
|
|
class _StubSession:
|
|
def __init__(self):
|
|
self.cost = CostTracker(pricing=Pricing())
|
|
self.settings = _Settings()
|
|
self._ledger = _Ledger()
|
|
self._llm = None
|
|
self.dispatcher = None
|
|
self.owner_kind = "user"
|
|
self.owner_id = "owner-1"
|
|
|
|
|
|
def _runner(**overrides):
|
|
args = {
|
|
"session": _StubSession(),
|
|
"uid": "run-1",
|
|
"label": "run",
|
|
"category_key": "research",
|
|
"effort": "standard",
|
|
"task": "Compare static site generators.",
|
|
"output_format": "A markdown table.",
|
|
"tools": [],
|
|
"task_scope": False,
|
|
}
|
|
args.update(overrides)
|
|
return AgentRunner(**args)
|
|
|
|
|
|
def _ask_call(call_id, question):
|
|
return {
|
|
"id": call_id,
|
|
"function": {"name": "ask_user", "arguments": json.dumps({"question": question})},
|
|
}
|
|
|
|
|
|
def test_system_prompt_carries_contract_and_behavior():
|
|
runner = _runner(behavior_text="Be terse.")
|
|
prompt = runner.messages[0]["content"]
|
|
assert "no browser" in prompt
|
|
assert "ask_user" in prompt
|
|
assert "Be terse." in prompt
|
|
assert "Compare static site generators." in prompt
|
|
assert "A markdown table." in prompt
|
|
|
|
|
|
def test_pending_ask_found_and_answered():
|
|
runner = _runner()
|
|
runner.messages.append(
|
|
{"role": "assistant", "content": "", "tool_calls": [_ask_call("c1", "Budget?")]}
|
|
)
|
|
runner.messages.append(
|
|
{
|
|
"role": "tool",
|
|
"tool_call_id": "c1",
|
|
"name": "ask_user",
|
|
"content": json.dumps({"status": "paused_for_user"}),
|
|
}
|
|
)
|
|
assert runner._pending_ask() == ("c1", "Budget?")
|
|
runner.apply_answer("zero")
|
|
assert runner._pending_ask() is None
|
|
assert runner.messages[-1]["content"] == "The user answered: zero"
|
|
|
|
|
|
def test_quota_spent_short_circuits_without_llm():
|
|
session = _StubSession()
|
|
session._ledger.spent = 5.0
|
|
runner = _runner(session=session)
|
|
outcome, text = run_async(runner.run_once())
|
|
assert outcome == "error"
|
|
assert "quota" in text
|
|
|
|
|
|
def test_trace_tracks_progress_and_streak():
|
|
runner = _runner()
|
|
runner._trace("call", "fetch_url", "example.com")
|
|
assert runner.tool_calls == 1
|
|
runner._trace("err", "fetch_url", "boom")
|
|
assert runner.error_streak == 1
|
|
runner._trace("ok", "fetch_url", "")
|
|
assert runner.error_streak == 0
|
|
assert "fetch_url" in runner.progress_note
|
|
|
|
|
|
def test_task_scope_wraps_the_loop(monkeypatch):
|
|
from devplacepy.services.devii.tasks.context import in_task_run
|
|
|
|
seen = {}
|
|
|
|
async def fake_loop(**kwargs):
|
|
seen["scoped"] = in_task_run()
|
|
return "done"
|
|
|
|
monkeypatch.setattr(
|
|
"devplacepy.services.devii.agents.runner.react_loop", fake_loop
|
|
)
|
|
run_async(_runner(task_scope=True).run_once())
|
|
assert seen == {"scoped": True}
|
|
run_async(_runner(task_scope=False).run_once())
|
|
assert seen == {"scoped": False}
|
|
|
|
|
|
def test_counts_and_usage_start_at_zero():
|
|
runner = _runner()
|
|
assert runner.counts() == (0, 0, 0.0)
|
|
assert runner.usage() == {
|
|
"prompt_tokens": 0,
|
|
"completion_tokens": 0,
|
|
"cache_hit_tokens": 0,
|
|
"cache_miss_tokens": 0,
|
|
}
|