soliplex 0.77.2__tar.gz → 0.78.1__tar.gz
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.
- {soliplex-0.77.2/src/soliplex.egg-info → soliplex-0.78.1}/PKG-INFO +1 -1
- {soliplex-0.77.2 → soliplex-0.78.1}/pyproject.toml +1 -1
- {soliplex-0.77.2 → soliplex-0.78.1}/src/soliplex/agents.py +5 -12
- {soliplex-0.77.2 → soliplex-0.78.1}/src/soliplex/cli/audit.py +75 -41
- {soliplex-0.77.2 → soliplex-0.78.1}/src/soliplex/config/agents.py +15 -4
- {soliplex-0.77.2 → soliplex-0.78.1}/src/soliplex/config/skills.py +86 -11
- {soliplex-0.77.2 → soliplex-0.78.1}/src/soliplex/installation.py +1 -1
- {soliplex-0.77.2 → soliplex-0.78.1}/src/soliplex/models.py +2 -2
- {soliplex-0.77.2 → soliplex-0.78.1}/src/soliplex/quizzes.py +1 -1
- {soliplex-0.77.2 → soliplex-0.78.1}/src/soliplex/skills/bwrap_sandbox/SKILL.md +44 -18
- {soliplex-0.77.2 → soliplex-0.78.1}/src/soliplex/skills/bwrap_sandbox/__init__.py +194 -10
- {soliplex-0.77.2 → soliplex-0.78.1/src/soliplex.egg-info}/PKG-INFO +1 -1
- {soliplex-0.77.2 → soliplex-0.78.1}/LICENSE +0 -0
- {soliplex-0.77.2 → soliplex-0.78.1}/MANIFEST.in +0 -0
- {soliplex-0.77.2 → soliplex-0.78.1}/README.md +0 -0
- {soliplex-0.77.2 → soliplex-0.78.1}/example/skills/bare-bones/SKILL.md +0 -0
- {soliplex-0.77.2 → soliplex-0.78.1}/example/skills/stanzas/SKILL.md +0 -0
- {soliplex-0.77.2 → soliplex-0.78.1}/setup.cfg +0 -0
- {soliplex-0.77.2 → soliplex-0.78.1}/skills/soliplex-docs/SKILL.md +0 -0
- {soliplex-0.77.2 → soliplex-0.78.1}/src/soliplex/agui/__init__.py +0 -0
- {soliplex-0.77.2 → soliplex-0.78.1}/src/soliplex/agui/parser.py +0 -0
- {soliplex-0.77.2 → soliplex-0.78.1}/src/soliplex/agui/persistence.py +0 -0
- {soliplex-0.77.2 → soliplex-0.78.1}/src/soliplex/agui/schema.py +0 -0
- {soliplex-0.77.2 → soliplex-0.78.1}/src/soliplex/agui/util.py +0 -0
- {soliplex-0.77.2 → soliplex-0.78.1}/src/soliplex/authn.py +0 -0
- {soliplex-0.77.2 → soliplex-0.78.1}/src/soliplex/authz/__init__.py +0 -0
- {soliplex-0.77.2 → soliplex-0.78.1}/src/soliplex/authz/persistence.py +0 -0
- {soliplex-0.77.2 → soliplex-0.78.1}/src/soliplex/authz/schema.py +0 -0
- {soliplex-0.77.2 → soliplex-0.78.1}/src/soliplex/capabilities/filesystem.py +0 -0
- {soliplex-0.77.2 → soliplex-0.78.1}/src/soliplex/capabilities/rag_audit.py +0 -0
- {soliplex-0.77.2 → soliplex-0.78.1}/src/soliplex/cli/admin_users.py +0 -0
- {soliplex-0.77.2 → soliplex-0.78.1}/src/soliplex/cli/ask.py +0 -0
- {soliplex-0.77.2 → soliplex-0.78.1}/src/soliplex/cli/cli_util.py +0 -0
- {soliplex-0.77.2 → soliplex-0.78.1}/src/soliplex/cli/main.py +0 -0
- {soliplex-0.77.2 → soliplex-0.78.1}/src/soliplex/cli/ollama.py +0 -0
- {soliplex-0.77.2 → soliplex-0.78.1}/src/soliplex/cli/room_authz.py +0 -0
- {soliplex-0.77.2 → soliplex-0.78.1}/src/soliplex/cli/serve.py +0 -0
- {soliplex-0.77.2 → soliplex-0.78.1}/src/soliplex/cli/types.py +0 -0
- {soliplex-0.77.2 → soliplex-0.78.1}/src/soliplex/completions.py +0 -0
- {soliplex-0.77.2 → soliplex-0.78.1}/src/soliplex/config/__init__.py +0 -0
- {soliplex-0.77.2 → soliplex-0.78.1}/src/soliplex/config/_utils.py +0 -0
- {soliplex-0.77.2 → soliplex-0.78.1}/src/soliplex/config/agui.py +0 -0
- {soliplex-0.77.2 → soliplex-0.78.1}/src/soliplex/config/authsystem.py +0 -0
- {soliplex-0.77.2 → soliplex-0.78.1}/src/soliplex/config/completions.py +0 -0
- {soliplex-0.77.2 → soliplex-0.78.1}/src/soliplex/config/exceptions.py +0 -0
- {soliplex-0.77.2 → soliplex-0.78.1}/src/soliplex/config/installation.py +0 -0
- {soliplex-0.77.2 → soliplex-0.78.1}/src/soliplex/config/logfire.py +0 -0
- {soliplex-0.77.2 → soliplex-0.78.1}/src/soliplex/config/meta.py +0 -0
- {soliplex-0.77.2 → soliplex-0.78.1}/src/soliplex/config/middleware.py +0 -0
- {soliplex-0.77.2 → soliplex-0.78.1}/src/soliplex/config/quizzes.py +0 -0
- {soliplex-0.77.2 → soliplex-0.78.1}/src/soliplex/config/rag.py +0 -0
- {soliplex-0.77.2 → soliplex-0.78.1}/src/soliplex/config/rooms.py +0 -0
- {soliplex-0.77.2 → soliplex-0.78.1}/src/soliplex/config/routing.py +0 -0
- {soliplex-0.77.2 → soliplex-0.78.1}/src/soliplex/config/secrets.py +0 -0
- {soliplex-0.77.2 → soliplex-0.78.1}/src/soliplex/config/tools.py +0 -0
- {soliplex-0.77.2 → soliplex-0.78.1}/src/soliplex/examples.py +0 -0
- {soliplex-0.77.2 → soliplex-0.78.1}/src/soliplex/log_ingest.py +0 -0
- {soliplex-0.77.2 → soliplex-0.78.1}/src/soliplex/loggers.py +0 -0
- {soliplex-0.77.2 → soliplex-0.78.1}/src/soliplex/main.py +0 -0
- {soliplex-0.77.2 → soliplex-0.78.1}/src/soliplex/mcp_auth.py +0 -0
- {soliplex-0.77.2 → soliplex-0.78.1}/src/soliplex/mcp_client.py +0 -0
- {soliplex-0.77.2 → soliplex-0.78.1}/src/soliplex/mcp_server.py +0 -0
- {soliplex-0.77.2 → soliplex-0.78.1}/src/soliplex/ollama.py +0 -0
- {soliplex-0.77.2 → soliplex-0.78.1}/src/soliplex/rag_audit.py +0 -0
- {soliplex-0.77.2 → soliplex-0.78.1}/src/soliplex/sandbox_audit.py +0 -0
- {soliplex-0.77.2 → soliplex-0.78.1}/src/soliplex/secrets.py +0 -0
- {soliplex-0.77.2 → soliplex-0.78.1}/src/soliplex/titles.py +0 -0
- {soliplex-0.77.2 → soliplex-0.78.1}/src/soliplex/tools/__init__.py +0 -0
- {soliplex-0.77.2 → soliplex-0.78.1}/src/soliplex/tools/agui_run_feedback.py +0 -0
- {soliplex-0.77.2 → soliplex-0.78.1}/src/soliplex/tools/file_uploads.py +0 -0
- {soliplex-0.77.2 → soliplex-0.78.1}/src/soliplex/tools/rag.py +0 -0
- {soliplex-0.77.2 → soliplex-0.78.1}/src/soliplex/tui/cli.py +0 -0
- {soliplex-0.77.2 → soliplex-0.78.1}/src/soliplex/tui/main.py +0 -0
- {soliplex-0.77.2 → soliplex-0.78.1}/src/soliplex/tui/rest_api.py +0 -0
- {soliplex-0.77.2 → soliplex-0.78.1}/src/soliplex/tui/serve.py +0 -0
- {soliplex-0.77.2 → soliplex-0.78.1}/src/soliplex/util.py +0 -0
- {soliplex-0.77.2 → soliplex-0.78.1}/src/soliplex/views/__init__.py +0 -0
- {soliplex-0.77.2 → soliplex-0.78.1}/src/soliplex/views/agui.py +0 -0
- {soliplex-0.77.2 → soliplex-0.78.1}/src/soliplex/views/authn.py +0 -0
- {soliplex-0.77.2 → soliplex-0.78.1}/src/soliplex/views/authz.py +0 -0
- {soliplex-0.77.2 → soliplex-0.78.1}/src/soliplex/views/completions.py +0 -0
- {soliplex-0.77.2 → soliplex-0.78.1}/src/soliplex/views/installation.py +0 -0
- {soliplex-0.77.2 → soliplex-0.78.1}/src/soliplex/views/log_ingest.py +0 -0
- {soliplex-0.77.2 → soliplex-0.78.1}/src/soliplex/views/quizzes.py +0 -0
- {soliplex-0.77.2 → soliplex-0.78.1}/src/soliplex/views/room_file_uploads.py +0 -0
- {soliplex-0.77.2 → soliplex-0.78.1}/src/soliplex/views/rooms.py +0 -0
- {soliplex-0.77.2 → soliplex-0.78.1}/src/soliplex/views/sandbox_workdirs.py +0 -0
- {soliplex-0.77.2 → soliplex-0.78.1}/src/soliplex/views/stats.py +0 -0
- {soliplex-0.77.2 → soliplex-0.78.1}/src/soliplex/views/streaming.py +0 -0
- {soliplex-0.77.2 → soliplex-0.78.1}/src/soliplex/views/thread_file_uploads.py +0 -0
- {soliplex-0.77.2 → soliplex-0.78.1}/src/soliplex.egg-info/SOURCES.txt +0 -0
- {soliplex-0.77.2 → soliplex-0.78.1}/src/soliplex.egg-info/dependency_links.txt +0 -0
- {soliplex-0.77.2 → soliplex-0.78.1}/src/soliplex.egg-info/entry_points.txt +0 -0
- {soliplex-0.77.2 → soliplex-0.78.1}/src/soliplex.egg-info/requires.txt +0 -0
- {soliplex-0.77.2 → soliplex-0.78.1}/src/soliplex.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: soliplex
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.78.1
|
|
4
4
|
Summary: An AI-powered Retrieval-Augmented Generation (RAG) system with a modern web interface.
|
|
5
5
|
Author-email: Enfold <info@enfoldsystems.net>
|
|
6
6
|
Classifier: Programming Language :: Python :: 3
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "soliplex"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.78.1"
|
|
8
8
|
description = "An AI-powered Retrieval-Augmented Generation (RAG) system with a modern web interface."
|
|
9
9
|
authors = [{ name = "Enfold", email = "info@enfoldsystems.net" }]
|
|
10
10
|
readme = "README.md"
|
|
@@ -144,18 +144,11 @@ def get_default_agent_from_configs(
|
|
|
144
144
|
if isinstance(capability, HR_VisionCapabilities):
|
|
145
145
|
capability.vision = agent_config.multimodal
|
|
146
146
|
|
|
147
|
-
# A
|
|
148
|
-
#
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
capability
|
|
153
|
-
for capability in capabilities
|
|
154
|
-
if _is_routing_capability(capability)
|
|
155
|
-
]
|
|
156
|
-
defer_loading = len(routing_capabilities) > 1
|
|
157
|
-
for capability in routing_capabilities:
|
|
158
|
-
capability.defer_loading = defer_loading
|
|
147
|
+
# A hook-only capability is never deferred: nothing would load it, and
|
|
148
|
+
# its hooks only fire once loaded.
|
|
149
|
+
for capability in capabilities:
|
|
150
|
+
if not _is_routing_capability(capability):
|
|
151
|
+
capability.defer_loading = False
|
|
159
152
|
|
|
160
153
|
return pydantic_ai.Agent(
|
|
161
154
|
model=model,
|
|
@@ -568,22 +568,39 @@ async def _list_admin_discriminators(dburi):
|
|
|
568
568
|
return await policy.list_admin_user_discriminators()
|
|
569
569
|
|
|
570
570
|
|
|
571
|
-
def
|
|
572
|
-
"""
|
|
573
|
-
|
|
571
|
+
def _room_policies(the_installation) -> tuple[list, str | None]:
|
|
572
|
+
"""Return ``(policies, error)`` for the stored room policies.
|
|
573
|
+
|
|
574
|
+
``policies`` holds the unchecked policy models read via
|
|
575
|
+
'RoomAuthorizationPolicy.list_room_policies' and ``error`` is ``None``
|
|
576
|
+
on success. A RAM-based authz DB can hold no persisted rows, so it
|
|
577
|
+
short-circuits to ``([], None)`` without touching the database.
|
|
578
|
+
|
|
579
|
+
When the database itself cannot be reached -- e.g. its DBURI names a
|
|
580
|
+
Postgres server that isn't listening -- ``policies`` is empty and
|
|
581
|
+
``error`` carries the exception message, so the audit can report the
|
|
582
|
+
unreachable DB instead of dying on the traceback.
|
|
583
|
+
"""
|
|
574
584
|
dburi = the_installation.authorization_dburi_async
|
|
575
585
|
|
|
576
586
|
if dburi in cli_util._RAM_DBURIS:
|
|
577
|
-
return
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
}
|
|
587
|
+
return [], None
|
|
588
|
+
|
|
589
|
+
try:
|
|
590
|
+
return list(asyncio.run(_list_room_policies(dburi))), None
|
|
591
|
+
except Exception as exc:
|
|
592
|
+
return [], f"{type(exc).__name__}: {exc}"
|
|
593
|
+
|
|
594
|
+
|
|
595
|
+
def _room_authz_groups(the_installation, room_policies):
|
|
596
|
+
"""Bucket configured rooms by authorization state; collect stale rows.
|
|
597
|
+
|
|
598
|
+
``room_policies`` is the policy list from '_room_policies'.
|
|
599
|
+
"""
|
|
600
|
+
configured = sorted(the_installation._config.room_configs)
|
|
583
601
|
|
|
584
602
|
policies = {
|
|
585
|
-
policy.room_id: policy.default_allow_deny
|
|
586
|
-
for policy in asyncio.run(_list_room_policies(dburi))
|
|
603
|
+
policy.room_id: policy.default_allow_deny for policy in room_policies
|
|
587
604
|
}
|
|
588
605
|
|
|
589
606
|
configured_set = set(configured)
|
|
@@ -606,26 +623,21 @@ def _room_authz_groups(the_installation):
|
|
|
606
623
|
}
|
|
607
624
|
|
|
608
625
|
|
|
609
|
-
def _invalid_acl_json_paths(
|
|
626
|
+
def _invalid_acl_json_paths(room_policies) -> dict:
|
|
610
627
|
"""Collect ACL entries whose stored 'json_path' fails to validate.
|
|
611
628
|
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
629
|
+
``room_policies`` is the policy list from '_room_policies' -- the
|
|
630
|
+
unchecked models, which tolerate entries that would fail
|
|
631
|
+
'policy.as_model'. Each surfaced 'json_path' is re-validated against
|
|
632
|
+
the currently-loaded JSONPath environment. Typically an entry lands
|
|
633
|
+
here when it was authored under a meta-config that registered filter
|
|
634
|
+
functions which are no longer present.
|
|
618
635
|
|
|
619
636
|
Returns a dict mapping 'room_id' to a list of '(json_path, error)'
|
|
620
|
-
pairs.
|
|
637
|
+
pairs.
|
|
621
638
|
"""
|
|
622
|
-
dburi = the_installation.authorization_dburi_async
|
|
623
|
-
|
|
624
|
-
if dburi in cli_util._RAM_DBURIS:
|
|
625
|
-
return {}
|
|
626
|
-
|
|
627
639
|
invalid: dict = {}
|
|
628
|
-
for policy in
|
|
640
|
+
for policy in room_policies:
|
|
629
641
|
for entry in policy.acl_entries:
|
|
630
642
|
if entry.json_path is None:
|
|
631
643
|
continue
|
|
@@ -651,7 +663,13 @@ def _audit_room_authz_section(
|
|
|
651
663
|
tc_rule("Configured rooms by authorization state")
|
|
652
664
|
tc_line()
|
|
653
665
|
|
|
654
|
-
|
|
666
|
+
room_policies, db_error = _room_policies(the_installation)
|
|
667
|
+
if db_error is not None:
|
|
668
|
+
tc_print(f"ERROR: authorization database unreachable: {db_error}")
|
|
669
|
+
tc_line()
|
|
670
|
+
return {"room_authz": {"unreachable": db_error}}
|
|
671
|
+
|
|
672
|
+
groups = _room_authz_groups(the_installation, room_policies)
|
|
655
673
|
|
|
656
674
|
for label, room_ids in (
|
|
657
675
|
(
|
|
@@ -677,7 +695,7 @@ def _audit_room_authz_section(
|
|
|
677
695
|
tc_print(" (none)")
|
|
678
696
|
tc_line()
|
|
679
697
|
|
|
680
|
-
invalid_acls = _invalid_acl_json_paths(
|
|
698
|
+
invalid_acls = _invalid_acl_json_paths(room_policies)
|
|
681
699
|
tc_print("ACL entries with invalid JSONPath:")
|
|
682
700
|
if invalid_acls:
|
|
683
701
|
for room_id in sorted(invalid_acls):
|
|
@@ -699,36 +717,47 @@ def _audit_room_authz_section(
|
|
|
699
717
|
return errors
|
|
700
718
|
|
|
701
719
|
|
|
702
|
-
def _admin_user_json_paths(
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
720
|
+
def _admin_user_json_paths(
|
|
721
|
+
the_installation,
|
|
722
|
+
) -> tuple[list[str], str | None]:
|
|
723
|
+
"""Return ``(json_paths, error)`` for the stored admin rows.
|
|
724
|
+
|
|
725
|
+
``json_paths`` holds every stored 'AdminUser.json_path', in insertion
|
|
726
|
+
order, read via 'AdminUserPolicy.list_admin_user_discriminators';
|
|
727
|
+
``error`` is ``None`` on success. A RAM-based authz DB can hold no
|
|
728
|
+
persisted rows, so it short-circuits to ``([], None)`` without
|
|
729
|
+
touching the database.
|
|
730
|
+
|
|
731
|
+
When the database itself cannot be reached -- e.g. its DBURI names a
|
|
732
|
+
Postgres server that isn't listening -- ``json_paths`` is empty and
|
|
733
|
+
``error`` carries the exception message, so the audit can report the
|
|
734
|
+
unreachable DB instead of dying on the traceback.
|
|
709
735
|
"""
|
|
710
736
|
dburi = the_installation.authorization_dburi_async
|
|
711
737
|
|
|
712
738
|
if dburi in cli_util._RAM_DBURIS:
|
|
713
|
-
return []
|
|
739
|
+
return [], None
|
|
714
740
|
|
|
715
|
-
|
|
741
|
+
try:
|
|
742
|
+
return list(asyncio.run(_list_admin_discriminators(dburi))), None
|
|
743
|
+
except Exception as exc:
|
|
744
|
+
return [], f"{type(exc).__name__}: {exc}"
|
|
716
745
|
|
|
717
746
|
|
|
718
747
|
def _invalid_admin_user_json_paths(
|
|
719
|
-
|
|
748
|
+
json_paths,
|
|
720
749
|
) -> list[tuple[str, str]]:
|
|
721
750
|
"""Collect admin entries whose 'json_path' fails to validate.
|
|
722
751
|
|
|
723
752
|
Same intent as '_invalid_acl_json_paths' but for the 'AdminUser'
|
|
724
753
|
table: a row typically lands here when it was authored under a
|
|
725
754
|
meta-config that registered filter functions which are no longer
|
|
726
|
-
present.
|
|
755
|
+
present. ``json_paths`` is the list from '_admin_user_json_paths'.
|
|
727
756
|
|
|
728
757
|
Returns a list of '(json_path, error)' pairs.
|
|
729
758
|
"""
|
|
730
759
|
invalid: list[tuple[str, str]] = []
|
|
731
|
-
for json_path in
|
|
760
|
+
for json_path in json_paths:
|
|
732
761
|
try:
|
|
733
762
|
authz.validate_json_path(json_path)
|
|
734
763
|
except authz.InvalidJSONPath as exc:
|
|
@@ -749,7 +778,12 @@ def _audit_admin_users_section(
|
|
|
749
778
|
tc_rule("Configured admin users")
|
|
750
779
|
tc_line()
|
|
751
780
|
|
|
752
|
-
json_paths = _admin_user_json_paths(the_installation)
|
|
781
|
+
json_paths, db_error = _admin_user_json_paths(the_installation)
|
|
782
|
+
if db_error is not None:
|
|
783
|
+
tc_print(f"ERROR: authorization database unreachable: {db_error}")
|
|
784
|
+
tc_line()
|
|
785
|
+
return {"admin_users": {"unreachable": db_error}}
|
|
786
|
+
|
|
753
787
|
tc_print(f"Admin users ({len(json_paths)}):")
|
|
754
788
|
if json_paths:
|
|
755
789
|
for json_path in json_paths:
|
|
@@ -763,7 +797,7 @@ def _audit_admin_users_section(
|
|
|
763
797
|
tc_print(" (none)")
|
|
764
798
|
tc_line()
|
|
765
799
|
|
|
766
|
-
invalid = _invalid_admin_user_json_paths(
|
|
800
|
+
invalid = _invalid_admin_user_json_paths(json_paths)
|
|
767
801
|
tc_print("Admin users with invalid JSONPath:")
|
|
768
802
|
if invalid:
|
|
769
803
|
for json_path, error in invalid:
|
|
@@ -271,6 +271,16 @@ class AgentConfig:
|
|
|
271
271
|
else: # pragma: NO COVER
|
|
272
272
|
pass
|
|
273
273
|
|
|
274
|
+
@property
|
|
275
|
+
def llm_model_name(self) -> str | None:
|
|
276
|
+
model_name = self.model_name
|
|
277
|
+
i_config = self._installation_config
|
|
278
|
+
|
|
279
|
+
if i_config is not None:
|
|
280
|
+
model_name = i_config.interpolate_environment(model_name)
|
|
281
|
+
|
|
282
|
+
return model_name
|
|
283
|
+
|
|
274
284
|
@property
|
|
275
285
|
def llm_provider_base_url(self) -> str | None:
|
|
276
286
|
ic = self._installation_config
|
|
@@ -324,7 +334,7 @@ class AgentConfig:
|
|
|
324
334
|
return {
|
|
325
335
|
"id": self.id,
|
|
326
336
|
"kind": self.kind,
|
|
327
|
-
"model_name": self.model_name,
|
|
337
|
+
"model_name": self.model_name, # not interpolated
|
|
328
338
|
"retries": self.retries,
|
|
329
339
|
"system_prompt": prompt,
|
|
330
340
|
"model_settings": self.model_settings,
|
|
@@ -478,6 +488,7 @@ def get_model_from_config(
|
|
|
478
488
|
provider_kw = agent_config.llm_provider_kw
|
|
479
489
|
|
|
480
490
|
model_settings_kw = {}
|
|
491
|
+
model_name = agent_config.llm_model_name
|
|
481
492
|
|
|
482
493
|
if agent_config.model_settings:
|
|
483
494
|
model_settings_kw["settings"] = ai_settings.ModelSettings(
|
|
@@ -487,7 +498,7 @@ def get_model_from_config(
|
|
|
487
498
|
if agent_config.provider_type == LLMProviderType.GOOGLE:
|
|
488
499
|
provider = google_providers.GoogleProvider(**provider_kw)
|
|
489
500
|
return google_models.GoogleModel(
|
|
490
|
-
model_name=
|
|
501
|
+
model_name=model_name,
|
|
491
502
|
provider=provider,
|
|
492
503
|
**model_settings_kw,
|
|
493
504
|
)
|
|
@@ -496,7 +507,7 @@ def get_model_from_config(
|
|
|
496
507
|
provider_kw["api_key"] = "dummy"
|
|
497
508
|
provider = ollama_providers.OllamaProvider(**provider_kw)
|
|
498
509
|
return openai_models.OpenAIChatModel(
|
|
499
|
-
model_name=
|
|
510
|
+
model_name=model_name,
|
|
500
511
|
provider=provider,
|
|
501
512
|
profile=_OPENAI_COMPAT_PROFILE,
|
|
502
513
|
**model_settings_kw,
|
|
@@ -510,7 +521,7 @@ def get_model_from_config(
|
|
|
510
521
|
)
|
|
511
522
|
provider = openai_providers.OpenAIProvider(**provider_kw)
|
|
512
523
|
return openai_models.OpenAIChatModel(
|
|
513
|
-
model_name=
|
|
524
|
+
model_name=model_name,
|
|
514
525
|
provider=provider,
|
|
515
526
|
**profile_kw,
|
|
516
527
|
**model_settings_kw,
|
|
@@ -179,6 +179,22 @@ class FilesystemSkillConfig:
|
|
|
179
179
|
def extra_parameters(self) -> dict[str, pathlib.Path]:
|
|
180
180
|
return {"path": self.path}
|
|
181
181
|
|
|
182
|
+
def with_defer_loading(self, defer_loading: bool) -> FilesystemSkillConfig:
|
|
183
|
+
"""A copy whose capability loads as a room asked.
|
|
184
|
+
|
|
185
|
+
Installation skill configs are shared between the rooms which name
|
|
186
|
+
them, so a room's choice cannot be written onto the capability.
|
|
187
|
+
"""
|
|
188
|
+
if defer_loading == self._capability.defer_loading:
|
|
189
|
+
return self
|
|
190
|
+
return dataclasses.replace(
|
|
191
|
+
self,
|
|
192
|
+
_capability=dataclasses.replace(
|
|
193
|
+
self._capability,
|
|
194
|
+
defer_loading=defer_loading,
|
|
195
|
+
),
|
|
196
|
+
)
|
|
197
|
+
|
|
182
198
|
|
|
183
199
|
@dataclasses.dataclass(kw_only=True)
|
|
184
200
|
class _HaikuRAGCapabilityConfig(
|
|
@@ -192,6 +208,8 @@ class _HaikuRAGCapabilityConfig(
|
|
|
192
208
|
state_type: typing.ClassVar[type[pydantic.BaseModel]]
|
|
193
209
|
source: typing.ClassVar[SkillKind] = SkillKind.NATIVE
|
|
194
210
|
|
|
211
|
+
defer_loading: bool = False
|
|
212
|
+
|
|
195
213
|
@classmethod
|
|
196
214
|
def from_yaml(
|
|
197
215
|
cls,
|
|
@@ -230,7 +248,7 @@ class _HaikuRAGCapabilityConfig(
|
|
|
230
248
|
return type(self).capability_factory(
|
|
231
249
|
db_path=self.rag_lancedb_path,
|
|
232
250
|
config=self.haiku_rag_config,
|
|
233
|
-
defer_loading=
|
|
251
|
+
defer_loading=self.defer_loading,
|
|
234
252
|
)
|
|
235
253
|
|
|
236
254
|
@property
|
|
@@ -239,7 +257,10 @@ class _HaikuRAGCapabilityConfig(
|
|
|
239
257
|
|
|
240
258
|
@property
|
|
241
259
|
def as_yaml(self) -> dict:
|
|
242
|
-
result = {
|
|
260
|
+
result = {
|
|
261
|
+
"kind": self.kind,
|
|
262
|
+
"defer_loading": self.defer_loading,
|
|
263
|
+
}
|
|
243
264
|
if self.rag_lancedb_stem is not None:
|
|
244
265
|
result["rag_lancedb_stem"] = self.rag_lancedb_stem
|
|
245
266
|
else:
|
|
@@ -383,6 +404,7 @@ class BwrapSandboxSkillConfig:
|
|
|
383
404
|
allowed_environments: bwrap_sandbox.AllowedEnvironments = None
|
|
384
405
|
sandbox_config: bs_config.Config = None
|
|
385
406
|
volumes: bs_models.VolumeMap = _default_dict_field()
|
|
407
|
+
defer_loading: bool = False
|
|
386
408
|
|
|
387
409
|
@classmethod
|
|
388
410
|
def from_yaml(
|
|
@@ -420,6 +442,7 @@ class BwrapSandboxSkillConfig:
|
|
|
420
442
|
sandbox_config=self.sandbox_config,
|
|
421
443
|
volumes=self.volumes,
|
|
422
444
|
installation_config=self._installation_config,
|
|
445
|
+
defer_loading=self.defer_loading,
|
|
423
446
|
)
|
|
424
447
|
|
|
425
448
|
@property
|
|
@@ -427,6 +450,7 @@ class BwrapSandboxSkillConfig:
|
|
|
427
450
|
result = {
|
|
428
451
|
"kind": self.kind,
|
|
429
452
|
"default_environment": self.default_environment,
|
|
453
|
+
"defer_loading": self.defer_loading,
|
|
430
454
|
}
|
|
431
455
|
if self.id is not None:
|
|
432
456
|
result["id"] = self.id
|
|
@@ -475,6 +499,7 @@ class EntrypointCapabilityConfig:
|
|
|
475
499
|
|
|
476
500
|
name: str
|
|
477
501
|
params: dict[str, typing.Any] = _default_dict_field()
|
|
502
|
+
defer_loading: bool = True
|
|
478
503
|
|
|
479
504
|
def __post_init__(self) -> None:
|
|
480
505
|
# Register the capability's typed AG-UI state feature so the room can
|
|
@@ -500,10 +525,13 @@ class EntrypointCapabilityConfig:
|
|
|
500
525
|
params = dict(config_dict)
|
|
501
526
|
params.pop("kind", None)
|
|
502
527
|
name = params.pop("name", None)
|
|
528
|
+
# Soliplex's own flag, not one of the plugin's parameters.
|
|
529
|
+
defer_loading = params.pop("defer_loading", True)
|
|
503
530
|
try:
|
|
504
531
|
return cls(
|
|
505
532
|
name=name,
|
|
506
533
|
params=params,
|
|
534
|
+
defer_loading=defer_loading,
|
|
507
535
|
_installation_config=installation_config,
|
|
508
536
|
_config_path=config_path,
|
|
509
537
|
)
|
|
@@ -521,7 +549,7 @@ class EntrypointCapabilityConfig:
|
|
|
521
549
|
@property
|
|
522
550
|
def capability(self) -> ai_capabilities.AbstractCapability:
|
|
523
551
|
return self._module.create_capability(
|
|
524
|
-
defer_loading=
|
|
552
|
+
defer_loading=self.defer_loading, **self.params
|
|
525
553
|
)
|
|
526
554
|
|
|
527
555
|
@property
|
|
@@ -543,7 +571,12 @@ class EntrypointCapabilityConfig:
|
|
|
543
571
|
|
|
544
572
|
@property
|
|
545
573
|
def as_yaml(self) -> dict:
|
|
546
|
-
return {
|
|
574
|
+
return {
|
|
575
|
+
"kind": self.kind,
|
|
576
|
+
"name": self.name,
|
|
577
|
+
"defer_loading": self.defer_loading,
|
|
578
|
+
**self.params,
|
|
579
|
+
}
|
|
547
580
|
|
|
548
581
|
@property
|
|
549
582
|
def extra_parameters(self) -> dict[str, typing.Any]:
|
|
@@ -611,24 +644,57 @@ def extract_skill_configs(
|
|
|
611
644
|
return skill_configs
|
|
612
645
|
|
|
613
646
|
|
|
647
|
+
@dataclasses.dataclass(kw_only=True)
|
|
648
|
+
class InstallationSkillRef:
|
|
649
|
+
"""A room's reference to one of the installation's skills.
|
|
650
|
+
|
|
651
|
+
Spelled as a bare name, or as a mapping when the room sets a flag.
|
|
652
|
+
"""
|
|
653
|
+
|
|
654
|
+
name: str
|
|
655
|
+
defer_loading: bool = True
|
|
656
|
+
|
|
657
|
+
@classmethod
|
|
658
|
+
def from_yaml(cls, entry: str | dict | InstallationSkillRef):
|
|
659
|
+
if isinstance(entry, cls):
|
|
660
|
+
return entry
|
|
661
|
+
if isinstance(entry, str):
|
|
662
|
+
return cls(name=entry)
|
|
663
|
+
return cls(**entry)
|
|
664
|
+
|
|
665
|
+
@property
|
|
666
|
+
def as_yaml(self) -> str | dict:
|
|
667
|
+
return {"name": self.name, "defer_loading": self.defer_loading}
|
|
668
|
+
|
|
669
|
+
|
|
614
670
|
@dataclasses.dataclass(kw_only=True)
|
|
615
671
|
class RoomSkillsConfig:
|
|
616
672
|
"""Select native capabilities for a room."""
|
|
617
673
|
|
|
618
|
-
installation_skill_names: list[
|
|
674
|
+
installation_skill_names: list[InstallationSkillRef] = (
|
|
675
|
+
_default_list_field()
|
|
676
|
+
)
|
|
619
677
|
_skill_configs: SkillConfigMap = _default_dict_field()
|
|
620
678
|
_installation_config: config_installation.InstallationConfig = (
|
|
621
679
|
_no_repr_no_compare_none()
|
|
622
680
|
)
|
|
623
681
|
_config_path: pathlib.Path = None
|
|
624
682
|
|
|
683
|
+
def __post_init__(self) -> None:
|
|
684
|
+
# Callers which build the config directly, rather than via
|
|
685
|
+
# 'from_yaml', may spell the entries as bare names.
|
|
686
|
+
self.installation_skill_names = [
|
|
687
|
+
InstallationSkillRef.from_yaml(entry)
|
|
688
|
+
for entry in self.installation_skill_names
|
|
689
|
+
]
|
|
690
|
+
|
|
625
691
|
@staticmethod
|
|
626
692
|
def _check_installation_skills(
|
|
627
693
|
installation_config: config_installation.InstallationConfig,
|
|
628
694
|
config_path: pathlib.Path,
|
|
629
|
-
|
|
695
|
+
refs: typing.Sequence[InstallationSkillRef],
|
|
630
696
|
) -> None:
|
|
631
|
-
requested =
|
|
697
|
+
requested = {ref.name for ref in refs}
|
|
632
698
|
available = set(installation_config.skill_configs)
|
|
633
699
|
if missing := requested - available:
|
|
634
700
|
raise MissingSkillNames(
|
|
@@ -645,11 +711,16 @@ class RoomSkillsConfig:
|
|
|
645
711
|
config_dict: dict,
|
|
646
712
|
):
|
|
647
713
|
try:
|
|
714
|
+
refs = [
|
|
715
|
+
InstallationSkillRef.from_yaml(entry)
|
|
716
|
+
for entry in config_dict.get("installation_skill_names", ())
|
|
717
|
+
]
|
|
648
718
|
cls._check_installation_skills(
|
|
649
719
|
installation_config,
|
|
650
720
|
config_path,
|
|
651
|
-
|
|
721
|
+
refs,
|
|
652
722
|
)
|
|
723
|
+
config_dict["installation_skill_names"] = refs
|
|
653
724
|
config_dict["_skill_configs"] = extract_skill_configs(
|
|
654
725
|
installation_config,
|
|
655
726
|
config_path,
|
|
@@ -671,7 +742,9 @@ class RoomSkillsConfig:
|
|
|
671
742
|
def as_yaml(self) -> dict:
|
|
672
743
|
result = {}
|
|
673
744
|
if self.installation_skill_names:
|
|
674
|
-
result["installation_skill_names"] =
|
|
745
|
+
result["installation_skill_names"] = [
|
|
746
|
+
ref.as_yaml for ref in self.installation_skill_names
|
|
747
|
+
]
|
|
675
748
|
if self._skill_configs:
|
|
676
749
|
result["skill_configs"] = [
|
|
677
750
|
config.as_yaml for config in self._skill_configs.values()
|
|
@@ -682,8 +755,10 @@ class RoomSkillsConfig:
|
|
|
682
755
|
def skill_configs(self) -> SkillConfigMap:
|
|
683
756
|
installation_configs = self._installation_config.skill_configs
|
|
684
757
|
return {
|
|
685
|
-
name: installation_configs[name]
|
|
686
|
-
|
|
758
|
+
ref.name: installation_configs[ref.name].with_defer_loading(
|
|
759
|
+
ref.defer_loading
|
|
760
|
+
)
|
|
761
|
+
for ref in self.installation_skill_names
|
|
687
762
|
} | self._skill_configs
|
|
688
763
|
|
|
689
764
|
@property
|
|
@@ -178,7 +178,7 @@ class Installation:
|
|
|
178
178
|
type_info = found.setdefault(provider_type, {})
|
|
179
179
|
base_url = agent_config.llm_provider_base_url
|
|
180
180
|
url_models = type_info.setdefault(base_url, set())
|
|
181
|
-
url_models.add(agent_config.
|
|
181
|
+
url_models.add(agent_config.llm_model_name)
|
|
182
182
|
|
|
183
183
|
return found
|
|
184
184
|
|
|
@@ -174,7 +174,7 @@ ConfiguredSkills = dict[str, Skill]
|
|
|
174
174
|
|
|
175
175
|
class DefaultAgent(pydantic.BaseModel):
|
|
176
176
|
id: str
|
|
177
|
-
model_name: str
|
|
177
|
+
model_name: str | None
|
|
178
178
|
retries: int
|
|
179
179
|
system_prompt: str | None
|
|
180
180
|
provider_type: config_agents.LLMProviderType # enum, not dataclass
|
|
@@ -187,7 +187,7 @@ class DefaultAgent(pydantic.BaseModel):
|
|
|
187
187
|
llm_provider_kw = agent_config.llm_provider_kw
|
|
188
188
|
return cls(
|
|
189
189
|
id=agent_config.id,
|
|
190
|
-
model_name=agent_config.
|
|
190
|
+
model_name=agent_config.llm_model_name,
|
|
191
191
|
retries=agent_config.retries,
|
|
192
192
|
system_prompt=agent_config.get_system_prompt(),
|
|
193
193
|
provider_type=agent_config.provider_type,
|
|
@@ -52,7 +52,7 @@ def get_quiz_judge_agent(quiz: config_quizzes.QuizConfig):
|
|
|
52
52
|
model_provider = ollama_providers.OllamaProvider(**llm_provider_kw)
|
|
53
53
|
|
|
54
54
|
ollama_model = openai_models.OpenAIChatModel(
|
|
55
|
-
model_name=quiz.judge_agent.
|
|
55
|
+
model_name=quiz.judge_agent.llm_model_name,
|
|
56
56
|
provider=model_provider,
|
|
57
57
|
)
|
|
58
58
|
|
|
@@ -55,11 +55,30 @@ Do NOT use the sandbox if **any** of these is true:
|
|
|
55
55
|
- `run_python(script, environment_name=None, timeout=None)` — run a Python
|
|
56
56
|
script. `script` is the full source as one string.
|
|
57
57
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
58
|
+
## Environment names come only from `list_environments`
|
|
59
|
+
|
|
60
|
+
**Never guess an environment name.** The only values `environment_name`
|
|
61
|
+
accepts are the exact `name` strings that a `list_environments` call
|
|
62
|
+
returned **to you, in this conversation**.
|
|
63
|
+
|
|
64
|
+
- Call `list_environments` before the first `run` or `run_python` of a
|
|
65
|
+
turn. It is cheap; skipping it is the most common way this skill fails.
|
|
66
|
+
- Every environment name written in this document is a placeholder —
|
|
67
|
+
`<name from list_environments>` — standing in for a name you have yet to
|
|
68
|
+
look up. No placeholder names a real environment.
|
|
69
|
+
- A name that appears in a task, a filename, a library you plan to import,
|
|
70
|
+
or an earlier conversation is not an environment name. Neither is the
|
|
71
|
+
name of a package: an environment's `name` and its `dependencies` are
|
|
72
|
+
different things, and are usually spelled differently.
|
|
73
|
+
- Passing a name that was not in the `list_environments` result fails the
|
|
74
|
+
call. The failure names the environments you may actually use — call
|
|
75
|
+
`list_environments` and retry with one of those, rather than guessing
|
|
76
|
+
again.
|
|
77
|
+
|
|
78
|
+
`environment_name` and `timeout` are both optional and fall back to the
|
|
79
|
+
values this skill was configured with. Always pass `environment_name`
|
|
80
|
+
explicitly: the configured default is not visible to you and may not be
|
|
81
|
+
the environment you want.
|
|
63
82
|
|
|
64
83
|
## Workflow
|
|
65
84
|
|
|
@@ -68,9 +87,9 @@ and may not be the environment you want.
|
|
|
68
87
|
|
|
69
88
|
```python
|
|
70
89
|
{
|
|
71
|
-
"name": "
|
|
72
|
-
"description": "
|
|
73
|
-
"dependencies": ["
|
|
90
|
+
"name": "<name from list_environments>",
|
|
91
|
+
"description": "<what this environment is for>",
|
|
92
|
+
"dependencies": ["<installed package>"],
|
|
74
93
|
}
|
|
75
94
|
```
|
|
76
95
|
|
|
@@ -78,10 +97,11 @@ and may not be the environment you want.
|
|
|
78
97
|
- If the list is empty, stop and tell the user the skill
|
|
79
98
|
is not configured — do not proceed.
|
|
80
99
|
- If the list contains exactly one environment, use its `name`.
|
|
81
|
-
- Otherwise,
|
|
82
|
-
|
|
83
|
-
`
|
|
84
|
-
|
|
100
|
+
- Otherwise, compare the task against each entry's `dependencies` —
|
|
101
|
+
a `pandas` dependency suits tabular data, `numpy` numeric work,
|
|
102
|
+
`pillow` images — and use the `name` of the first entry that matches.
|
|
103
|
+
Use the entry's `name`, not the name of the dependency that matched.
|
|
104
|
+
If no entry matches, use the `name` of the first entry in the list.
|
|
85
105
|
|
|
86
106
|
2. **List files in both volumes.** This step is mandatory — do not skip it,
|
|
87
107
|
even if the task seems to involve one volume. Run both:
|
|
@@ -113,7 +133,7 @@ and may not be the environment you want.
|
|
|
113
133
|
|
|
114
134
|
```python
|
|
115
135
|
run(command=["head", "-n", "5", "/sandbox/volumes/thread/orders.csv"],
|
|
116
|
-
environment_name="<
|
|
136
|
+
environment_name="<name from list_environments>")
|
|
117
137
|
```
|
|
118
138
|
|
|
119
139
|
(or `["wc", "-l", <path>]`, `["file", <path>]`, etc.). For anything
|
|
@@ -125,7 +145,8 @@ and may not be the environment you want.
|
|
|
125
145
|
argument to `run_python`:
|
|
126
146
|
|
|
127
147
|
```python
|
|
128
|
-
run_python(script="<python source>",
|
|
148
|
+
run_python(script="<python source>",
|
|
149
|
+
environment_name="<name from list_environments>")
|
|
129
150
|
```
|
|
130
151
|
|
|
131
152
|
Write the whole program as a single string, and use real newlines between
|
|
@@ -147,7 +168,10 @@ and may not be the environment you want.
|
|
|
147
168
|
```
|
|
148
169
|
|
|
149
170
|
5. **On failure.**
|
|
150
|
-
-
|
|
171
|
+
- If the failure says the environment is not available, you guessed a
|
|
172
|
+
name. Call `list_environments` and retry with a name it returned —
|
|
173
|
+
do not try a different guess.
|
|
174
|
+
- Otherwise change exactly one thing per retry.
|
|
151
175
|
- After 3 failed runs, stop. Report the error to the user (paste the
|
|
152
176
|
`Exited with code: <N>` line and any traceback), instead of retrying
|
|
153
177
|
further.
|
|
@@ -168,8 +192,10 @@ and may not be the environment you want.
|
|
|
168
192
|
Task: user uploads `orders.csv` and asks "what's the total order value?".
|
|
169
193
|
A full run looks like:
|
|
170
194
|
|
|
171
|
-
1. `list_environments()` —
|
|
172
|
-
|
|
195
|
+
1. `list_environments()` — returns exactly one environment, whose
|
|
196
|
+
`dependencies` include `pandas`. Use that entry's `name`; in step 3 it
|
|
197
|
+
is written as `<name from list_environments>`, because what the name
|
|
198
|
+
actually is can only be read off this call's result.
|
|
173
199
|
|
|
174
200
|
2. `list_volume_files("thread")` — returns
|
|
175
201
|
`["/sandbox/volumes/thread/orders.csv"]`.
|
|
@@ -184,7 +210,7 @@ A full run looks like:
|
|
|
184
210
|
df = pd.read_csv('/sandbox/volumes/thread/orders.csv')
|
|
185
211
|
print(f"Total: {df['amount'].sum():.2f}")
|
|
186
212
|
""",
|
|
187
|
-
environment_name="
|
|
213
|
+
environment_name="<name from list_environments>",
|
|
188
214
|
)
|
|
189
215
|
```
|
|
190
216
|
|