Introduce Devii agents subsystem with supervisor, policy, and evals
- 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
This commit is contained in:
@@ -0,0 +1,133 @@
|
||||
# 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,
|
||||
}
|
||||
Reference in New Issue
Block a user