quick-agent 0.1.2__py3-none-any.whl → 0.1.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- quick_agent/agent_registry.py +5 -25
- quick_agent/llms.txt +1 -1
- quick_agent/models/loaded_agent_file.py +136 -1
- quick_agent/models/output_spec.py +1 -1
- quick_agent/prompting.py +33 -15
- quick_agent/quick_agent.py +99 -38
- {quick_agent-0.1.2.data → quick_agent-0.1.3.data}/data/quick_agent/agents/business-extract-structured.md +1 -1
- {quick_agent-0.1.2.data → quick_agent-0.1.3.data}/data/quick_agent/agents/business-extract.md +1 -1
- {quick_agent-0.1.2.data → quick_agent-0.1.3.data}/data/quick_agent/agents/function-spec-validator.md +1 -1
- {quick_agent-0.1.2.data → quick_agent-0.1.3.data}/data/quick_agent/agents/subagent-validate-eval-list.md +1 -1
- {quick_agent-0.1.2.data → quick_agent-0.1.3.data}/data/quick_agent/agents/subagent-validator-contains.md +8 -1
- {quick_agent-0.1.2.data → quick_agent-0.1.3.data}/data/quick_agent/agents/template.md +12 -1
- {quick_agent-0.1.2.dist-info → quick_agent-0.1.3.dist-info}/METADATA +5 -1
- {quick_agent-0.1.2.dist-info → quick_agent-0.1.3.dist-info}/RECORD +21 -20
- tests/test_agent.py +273 -9
- tests/test_httpx_tools.py +295 -0
- tests/test_orchestrator.py +353 -28
- {quick_agent-0.1.2.dist-info → quick_agent-0.1.3.dist-info}/WHEEL +0 -0
- {quick_agent-0.1.2.dist-info → quick_agent-0.1.3.dist-info}/entry_points.txt +0 -0
- {quick_agent-0.1.2.dist-info → quick_agent-0.1.3.dist-info}/licenses/LICENSE +0 -0
- {quick_agent-0.1.2.dist-info → quick_agent-0.1.3.dist-info}/top_level.txt +0 -0
tests/test_orchestrator.py
CHANGED
|
@@ -27,6 +27,7 @@ from quick_agent.orchestrator import Orchestrator
|
|
|
27
27
|
from quick_agent.quick_agent import QuickAgent
|
|
28
28
|
from quick_agent.quick_agent import build_model
|
|
29
29
|
from quick_agent.quick_agent import resolve_schema
|
|
30
|
+
from quick_agent.prompting import make_user_prompt
|
|
30
31
|
|
|
31
32
|
|
|
32
33
|
class DummyProvider:
|
|
@@ -77,7 +78,8 @@ class FakeAgent:
|
|
|
77
78
|
def __init__(
|
|
78
79
|
self,
|
|
79
80
|
model: Any,
|
|
80
|
-
instructions: str,
|
|
81
|
+
instructions: str | None,
|
|
82
|
+
system_prompt: str | list[str],
|
|
81
83
|
toolsets: list[Any],
|
|
82
84
|
output_type: Any,
|
|
83
85
|
model_settings: Any | None = None,
|
|
@@ -85,6 +87,7 @@ class FakeAgent:
|
|
|
85
87
|
FakeAgent.last_init = {
|
|
86
88
|
"model": model,
|
|
87
89
|
"instructions": instructions,
|
|
90
|
+
"system_prompt": system_prompt,
|
|
88
91
|
"toolsets": toolsets,
|
|
89
92
|
"output_type": output_type,
|
|
90
93
|
"model_settings": model_settings,
|
|
@@ -188,7 +191,12 @@ def _make_loaded_with_chain(
|
|
|
188
191
|
output=output or OutputSpec(file="out/result.json"),
|
|
189
192
|
handoff=handoff or HandoffSpec(),
|
|
190
193
|
)
|
|
191
|
-
return LoadedAgentFile(
|
|
194
|
+
return LoadedAgentFile.from_parts(
|
|
195
|
+
spec=spec,
|
|
196
|
+
instructions="system",
|
|
197
|
+
system_prompt="",
|
|
198
|
+
step_prompts={"step:one": "do thing"},
|
|
199
|
+
)
|
|
192
200
|
|
|
193
201
|
|
|
194
202
|
def _permissions(tmp_path: Path | None = None) -> DirectoryPermissions:
|
|
@@ -237,7 +245,7 @@ def test_resolve_schema_valid_missing_and_invalid() -> None:
|
|
|
237
245
|
chain=[ChainStepSpec(id="s1", kind="text", prompt_section="step:one")],
|
|
238
246
|
schemas={"Good": "schemas.orch:GoodSchema", "Bad": "schemas.orch:NotSchema"},
|
|
239
247
|
)
|
|
240
|
-
loaded = LoadedAgentFile(spec=spec,
|
|
248
|
+
loaded = LoadedAgentFile.from_parts(spec=spec, instructions="", system_prompt="", step_prompts={})
|
|
241
249
|
|
|
242
250
|
try:
|
|
243
251
|
assert resolve_schema(loaded, "Good") is GoodSchema
|
|
@@ -408,21 +416,6 @@ def test_build_structured_model_settings_openai_injects_schema() -> None:
|
|
|
408
416
|
assert json_schema_obj["strict"] is True
|
|
409
417
|
|
|
410
418
|
|
|
411
|
-
def test_build_user_prompt_raises_for_missing_section() -> None:
|
|
412
|
-
step = ChainStepSpec(id="s1", kind="text", prompt_section="step:missing")
|
|
413
|
-
loaded = LoadedAgentFile(spec=_make_loaded_with_chain([step]).spec, body="body", step_prompts={})
|
|
414
|
-
run_input = RunInput(source_path="in.txt", kind="text", text="hi", data=None)
|
|
415
|
-
|
|
416
|
-
qa = object.__new__(QuickAgent)
|
|
417
|
-
qa.loaded = loaded
|
|
418
|
-
qa.run_input = run_input
|
|
419
|
-
qa.state = {"agent_id": "agent-1", "steps": {}, "final_output": None}
|
|
420
|
-
with pytest.raises(KeyError):
|
|
421
|
-
qa._build_user_prompt(
|
|
422
|
-
step=step,
|
|
423
|
-
)
|
|
424
|
-
|
|
425
|
-
|
|
426
419
|
def test_build_user_prompt_uses_prompting(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
427
420
|
step = ChainStepSpec(id="s1", kind="text", prompt_section="step:one")
|
|
428
421
|
loaded = _make_loaded_with_chain([step])
|
|
@@ -434,19 +427,40 @@ def test_build_user_prompt_uses_prompting(monkeypatch: pytest.MonkeyPatch) -> No
|
|
|
434
427
|
qa.loaded = loaded
|
|
435
428
|
qa.run_input = run_input
|
|
436
429
|
qa.state = {"agent_id": "agent-1", "steps": {}, "final_output": None}
|
|
437
|
-
result = qa._build_user_prompt(
|
|
438
|
-
step=step,
|
|
439
|
-
)
|
|
430
|
+
result = qa._build_user_prompt()
|
|
440
431
|
|
|
441
432
|
assert result == "prompt"
|
|
442
433
|
assert recorder.calls == [
|
|
443
434
|
(
|
|
444
|
-
(
|
|
435
|
+
(run_input, {"agent_id": "agent-1", "steps": {}, "final_output": None}),
|
|
445
436
|
{},
|
|
446
437
|
)
|
|
447
438
|
]
|
|
448
439
|
|
|
449
440
|
|
|
441
|
+
@pytest.mark.anyio
|
|
442
|
+
async def test_run_text_step_raises_for_missing_section(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
443
|
+
monkeypatch.setattr(qa_module, "Agent", FakeAgent)
|
|
444
|
+
|
|
445
|
+
step = ChainStepSpec(id="s1", kind="text", prompt_section="step:missing")
|
|
446
|
+
loaded = _make_loaded_with_chain([step])
|
|
447
|
+
run_input = RunInput(source_path="in.txt", kind="text", text="hi", data=None)
|
|
448
|
+
|
|
449
|
+
qa = object.__new__(QuickAgent)
|
|
450
|
+
qa.loaded = loaded
|
|
451
|
+
qa.model = cast(OpenAIChatModel, object())
|
|
452
|
+
qa.model_settings_json = None
|
|
453
|
+
qa.toolset = RecordingToolset()
|
|
454
|
+
qa.tool_ids = []
|
|
455
|
+
qa.run_input = run_input
|
|
456
|
+
qa.state = {"agent_id": "a", "steps": {}, "final_output": None}
|
|
457
|
+
|
|
458
|
+
with pytest.raises(KeyError):
|
|
459
|
+
await qa._run_text_step(
|
|
460
|
+
step=step,
|
|
461
|
+
)
|
|
462
|
+
|
|
463
|
+
|
|
450
464
|
@pytest.mark.anyio
|
|
451
465
|
async def test_run_step_text_returns_output(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
452
466
|
monkeypatch.setattr(qa_module, "Agent", FakeAgent)
|
|
@@ -461,6 +475,7 @@ async def test_run_step_text_returns_output(monkeypatch: pytest.MonkeyPatch) ->
|
|
|
461
475
|
qa.model = cast(OpenAIChatModel, object())
|
|
462
476
|
qa.model_settings_json = None
|
|
463
477
|
qa.toolset = RecordingToolset()
|
|
478
|
+
qa.tool_ids = []
|
|
464
479
|
qa.run_input = run_input
|
|
465
480
|
qa.state = {"agent_id": "a", "steps": {}, "final_output": None}
|
|
466
481
|
output, final = await qa._run_step(
|
|
@@ -470,10 +485,37 @@ async def test_run_step_text_returns_output(monkeypatch: pytest.MonkeyPatch) ->
|
|
|
470
485
|
assert output == "hello"
|
|
471
486
|
assert final == "hello"
|
|
472
487
|
assert FakeAgent.last_init is not None
|
|
473
|
-
assert FakeAgent.last_init["instructions"] == "
|
|
488
|
+
assert FakeAgent.last_init["instructions"] == "systemdo thing"
|
|
489
|
+
assert FakeAgent.last_init["system_prompt"] == []
|
|
474
490
|
assert FakeAgent.last_init["output_type"] is str
|
|
475
491
|
|
|
476
492
|
|
|
493
|
+
@pytest.mark.anyio
|
|
494
|
+
async def test_run_text_step_omits_tools_when_disabled(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
495
|
+
monkeypatch.setattr(qa_module, "Agent", FakeAgent)
|
|
496
|
+
FakeAgent.next_output = "hello"
|
|
497
|
+
|
|
498
|
+
step = ChainStepSpec(id="s1", kind="text", prompt_section="step:one")
|
|
499
|
+
loaded = _make_loaded_with_chain([step])
|
|
500
|
+
run_input = RunInput(source_path="in.txt", kind="text", text="hi", data=None)
|
|
501
|
+
|
|
502
|
+
qa = object.__new__(QuickAgent)
|
|
503
|
+
qa.loaded = loaded
|
|
504
|
+
qa.model = cast(OpenAIChatModel, object())
|
|
505
|
+
qa.model_settings_json = None
|
|
506
|
+
qa.toolset = RecordingToolset()
|
|
507
|
+
qa.run_input = run_input
|
|
508
|
+
qa.state = {"agent_id": "a", "steps": {}, "final_output": None}
|
|
509
|
+
qa.tool_ids = []
|
|
510
|
+
|
|
511
|
+
await qa._run_text_step(
|
|
512
|
+
step=step,
|
|
513
|
+
)
|
|
514
|
+
|
|
515
|
+
assert FakeAgent.last_init is not None
|
|
516
|
+
assert FakeAgent.last_init["toolsets"] == []
|
|
517
|
+
|
|
518
|
+
|
|
477
519
|
@pytest.mark.anyio
|
|
478
520
|
async def test_run_step_structured_parses_json_with_fallback(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
479
521
|
monkeypatch.setattr(qa_module, "Agent", FakeAgent)
|
|
@@ -490,7 +532,12 @@ async def test_run_step_structured_parses_json_with_fallback(monkeypatch: pytest
|
|
|
490
532
|
chain=[step],
|
|
491
533
|
schemas={"Example": "schemas.struct:ExampleSchema"},
|
|
492
534
|
)
|
|
493
|
-
loaded = LoadedAgentFile(
|
|
535
|
+
loaded = LoadedAgentFile.from_parts(
|
|
536
|
+
spec=spec,
|
|
537
|
+
instructions="system",
|
|
538
|
+
system_prompt="",
|
|
539
|
+
step_prompts={"step:one": "do thing"},
|
|
540
|
+
)
|
|
494
541
|
run_input = RunInput(source_path="in.json", kind="json", text="{}", data={})
|
|
495
542
|
|
|
496
543
|
try:
|
|
@@ -499,6 +546,7 @@ async def test_run_step_structured_parses_json_with_fallback(monkeypatch: pytest
|
|
|
499
546
|
qa.model = cast(OpenAIChatModel, object())
|
|
500
547
|
qa.model_settings_json = {"extra_body": {"format": "json"}}
|
|
501
548
|
qa.toolset = RecordingToolset()
|
|
549
|
+
qa.tool_ids = []
|
|
502
550
|
qa.run_input = run_input
|
|
503
551
|
qa.state = {"agent_id": "a", "steps": {}, "final_output": None}
|
|
504
552
|
output, final = await qa._run_step(
|
|
@@ -510,6 +558,8 @@ async def test_run_step_structured_parses_json_with_fallback(monkeypatch: pytest
|
|
|
510
558
|
assert output == {"x": 7}
|
|
511
559
|
assert isinstance(final, ExampleSchema)
|
|
512
560
|
assert final.x == 7
|
|
561
|
+
assert FakeAgent.last_init is not None
|
|
562
|
+
assert FakeAgent.last_init["output_type"] is ExampleSchema
|
|
513
563
|
|
|
514
564
|
|
|
515
565
|
@pytest.mark.anyio
|
|
@@ -544,6 +594,7 @@ async def test_run_text_step_uses_build_user_prompt(monkeypatch: pytest.MonkeyPa
|
|
|
544
594
|
qa.loaded = loaded
|
|
545
595
|
qa.model = cast(OpenAIChatModel, object())
|
|
546
596
|
qa.toolset = RecordingToolset()
|
|
597
|
+
qa.tool_ids = []
|
|
547
598
|
qa.run_input = run_input
|
|
548
599
|
monkeypatch.setattr(qa, "_build_user_prompt", SyncCallRecorder(return_value="prompt"))
|
|
549
600
|
|
|
@@ -556,6 +607,129 @@ async def test_run_text_step_uses_build_user_prompt(monkeypatch: pytest.MonkeyPa
|
|
|
556
607
|
assert FakeAgent.last_prompt == "prompt"
|
|
557
608
|
|
|
558
609
|
|
|
610
|
+
@pytest.mark.anyio
|
|
611
|
+
async def test_run_text_step_no_instructions_or_system_prompt(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
612
|
+
monkeypatch.setattr(qa_module, "Agent", FakeAgent)
|
|
613
|
+
FakeAgent.next_output = "ok"
|
|
614
|
+
|
|
615
|
+
step = ChainStepSpec(id="s1", kind="text", prompt_section="step:one")
|
|
616
|
+
spec = AgentSpec(
|
|
617
|
+
name="test",
|
|
618
|
+
model=ModelSpec(base_url="http://x", model_name="m"),
|
|
619
|
+
chain=[step],
|
|
620
|
+
output=OutputSpec(file=None),
|
|
621
|
+
)
|
|
622
|
+
loaded = LoadedAgentFile.from_parts(
|
|
623
|
+
spec=spec,
|
|
624
|
+
instructions="",
|
|
625
|
+
system_prompt="",
|
|
626
|
+
step_prompts={"step:one": "do thing"},
|
|
627
|
+
)
|
|
628
|
+
run_input = RunInput(source_path="in.txt", kind="text", text="hi", data=None)
|
|
629
|
+
|
|
630
|
+
qa = object.__new__(QuickAgent)
|
|
631
|
+
qa.loaded = loaded
|
|
632
|
+
qa.model = cast(OpenAIChatModel, object())
|
|
633
|
+
qa.model_settings_json = None
|
|
634
|
+
qa.toolset = RecordingToolset()
|
|
635
|
+
qa.tool_ids = []
|
|
636
|
+
qa.run_input = run_input
|
|
637
|
+
qa.state = {"agent_id": "a", "steps": {}, "final_output": None}
|
|
638
|
+
|
|
639
|
+
output, final = await qa._run_text_step(
|
|
640
|
+
step=step,
|
|
641
|
+
)
|
|
642
|
+
|
|
643
|
+
assert output == "ok"
|
|
644
|
+
assert final == "ok"
|
|
645
|
+
assert FakeAgent.last_init is not None
|
|
646
|
+
assert FakeAgent.last_init["instructions"] == "do thing"
|
|
647
|
+
assert FakeAgent.last_init["system_prompt"] == []
|
|
648
|
+
assert FakeAgent.last_prompt == make_user_prompt(run_input, qa.state)
|
|
649
|
+
|
|
650
|
+
|
|
651
|
+
@pytest.mark.anyio
|
|
652
|
+
async def test_run_text_step_system_prompt_only(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
653
|
+
monkeypatch.setattr(qa_module, "Agent", FakeAgent)
|
|
654
|
+
FakeAgent.next_output = "ok"
|
|
655
|
+
|
|
656
|
+
step = ChainStepSpec(id="s1", kind="text", prompt_section="step:one")
|
|
657
|
+
spec = AgentSpec(
|
|
658
|
+
name="test",
|
|
659
|
+
model=ModelSpec(base_url="http://x", model_name="m"),
|
|
660
|
+
chain=[step],
|
|
661
|
+
output=OutputSpec(file=None),
|
|
662
|
+
)
|
|
663
|
+
loaded = LoadedAgentFile.from_parts(
|
|
664
|
+
spec=spec,
|
|
665
|
+
instructions="",
|
|
666
|
+
system_prompt="You are concise.",
|
|
667
|
+
step_prompts={"step:one": "do thing"},
|
|
668
|
+
)
|
|
669
|
+
run_input = RunInput(source_path="in.txt", kind="text", text="hi", data=None)
|
|
670
|
+
|
|
671
|
+
qa = object.__new__(QuickAgent)
|
|
672
|
+
qa.loaded = loaded
|
|
673
|
+
qa.model = cast(OpenAIChatModel, object())
|
|
674
|
+
qa.model_settings_json = None
|
|
675
|
+
qa.toolset = RecordingToolset()
|
|
676
|
+
qa.tool_ids = []
|
|
677
|
+
qa.run_input = run_input
|
|
678
|
+
qa.state = {"agent_id": "a", "steps": {}, "final_output": None}
|
|
679
|
+
|
|
680
|
+
output, final = await qa._run_text_step(
|
|
681
|
+
step=step,
|
|
682
|
+
)
|
|
683
|
+
|
|
684
|
+
assert output == "ok"
|
|
685
|
+
assert final == "ok"
|
|
686
|
+
assert FakeAgent.last_init is not None
|
|
687
|
+
assert FakeAgent.last_init["instructions"] == "do thing"
|
|
688
|
+
assert FakeAgent.last_init["system_prompt"] == "You are concise."
|
|
689
|
+
assert FakeAgent.last_prompt == make_user_prompt(run_input, qa.state)
|
|
690
|
+
|
|
691
|
+
|
|
692
|
+
@pytest.mark.anyio
|
|
693
|
+
async def test_run_text_step_instructions_only(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
694
|
+
monkeypatch.setattr(qa_module, "Agent", FakeAgent)
|
|
695
|
+
FakeAgent.next_output = "ok"
|
|
696
|
+
|
|
697
|
+
step = ChainStepSpec(id="s1", kind="text", prompt_section="step:one")
|
|
698
|
+
spec = AgentSpec(
|
|
699
|
+
name="test",
|
|
700
|
+
model=ModelSpec(base_url="http://x", model_name="m"),
|
|
701
|
+
chain=[step],
|
|
702
|
+
output=OutputSpec(file=None),
|
|
703
|
+
)
|
|
704
|
+
loaded = LoadedAgentFile.from_parts(
|
|
705
|
+
spec=spec,
|
|
706
|
+
instructions="Use the tool.",
|
|
707
|
+
system_prompt="",
|
|
708
|
+
step_prompts={"step:one": "do thing"},
|
|
709
|
+
)
|
|
710
|
+
run_input = RunInput(source_path="in.txt", kind="text", text="hi", data=None)
|
|
711
|
+
|
|
712
|
+
qa = object.__new__(QuickAgent)
|
|
713
|
+
qa.loaded = loaded
|
|
714
|
+
qa.model = cast(OpenAIChatModel, object())
|
|
715
|
+
qa.model_settings_json = None
|
|
716
|
+
qa.toolset = RecordingToolset()
|
|
717
|
+
qa.tool_ids = []
|
|
718
|
+
qa.run_input = run_input
|
|
719
|
+
qa.state = {"agent_id": "a", "steps": {}, "final_output": None}
|
|
720
|
+
|
|
721
|
+
output, final = await qa._run_text_step(
|
|
722
|
+
step=step,
|
|
723
|
+
)
|
|
724
|
+
|
|
725
|
+
assert output == "ok"
|
|
726
|
+
assert final == "ok"
|
|
727
|
+
assert FakeAgent.last_init is not None
|
|
728
|
+
assert FakeAgent.last_init["instructions"] == "Use the tool.do thing"
|
|
729
|
+
assert FakeAgent.last_init["system_prompt"] == []
|
|
730
|
+
assert FakeAgent.last_prompt == make_user_prompt(run_input, qa.state)
|
|
731
|
+
|
|
732
|
+
|
|
559
733
|
@pytest.mark.anyio
|
|
560
734
|
async def test_run_structured_step_missing_schema_raises() -> None:
|
|
561
735
|
step = ChainStepSpec(id="s1", kind="structured", prompt_section="step:one", output_schema=None)
|
|
@@ -590,7 +764,12 @@ async def test_run_structured_step_parses_json(monkeypatch: pytest.MonkeyPatch)
|
|
|
590
764
|
chain=[step],
|
|
591
765
|
schemas={"Example": "schemas.struct2:ExampleSchema"},
|
|
592
766
|
)
|
|
593
|
-
loaded = LoadedAgentFile(
|
|
767
|
+
loaded = LoadedAgentFile.from_parts(
|
|
768
|
+
spec=spec,
|
|
769
|
+
instructions="system",
|
|
770
|
+
system_prompt="",
|
|
771
|
+
step_prompts={"step:one": "do thing"},
|
|
772
|
+
)
|
|
594
773
|
run_input = RunInput(source_path="in.json", kind="json", text="{}", data={})
|
|
595
774
|
|
|
596
775
|
try:
|
|
@@ -599,6 +778,7 @@ async def test_run_structured_step_parses_json(monkeypatch: pytest.MonkeyPatch)
|
|
|
599
778
|
qa.model = cast(OpenAIChatModel, object())
|
|
600
779
|
qa.model_settings_json = None
|
|
601
780
|
qa.toolset = RecordingToolset()
|
|
781
|
+
qa.tool_ids = []
|
|
602
782
|
qa.run_input = run_input
|
|
603
783
|
qa.state = {"agent_id": "a", "steps": {}, "final_output": None}
|
|
604
784
|
output, final = await qa._run_structured_step(
|
|
@@ -627,7 +807,12 @@ async def test_run_structured_step_adds_json_schema_for_openai(monkeypatch: pyte
|
|
|
627
807
|
chain=[step],
|
|
628
808
|
schemas={"Example": "schemas.struct3:ExampleSchema"},
|
|
629
809
|
)
|
|
630
|
-
loaded = LoadedAgentFile(
|
|
810
|
+
loaded = LoadedAgentFile.from_parts(
|
|
811
|
+
spec=spec,
|
|
812
|
+
instructions="system",
|
|
813
|
+
system_prompt="",
|
|
814
|
+
step_prompts={"step:one": "do thing"},
|
|
815
|
+
)
|
|
631
816
|
run_input = RunInput(source_path="in.json", kind="json", text="{}", data={})
|
|
632
817
|
|
|
633
818
|
try:
|
|
@@ -636,6 +821,7 @@ async def test_run_structured_step_adds_json_schema_for_openai(monkeypatch: pyte
|
|
|
636
821
|
qa.model = cast(OpenAIChatModel, DummyOpenAIModel("https://api.openai.com/v1"))
|
|
637
822
|
qa.model_settings_json = None
|
|
638
823
|
qa.toolset = RecordingToolset()
|
|
824
|
+
qa.tool_ids = []
|
|
639
825
|
qa.run_input = run_input
|
|
640
826
|
qa.state = {"agent_id": "a", "steps": {}, "final_output": None}
|
|
641
827
|
await qa._run_structured_step(
|
|
@@ -675,6 +861,80 @@ async def test_run_chain_updates_state_and_returns_last() -> None:
|
|
|
675
861
|
assert qa.calls == ["s1", "s2"]
|
|
676
862
|
|
|
677
863
|
|
|
864
|
+
@pytest.mark.anyio
|
|
865
|
+
async def test_run_chain_single_shot_system_prompt_only(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
866
|
+
monkeypatch.setattr(qa_module, "Agent", FakeAgent)
|
|
867
|
+
FakeAgent.next_output = "hello"
|
|
868
|
+
|
|
869
|
+
spec = AgentSpec(
|
|
870
|
+
name="test",
|
|
871
|
+
model=ModelSpec(base_url="http://x", model_name="m"),
|
|
872
|
+
chain=[],
|
|
873
|
+
output=OutputSpec(file=None),
|
|
874
|
+
)
|
|
875
|
+
loaded = LoadedAgentFile.from_parts(
|
|
876
|
+
spec=spec,
|
|
877
|
+
instructions="",
|
|
878
|
+
system_prompt="You are concise.",
|
|
879
|
+
step_prompts={},
|
|
880
|
+
)
|
|
881
|
+
run_input = RunInput(source_path="in.txt", kind="text", text="hi", data=None)
|
|
882
|
+
|
|
883
|
+
qa = object.__new__(QuickAgent)
|
|
884
|
+
qa.loaded = loaded
|
|
885
|
+
qa.model = cast(OpenAIChatModel, object())
|
|
886
|
+
qa.model_settings_json = None
|
|
887
|
+
qa.toolset = RecordingToolset()
|
|
888
|
+
qa.tool_ids = []
|
|
889
|
+
qa.run_input = run_input
|
|
890
|
+
qa.state = {"agent_id": "a", "steps": {}, "final_output": None}
|
|
891
|
+
|
|
892
|
+
output = await qa._run_chain()
|
|
893
|
+
|
|
894
|
+
assert output == "hello"
|
|
895
|
+
assert FakeAgent.last_init is not None
|
|
896
|
+
assert FakeAgent.last_init["instructions"] is None
|
|
897
|
+
assert FakeAgent.last_init["system_prompt"] == "You are concise."
|
|
898
|
+
assert FakeAgent.last_prompt == make_user_prompt(run_input, qa.state)
|
|
899
|
+
|
|
900
|
+
|
|
901
|
+
@pytest.mark.anyio
|
|
902
|
+
async def test_run_chain_single_shot_instructions_only(monkeypatch: pytest.MonkeyPatch) -> None:
|
|
903
|
+
monkeypatch.setattr(qa_module, "Agent", FakeAgent)
|
|
904
|
+
FakeAgent.next_output = "hello"
|
|
905
|
+
|
|
906
|
+
spec = AgentSpec(
|
|
907
|
+
name="test",
|
|
908
|
+
model=ModelSpec(base_url="http://x", model_name="m"),
|
|
909
|
+
chain=[],
|
|
910
|
+
output=OutputSpec(file=None),
|
|
911
|
+
)
|
|
912
|
+
loaded = LoadedAgentFile.from_parts(
|
|
913
|
+
spec=spec,
|
|
914
|
+
instructions="Use the tool.",
|
|
915
|
+
system_prompt="",
|
|
916
|
+
step_prompts={},
|
|
917
|
+
)
|
|
918
|
+
run_input = RunInput(source_path="in.txt", kind="text", text="hi", data=None)
|
|
919
|
+
|
|
920
|
+
qa = object.__new__(QuickAgent)
|
|
921
|
+
qa.loaded = loaded
|
|
922
|
+
qa.model = cast(OpenAIChatModel, object())
|
|
923
|
+
qa.model_settings_json = None
|
|
924
|
+
qa.toolset = RecordingToolset()
|
|
925
|
+
qa.tool_ids = []
|
|
926
|
+
qa.run_input = run_input
|
|
927
|
+
qa.state = {"agent_id": "a", "steps": {}, "final_output": None}
|
|
928
|
+
|
|
929
|
+
output = await qa._run_chain()
|
|
930
|
+
|
|
931
|
+
assert output == "hello"
|
|
932
|
+
assert FakeAgent.last_init is not None
|
|
933
|
+
assert FakeAgent.last_init["instructions"] == "Use the tool."
|
|
934
|
+
assert FakeAgent.last_init["system_prompt"] == []
|
|
935
|
+
assert FakeAgent.last_prompt == make_user_prompt(run_input, qa.state)
|
|
936
|
+
|
|
937
|
+
|
|
678
938
|
def test_write_final_output_serializes_model(tmp_path: Path) -> None:
|
|
679
939
|
safe_root = tmp_path / "safe"
|
|
680
940
|
out_path = safe_root / "out.json"
|
|
@@ -768,7 +1028,12 @@ async def test_run_agent_wires_dependencies(monkeypatch: pytest.MonkeyPatch, tmp
|
|
|
768
1028
|
tools=["tool.a", "agent.call", "tool.a"],
|
|
769
1029
|
output=OutputSpec(file=str(tmp_path / "out.json")),
|
|
770
1030
|
)
|
|
771
|
-
loaded = LoadedAgentFile(
|
|
1031
|
+
loaded = LoadedAgentFile.from_parts(
|
|
1032
|
+
spec=spec,
|
|
1033
|
+
instructions="system",
|
|
1034
|
+
system_prompt="",
|
|
1035
|
+
step_prompts={"step:one": "do thing"},
|
|
1036
|
+
)
|
|
772
1037
|
|
|
773
1038
|
run_input = RunInput(source_path=str(tmp_path / "input.json"), kind="json", text="{}", data={})
|
|
774
1039
|
toolset = RecordingToolset()
|
|
@@ -819,6 +1084,8 @@ async def test_run_agent_wires_dependencies(monkeypatch: pytest.MonkeyPatch, tmp
|
|
|
819
1084
|
assert load_args[1].root == _permissions(tmp_path).root
|
|
820
1085
|
assert build_model_recorder.calls == [((loaded.spec.model,), {})]
|
|
821
1086
|
|
|
1087
|
+
assert build_settings_recorder.calls == [((loaded.spec.model,), {})]
|
|
1088
|
+
|
|
822
1089
|
assert build_toolset_recorder.calls
|
|
823
1090
|
args, kwargs = build_toolset_recorder.calls[0]
|
|
824
1091
|
assert kwargs == {}
|
|
@@ -829,7 +1096,6 @@ async def test_run_agent_wires_dependencies(monkeypatch: pytest.MonkeyPatch, tmp
|
|
|
829
1096
|
]
|
|
830
1097
|
assert isinstance(args[1], DirectoryPermissions)
|
|
831
1098
|
|
|
832
|
-
assert build_settings_recorder.calls == [((loaded.spec.model,), {})]
|
|
833
1099
|
maybe_args, maybe_kwargs = maybe_inject_recorder.calls[0]
|
|
834
1100
|
assert maybe_kwargs == {}
|
|
835
1101
|
assert maybe_args[0] == [
|
|
@@ -851,6 +1117,65 @@ async def test_run_agent_wires_dependencies(monkeypatch: pytest.MonkeyPatch, tmp
|
|
|
851
1117
|
assert handoff_recorder.calls == [{"args": ("final",), "kwargs": {}}]
|
|
852
1118
|
|
|
853
1119
|
|
|
1120
|
+
@pytest.mark.anyio
|
|
1121
|
+
async def test_run_skips_write_when_output_file_missing(
|
|
1122
|
+
monkeypatch: pytest.MonkeyPatch, tmp_path: Path
|
|
1123
|
+
) -> None:
|
|
1124
|
+
step = ChainStepSpec(id="s1", kind="text", prompt_section="step:one")
|
|
1125
|
+
spec = AgentSpec(
|
|
1126
|
+
name="test",
|
|
1127
|
+
model=ModelSpec(base_url="http://x", model_name="m"),
|
|
1128
|
+
chain=[step],
|
|
1129
|
+
output=OutputSpec(file=None),
|
|
1130
|
+
)
|
|
1131
|
+
loaded = LoadedAgentFile.from_parts(
|
|
1132
|
+
spec=spec,
|
|
1133
|
+
instructions="system",
|
|
1134
|
+
system_prompt="",
|
|
1135
|
+
step_prompts={"step:one": "do thing"},
|
|
1136
|
+
)
|
|
1137
|
+
|
|
1138
|
+
run_input = RunInput(source_path=str(tmp_path / "input.json"), kind="json", text="{}", data={})
|
|
1139
|
+
toolset = RecordingToolset()
|
|
1140
|
+
model = object()
|
|
1141
|
+
|
|
1142
|
+
load_input_recorder = SyncCallRecorder(return_value=run_input)
|
|
1143
|
+
build_model_recorder = SyncCallRecorder(return_value=model)
|
|
1144
|
+
build_toolset_recorder = SyncCallRecorder(return_value=toolset)
|
|
1145
|
+
build_settings_recorder = SyncCallRecorder(return_value=None)
|
|
1146
|
+
maybe_inject_recorder = SyncCallRecorder(return_value=None)
|
|
1147
|
+
run_chain_recorder = AsyncCallRecorder(return_value="final")
|
|
1148
|
+
write_output_recorder = SyncCallRecorder(return_value=tmp_path / "out.json")
|
|
1149
|
+
handoff_recorder = AsyncCallRecorder(return_value=None)
|
|
1150
|
+
|
|
1151
|
+
monkeypatch.setattr(input_adaptors_module, "load_input", load_input_recorder)
|
|
1152
|
+
monkeypatch.setattr(qa_module, "build_model", build_model_recorder)
|
|
1153
|
+
monkeypatch.setattr(QuickAgent, "_build_model_settings", build_settings_recorder)
|
|
1154
|
+
monkeypatch.setattr(QuickAgent, "_run_chain", run_chain_recorder)
|
|
1155
|
+
monkeypatch.setattr(QuickAgent, "_write_final_output", write_output_recorder)
|
|
1156
|
+
monkeypatch.setattr(QuickAgent, "_handle_handoff", handoff_recorder)
|
|
1157
|
+
|
|
1158
|
+
tools = AgentTools([tmp_path])
|
|
1159
|
+
monkeypatch.setattr(tools, "build_toolset", build_toolset_recorder)
|
|
1160
|
+
monkeypatch.setattr(tools, "maybe_inject_agent_call", maybe_inject_recorder)
|
|
1161
|
+
fake_registry = FakeRegistry(loaded)
|
|
1162
|
+
|
|
1163
|
+
agent = QuickAgent(
|
|
1164
|
+
registry=fake_registry,
|
|
1165
|
+
tools=tools,
|
|
1166
|
+
directory_permissions=_permissions(tmp_path),
|
|
1167
|
+
agent_id="agent-1",
|
|
1168
|
+
input_data=tmp_path / "input.json",
|
|
1169
|
+
extra_tools=None,
|
|
1170
|
+
)
|
|
1171
|
+
|
|
1172
|
+
result = await agent.run()
|
|
1173
|
+
|
|
1174
|
+
assert result == "final"
|
|
1175
|
+
assert write_output_recorder.calls == []
|
|
1176
|
+
assert handoff_recorder.calls == [{"args": ("final",), "kwargs": {}}]
|
|
1177
|
+
|
|
1178
|
+
|
|
854
1179
|
@pytest.mark.anyio
|
|
855
1180
|
@pytest.mark.parametrize(
|
|
856
1181
|
("nested_output", "expected_write_output"),
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|