soliplex 0.76__tar.gz → 0.76.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.76/src/soliplex.egg-info → soliplex-0.76.1}/PKG-INFO +1 -1
- {soliplex-0.76 → soliplex-0.76.1}/pyproject.toml +1 -1
- {soliplex-0.76 → soliplex-0.76.1}/src/soliplex/agents.py +24 -14
- {soliplex-0.76 → soliplex-0.76.1}/src/soliplex/config/authsystem.py +23 -6
- {soliplex-0.76 → soliplex-0.76.1}/src/soliplex/config/completions.py +21 -0
- {soliplex-0.76 → soliplex-0.76.1}/src/soliplex/config/quizzes.py +2 -5
- {soliplex-0.76 → soliplex-0.76.1}/src/soliplex/config/skills.py +0 -80
- {soliplex-0.76 → soliplex-0.76.1}/src/soliplex/installation.py +3 -4
- {soliplex-0.76 → soliplex-0.76.1}/src/soliplex/models.py +0 -8
- {soliplex-0.76 → soliplex-0.76.1}/src/soliplex/tools/agui_run_feedback.py +64 -43
- {soliplex-0.76 → soliplex-0.76.1}/src/soliplex/views/agui.py +1 -9
- {soliplex-0.76 → soliplex-0.76.1/src/soliplex.egg-info}/PKG-INFO +1 -1
- {soliplex-0.76 → soliplex-0.76.1}/LICENSE +0 -0
- {soliplex-0.76 → soliplex-0.76.1}/MANIFEST.in +0 -0
- {soliplex-0.76 → soliplex-0.76.1}/README.md +0 -0
- {soliplex-0.76 → soliplex-0.76.1}/example/skills/bare-bones/SKILL.md +0 -0
- {soliplex-0.76 → soliplex-0.76.1}/example/skills/stanzas/SKILL.md +0 -0
- {soliplex-0.76 → soliplex-0.76.1}/setup.cfg +0 -0
- {soliplex-0.76 → soliplex-0.76.1}/skills/soliplex-docs/SKILL.md +0 -0
- {soliplex-0.76 → soliplex-0.76.1}/src/soliplex/agui/__init__.py +0 -0
- {soliplex-0.76 → soliplex-0.76.1}/src/soliplex/agui/parser.py +0 -0
- {soliplex-0.76 → soliplex-0.76.1}/src/soliplex/agui/persistence.py +0 -0
- {soliplex-0.76 → soliplex-0.76.1}/src/soliplex/agui/schema.py +0 -0
- {soliplex-0.76 → soliplex-0.76.1}/src/soliplex/agui/util.py +0 -0
- {soliplex-0.76 → soliplex-0.76.1}/src/soliplex/authn.py +0 -0
- {soliplex-0.76 → soliplex-0.76.1}/src/soliplex/authz/__init__.py +0 -0
- {soliplex-0.76 → soliplex-0.76.1}/src/soliplex/authz/persistence.py +0 -0
- {soliplex-0.76 → soliplex-0.76.1}/src/soliplex/authz/schema.py +0 -0
- {soliplex-0.76 → soliplex-0.76.1}/src/soliplex/capabilities/filesystem.py +0 -0
- {soliplex-0.76 → soliplex-0.76.1}/src/soliplex/capabilities/rag_audit.py +0 -0
- {soliplex-0.76 → soliplex-0.76.1}/src/soliplex/cli/admin_users.py +0 -0
- {soliplex-0.76 → soliplex-0.76.1}/src/soliplex/cli/ask.py +0 -0
- {soliplex-0.76 → soliplex-0.76.1}/src/soliplex/cli/audit.py +0 -0
- {soliplex-0.76 → soliplex-0.76.1}/src/soliplex/cli/cli_util.py +0 -0
- {soliplex-0.76 → soliplex-0.76.1}/src/soliplex/cli/main.py +0 -0
- {soliplex-0.76 → soliplex-0.76.1}/src/soliplex/cli/ollama.py +0 -0
- {soliplex-0.76 → soliplex-0.76.1}/src/soliplex/cli/room_authz.py +0 -0
- {soliplex-0.76 → soliplex-0.76.1}/src/soliplex/cli/serve.py +0 -0
- {soliplex-0.76 → soliplex-0.76.1}/src/soliplex/cli/types.py +0 -0
- {soliplex-0.76 → soliplex-0.76.1}/src/soliplex/completions.py +0 -0
- {soliplex-0.76 → soliplex-0.76.1}/src/soliplex/config/__init__.py +0 -0
- {soliplex-0.76 → soliplex-0.76.1}/src/soliplex/config/_utils.py +0 -0
- {soliplex-0.76 → soliplex-0.76.1}/src/soliplex/config/agents.py +0 -0
- {soliplex-0.76 → soliplex-0.76.1}/src/soliplex/config/agui.py +0 -0
- {soliplex-0.76 → soliplex-0.76.1}/src/soliplex/config/exceptions.py +0 -0
- {soliplex-0.76 → soliplex-0.76.1}/src/soliplex/config/installation.py +0 -0
- {soliplex-0.76 → soliplex-0.76.1}/src/soliplex/config/logfire.py +0 -0
- {soliplex-0.76 → soliplex-0.76.1}/src/soliplex/config/meta.py +0 -0
- {soliplex-0.76 → soliplex-0.76.1}/src/soliplex/config/middleware.py +0 -0
- {soliplex-0.76 → soliplex-0.76.1}/src/soliplex/config/rag.py +0 -0
- {soliplex-0.76 → soliplex-0.76.1}/src/soliplex/config/rooms.py +0 -0
- {soliplex-0.76 → soliplex-0.76.1}/src/soliplex/config/routing.py +0 -0
- {soliplex-0.76 → soliplex-0.76.1}/src/soliplex/config/secrets.py +0 -0
- {soliplex-0.76 → soliplex-0.76.1}/src/soliplex/config/tools.py +0 -0
- {soliplex-0.76 → soliplex-0.76.1}/src/soliplex/examples.py +0 -0
- {soliplex-0.76 → soliplex-0.76.1}/src/soliplex/log_ingest.py +0 -0
- {soliplex-0.76 → soliplex-0.76.1}/src/soliplex/loggers.py +0 -0
- {soliplex-0.76 → soliplex-0.76.1}/src/soliplex/main.py +0 -0
- {soliplex-0.76 → soliplex-0.76.1}/src/soliplex/mcp_auth.py +0 -0
- {soliplex-0.76 → soliplex-0.76.1}/src/soliplex/mcp_client.py +0 -0
- {soliplex-0.76 → soliplex-0.76.1}/src/soliplex/mcp_server.py +0 -0
- {soliplex-0.76 → soliplex-0.76.1}/src/soliplex/ollama.py +0 -0
- {soliplex-0.76 → soliplex-0.76.1}/src/soliplex/quizzes.py +0 -0
- {soliplex-0.76 → soliplex-0.76.1}/src/soliplex/rag_audit.py +0 -0
- {soliplex-0.76 → soliplex-0.76.1}/src/soliplex/sandbox_audit.py +0 -0
- {soliplex-0.76 → soliplex-0.76.1}/src/soliplex/secrets.py +0 -0
- {soliplex-0.76 → soliplex-0.76.1}/src/soliplex/skills/bwrap_sandbox/SKILL.md +0 -0
- {soliplex-0.76 → soliplex-0.76.1}/src/soliplex/skills/bwrap_sandbox/__init__.py +0 -0
- {soliplex-0.76 → soliplex-0.76.1}/src/soliplex/titles.py +0 -0
- {soliplex-0.76 → soliplex-0.76.1}/src/soliplex/tools/__init__.py +0 -0
- {soliplex-0.76 → soliplex-0.76.1}/src/soliplex/tools/file_uploads.py +0 -0
- {soliplex-0.76 → soliplex-0.76.1}/src/soliplex/tools/rag.py +0 -0
- {soliplex-0.76 → soliplex-0.76.1}/src/soliplex/tui/cli.py +0 -0
- {soliplex-0.76 → soliplex-0.76.1}/src/soliplex/tui/main.py +0 -0
- {soliplex-0.76 → soliplex-0.76.1}/src/soliplex/tui/rest_api.py +0 -0
- {soliplex-0.76 → soliplex-0.76.1}/src/soliplex/tui/serve.py +0 -0
- {soliplex-0.76 → soliplex-0.76.1}/src/soliplex/util.py +0 -0
- {soliplex-0.76 → soliplex-0.76.1}/src/soliplex/views/__init__.py +0 -0
- {soliplex-0.76 → soliplex-0.76.1}/src/soliplex/views/authn.py +0 -0
- {soliplex-0.76 → soliplex-0.76.1}/src/soliplex/views/authz.py +0 -0
- {soliplex-0.76 → soliplex-0.76.1}/src/soliplex/views/completions.py +0 -0
- {soliplex-0.76 → soliplex-0.76.1}/src/soliplex/views/installation.py +0 -0
- {soliplex-0.76 → soliplex-0.76.1}/src/soliplex/views/log_ingest.py +0 -0
- {soliplex-0.76 → soliplex-0.76.1}/src/soliplex/views/quizzes.py +0 -0
- {soliplex-0.76 → soliplex-0.76.1}/src/soliplex/views/room_file_uploads.py +0 -0
- {soliplex-0.76 → soliplex-0.76.1}/src/soliplex/views/rooms.py +0 -0
- {soliplex-0.76 → soliplex-0.76.1}/src/soliplex/views/sandbox_workdirs.py +0 -0
- {soliplex-0.76 → soliplex-0.76.1}/src/soliplex/views/stats.py +0 -0
- {soliplex-0.76 → soliplex-0.76.1}/src/soliplex/views/streaming.py +0 -0
- {soliplex-0.76 → soliplex-0.76.1}/src/soliplex/views/thread_file_uploads.py +0 -0
- {soliplex-0.76 → soliplex-0.76.1}/src/soliplex.egg-info/SOURCES.txt +0 -0
- {soliplex-0.76 → soliplex-0.76.1}/src/soliplex.egg-info/dependency_links.txt +0 -0
- {soliplex-0.76 → soliplex-0.76.1}/src/soliplex.egg-info/entry_points.txt +0 -0
- {soliplex-0.76 → soliplex-0.76.1}/src/soliplex.egg-info/requires.txt +0 -0
- {soliplex-0.76 → soliplex-0.76.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.76
|
|
3
|
+
Version: 0.76.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.76"
|
|
7
|
+
version = "0.76.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"
|
|
@@ -5,13 +5,12 @@ import typing
|
|
|
5
5
|
|
|
6
6
|
import pydantic_ai
|
|
7
7
|
from haiku.rag.capabilities import analysis as hr_caps_analysis
|
|
8
|
-
from haiku.rag.capabilities import compaction as hr_caps_compaction
|
|
9
|
-
from haiku.rag.capabilities import policy as hr_caps_policy
|
|
10
8
|
from haiku.rag.capabilities import rag as hr_caps_rag
|
|
11
9
|
from pydantic_ai import agent as ai_agent
|
|
12
10
|
from pydantic_ai import capabilities as ai_capabilities
|
|
13
11
|
from pydantic_ai import tools as ai_tools
|
|
14
12
|
from pydantic_ai import toolsets as ai_toolsets
|
|
13
|
+
from sqlalchemy.ext import asyncio as sqla_asyncio
|
|
15
14
|
|
|
16
15
|
from soliplex import agui
|
|
17
16
|
from soliplex import mcp_client
|
|
@@ -27,13 +26,6 @@ HR_VisionCapabilities = (
|
|
|
27
26
|
hr_caps_rag.RAGCapability | hr_caps_analysis.AnalysisCapability
|
|
28
27
|
)
|
|
29
28
|
|
|
30
|
-
# Capabilities which can always be eagerly loaded
|
|
31
|
-
HookOnlyCapabilities = (
|
|
32
|
-
hr_caps_compaction.EvidenceCompactionCapability
|
|
33
|
-
| hr_caps_policy.CitationPolicyCapability
|
|
34
|
-
| cap_rag_audit.RAGAccessAuditCapability
|
|
35
|
-
)
|
|
36
|
-
|
|
37
29
|
|
|
38
30
|
class CapabilityConfig(typing.Protocol):
|
|
39
31
|
"""Room-specific native capabilities and their audit targets."""
|
|
@@ -54,7 +46,7 @@ class AgentDependencies:
|
|
|
54
46
|
"""
|
|
55
47
|
|
|
56
48
|
the_installation: typing.Any # installation.Installation
|
|
57
|
-
|
|
49
|
+
thread_persistence_engine: sqla_asyncio.AsyncEngine = None
|
|
58
50
|
state: agui.AGUI_State = dataclasses.field(default_factory=dict)
|
|
59
51
|
room_id: str | None = None
|
|
60
52
|
thread_id: str | None = None
|
|
@@ -94,6 +86,27 @@ def make_mcp_client_toolset(
|
|
|
94
86
|
return factory(**toolset_config.tool_kwargs)
|
|
95
87
|
|
|
96
88
|
|
|
89
|
+
def _is_routing_capability(
|
|
90
|
+
capability: ai_capabilities.AbstractCapability,
|
|
91
|
+
) -> bool:
|
|
92
|
+
"""Whether the model has anything to load from this capability.
|
|
93
|
+
|
|
94
|
+
Tools or instructions are what there is to route between; a hook-only
|
|
95
|
+
capability offers neither.
|
|
96
|
+
"""
|
|
97
|
+
if capability.get_instructions() or capability.get_native_tools():
|
|
98
|
+
return True
|
|
99
|
+
|
|
100
|
+
toolset = capability.get_toolset()
|
|
101
|
+
|
|
102
|
+
if toolset is None:
|
|
103
|
+
return False
|
|
104
|
+
|
|
105
|
+
# A capability registering no tools of its own still gets an empty
|
|
106
|
+
# 'FunctionToolset', so ask whether it is empty, not whether it exists.
|
|
107
|
+
return bool(getattr(toolset, "tools", True))
|
|
108
|
+
|
|
109
|
+
|
|
97
110
|
def get_default_agent_from_configs(
|
|
98
111
|
*,
|
|
99
112
|
agent_config: config_agents.AgentConfig,
|
|
@@ -135,13 +148,10 @@ def get_default_agent_from_configs(
|
|
|
135
148
|
#
|
|
136
149
|
# Multiple stay deferred so the model routes between them via
|
|
137
150
|
# 'load_capability'.
|
|
138
|
-
#
|
|
139
|
-
# Hook-only capabilities expose no tools to route
|
|
140
|
-
# between, so they neither defer nor count.
|
|
141
151
|
routing_capabilities = [
|
|
142
152
|
capability
|
|
143
153
|
for capability in capabilities
|
|
144
|
-
if
|
|
154
|
+
if _is_routing_capability(capability)
|
|
145
155
|
]
|
|
146
156
|
defer_loading = len(routing_capabilities) > 1
|
|
147
157
|
for capability in routing_capabilities:
|
|
@@ -9,7 +9,6 @@ from . import _utils
|
|
|
9
9
|
from . import exceptions as config_exc
|
|
10
10
|
|
|
11
11
|
_no_repr_no_compare_none = _utils._no_repr_no_compare_none
|
|
12
|
-
_default_list_field = _utils._default_list_field
|
|
13
12
|
|
|
14
13
|
|
|
15
14
|
# ============================================================================
|
|
@@ -62,6 +61,29 @@ class OIDCAuthSystemConfig:
|
|
|
62
61
|
config_dict,
|
|
63
62
|
) from exc
|
|
64
63
|
|
|
64
|
+
@property
|
|
65
|
+
def as_yaml(self) -> dict:
|
|
66
|
+
result = {
|
|
67
|
+
"id": self.id,
|
|
68
|
+
"title": self.title,
|
|
69
|
+
"server_url": self.server_url,
|
|
70
|
+
"token_validation_pem": self.token_validation_pem,
|
|
71
|
+
"client_id": self.client_id,
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if self.scope is not None:
|
|
75
|
+
result["scope"] = self.scope
|
|
76
|
+
|
|
77
|
+
if self.client_secret:
|
|
78
|
+
# Dumped raw: 'env:'/'secret:' interpolations are not resolved.
|
|
79
|
+
result["client_secret"] = self.client_secret
|
|
80
|
+
|
|
81
|
+
if self.oidc_client_pem_path is not None:
|
|
82
|
+
# Absolutized against the config dir on load. See #1228.
|
|
83
|
+
result["oidc_client_pem_path"] = str(self.oidc_client_pem_path)
|
|
84
|
+
|
|
85
|
+
return result
|
|
86
|
+
|
|
65
87
|
@property
|
|
66
88
|
def server_metadata_url(self):
|
|
67
89
|
return f"{self.server_url}/{WELL_KNOWN_OPENID_CONFIGURATION}"
|
|
@@ -94,8 +116,3 @@ class OIDCAuthSystemConfig:
|
|
|
94
116
|
# added by the auth setup
|
|
95
117
|
# "authorize_state": main.SESSION_SECRET_KEY,
|
|
96
118
|
}
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
@dataclasses.dataclass(kw_only=True)
|
|
100
|
-
class AvailableOIDCAuthSystemConfigs:
|
|
101
|
-
systems: list[OIDCAuthSystemConfig] = _default_list_field()
|
|
@@ -81,5 +81,26 @@ class CompletionConfig:
|
|
|
81
81
|
|
|
82
82
|
return cls(**config_dict)
|
|
83
83
|
|
|
84
|
+
@property
|
|
85
|
+
def as_yaml(self) -> dict:
|
|
86
|
+
result = {
|
|
87
|
+
"id": self.id,
|
|
88
|
+
"agent": self.agent_config.as_yaml,
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if self.name is not None:
|
|
92
|
+
result["name"] = self.name
|
|
93
|
+
|
|
94
|
+
if self.tool_configs:
|
|
95
|
+
result["tools"] = [tc.as_yaml for tc in self.tool_configs.values()]
|
|
96
|
+
|
|
97
|
+
if self.mcp_client_toolset_configs:
|
|
98
|
+
result["mcp_client_toolsets"] = {
|
|
99
|
+
key: mctc.as_yaml
|
|
100
|
+
for key, mctc in self.mcp_client_toolset_configs.items()
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return result
|
|
104
|
+
|
|
84
105
|
|
|
85
106
|
CompletionConfigMap = dict[str, CompletionConfig]
|
|
@@ -145,12 +145,9 @@ class QuizConfig:
|
|
|
145
145
|
kwargs = {
|
|
146
146
|
"id": f"quiz-{self.id}-judge",
|
|
147
147
|
"model_name": "gpt-oss:20b",
|
|
148
|
+
"_installation_config": self._installation_config,
|
|
149
|
+
"_config_path": self._config_path,
|
|
148
150
|
}
|
|
149
|
-
if self._installation_config is not None:
|
|
150
|
-
i_config = self._installation_config
|
|
151
|
-
kwargs["provider_base_url"] = i_config.get_environment(
|
|
152
|
-
"OLLAMA_BASE_URL",
|
|
153
|
-
)
|
|
154
151
|
self.judge_agent = agents.AgentConfig(**kwargs)
|
|
155
152
|
|
|
156
153
|
@property
|
|
@@ -175,22 +175,6 @@ class FilesystemSkillConfig:
|
|
|
175
175
|
def errors(self) -> list[str]:
|
|
176
176
|
return self._validation_errors
|
|
177
177
|
|
|
178
|
-
@property
|
|
179
|
-
def license(self) -> None:
|
|
180
|
-
return None
|
|
181
|
-
|
|
182
|
-
@property
|
|
183
|
-
def compatibility(self) -> None:
|
|
184
|
-
return None
|
|
185
|
-
|
|
186
|
-
@property
|
|
187
|
-
def allowed_tools(self) -> list[str]:
|
|
188
|
-
return []
|
|
189
|
-
|
|
190
|
-
@property
|
|
191
|
-
def metadata(self) -> dict[str, str]:
|
|
192
|
-
return {}
|
|
193
|
-
|
|
194
178
|
@property
|
|
195
179
|
def extra_parameters(self) -> dict[str, pathlib.Path]:
|
|
196
180
|
return {"path": self.path}
|
|
@@ -264,22 +248,6 @@ class _HaikuRAGCapabilityConfig(
|
|
|
264
248
|
)
|
|
265
249
|
return result
|
|
266
250
|
|
|
267
|
-
@property
|
|
268
|
-
def license(self) -> None:
|
|
269
|
-
return None
|
|
270
|
-
|
|
271
|
-
@property
|
|
272
|
-
def compatibility(self) -> None:
|
|
273
|
-
return None
|
|
274
|
-
|
|
275
|
-
@property
|
|
276
|
-
def allowed_tools(self) -> list[str]:
|
|
277
|
-
return []
|
|
278
|
-
|
|
279
|
-
@property
|
|
280
|
-
def metadata(self) -> dict[str, str]:
|
|
281
|
-
return {}
|
|
282
|
-
|
|
283
251
|
@property
|
|
284
252
|
def extra_parameters(self) -> dict[str, typing.Any]:
|
|
285
253
|
return self.get_extra_parameters()
|
|
@@ -364,22 +332,6 @@ class _HaikuRAGEvidenceSkillConfig:
|
|
|
364
332
|
def as_yaml(self) -> dict:
|
|
365
333
|
return {"kind": self.kind}
|
|
366
334
|
|
|
367
|
-
@property
|
|
368
|
-
def license(self) -> None:
|
|
369
|
-
return None
|
|
370
|
-
|
|
371
|
-
@property
|
|
372
|
-
def compatibility(self) -> None:
|
|
373
|
-
return None
|
|
374
|
-
|
|
375
|
-
@property
|
|
376
|
-
def allowed_tools(self) -> list[str]:
|
|
377
|
-
return []
|
|
378
|
-
|
|
379
|
-
@property
|
|
380
|
-
def metadata(self) -> dict[str, str]:
|
|
381
|
-
return {}
|
|
382
|
-
|
|
383
335
|
@property
|
|
384
336
|
def extra_parameters(self) -> dict[str, typing.Any]:
|
|
385
337
|
return {}
|
|
@@ -499,22 +451,6 @@ class BwrapSandboxSkillConfig:
|
|
|
499
451
|
}
|
|
500
452
|
return result
|
|
501
453
|
|
|
502
|
-
@property
|
|
503
|
-
def license(self) -> None:
|
|
504
|
-
return None
|
|
505
|
-
|
|
506
|
-
@property
|
|
507
|
-
def compatibility(self) -> None:
|
|
508
|
-
return None
|
|
509
|
-
|
|
510
|
-
@property
|
|
511
|
-
def allowed_tools(self) -> list[str]:
|
|
512
|
-
return []
|
|
513
|
-
|
|
514
|
-
@property
|
|
515
|
-
def metadata(self) -> dict[str, str]:
|
|
516
|
-
return {}
|
|
517
|
-
|
|
518
454
|
@property
|
|
519
455
|
def extra_parameters(self) -> dict[str, typing.Any]:
|
|
520
456
|
result = {"default_environment": self.default_environment}
|
|
@@ -609,22 +545,6 @@ class EntrypointCapabilityConfig:
|
|
|
609
545
|
def as_yaml(self) -> dict:
|
|
610
546
|
return {"kind": self.kind, "name": self.name, **self.params}
|
|
611
547
|
|
|
612
|
-
@property
|
|
613
|
-
def license(self) -> None:
|
|
614
|
-
return None
|
|
615
|
-
|
|
616
|
-
@property
|
|
617
|
-
def compatibility(self) -> None:
|
|
618
|
-
return None
|
|
619
|
-
|
|
620
|
-
@property
|
|
621
|
-
def allowed_tools(self) -> list[str]:
|
|
622
|
-
return []
|
|
623
|
-
|
|
624
|
-
@property
|
|
625
|
-
def metadata(self) -> dict[str, str]:
|
|
626
|
-
return {}
|
|
627
|
-
|
|
628
548
|
@property
|
|
629
549
|
def extra_parameters(self) -> dict[str, typing.Any]:
|
|
630
550
|
return dict(self.params)
|
|
@@ -16,7 +16,6 @@ from sqlalchemy.ext import asyncio as sqla_asyncio
|
|
|
16
16
|
from sqlalchemy.pool import NullPool
|
|
17
17
|
|
|
18
18
|
from soliplex import agents
|
|
19
|
-
from soliplex import agui
|
|
20
19
|
from soliplex import authz
|
|
21
20
|
from soliplex import loggers
|
|
22
21
|
from soliplex import mcp_server
|
|
@@ -405,7 +404,7 @@ class Installation:
|
|
|
405
404
|
*,
|
|
406
405
|
room_id: str,
|
|
407
406
|
user: dict,
|
|
408
|
-
|
|
407
|
+
thread_persistence_engine: sqla_asyncio.AsyncEngine = None,
|
|
409
408
|
the_room_authz: authz.RoomAuthorizationPolicy = None,
|
|
410
409
|
run_agent_input: agui_core.RunAgentInput = None,
|
|
411
410
|
the_logger: loggers.LogWrapper = None,
|
|
@@ -424,8 +423,8 @@ class Installation:
|
|
|
424
423
|
thread_id = run_agent_input.thread_id
|
|
425
424
|
run_id = run_agent_input.run_id
|
|
426
425
|
|
|
427
|
-
if
|
|
428
|
-
kwargs["
|
|
426
|
+
if thread_persistence_engine is not None:
|
|
427
|
+
kwargs["thread_persistence_engine"] = thread_persistence_engine
|
|
429
428
|
|
|
430
429
|
return agents.AgentDependencies(
|
|
431
430
|
the_installation=self,
|
|
@@ -143,10 +143,6 @@ class Skill(pydantic.BaseModel):
|
|
|
143
143
|
source: config_skills.SkillKind | None = None
|
|
144
144
|
name: str
|
|
145
145
|
description: str
|
|
146
|
-
license: str | None
|
|
147
|
-
compatibility: str | None
|
|
148
|
-
allowed_tools: SkillAllowedTools = None
|
|
149
|
-
metadata: SkillMetadata = None
|
|
150
146
|
state_type_schema: dict[str, typing.Any] | None = None
|
|
151
147
|
state_namespace: str | None = None
|
|
152
148
|
extra_parameters: dict[str, typing.Any] = {}
|
|
@@ -168,10 +164,6 @@ class Skill(pydantic.BaseModel):
|
|
|
168
164
|
source=skill_config.source,
|
|
169
165
|
name=skill_config.name,
|
|
170
166
|
description=skill_config.description,
|
|
171
|
-
license=skill_config.license,
|
|
172
|
-
compatibility=skill_config.compatibility,
|
|
173
|
-
allowed_tools=" ".join(skill_config.allowed_tools),
|
|
174
|
-
metadata=skill_config.metadata,
|
|
175
167
|
state_namespace=skill_config.state_namespace,
|
|
176
168
|
**kwargs,
|
|
177
169
|
)
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
- Allow privileged users to mark feedback a as "reviewed", "resolved".
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
|
+
import contextlib
|
|
7
8
|
import datetime
|
|
8
9
|
import enum
|
|
9
10
|
import typing
|
|
@@ -12,10 +13,12 @@ import jsonpatch
|
|
|
12
13
|
import pydantic
|
|
13
14
|
import pydantic_ai
|
|
14
15
|
from ag_ui import core as agui_core
|
|
16
|
+
from sqlalchemy.ext import asyncio as sqla_asyncio
|
|
15
17
|
|
|
16
18
|
from soliplex import agents
|
|
17
19
|
from soliplex import agui
|
|
18
20
|
from soliplex.agui import parser as agui_parser
|
|
21
|
+
from soliplex.agui import persistence as agui_persistence
|
|
19
22
|
|
|
20
23
|
FRS = agui.FeedbackReviewStatus
|
|
21
24
|
STATE_NAMESPACE = "soliplex-agui-run-feedback"
|
|
@@ -241,11 +244,28 @@ class FeedbackResolution(pydantic.BaseModel):
|
|
|
241
244
|
note: str | None = None
|
|
242
245
|
|
|
243
246
|
|
|
244
|
-
|
|
247
|
+
@contextlib.asynccontextmanager
|
|
248
|
+
async def _get_threads_storage(
|
|
245
249
|
ctx: pydantic_ai.RunContext[agents.AgentDependencies],
|
|
250
|
+
):
|
|
251
|
+
"""Context manager for tool-call local thread storage / session
|
|
252
|
+
|
|
253
|
+
Cannot be derived from the FastAPI request dependency, because
|
|
254
|
+
tool calls must run / complete in their own boundaries.
|
|
255
|
+
"""
|
|
256
|
+
engine = ctx.deps.thread_persistence_engine
|
|
257
|
+
async with sqla_asyncio.AsyncSession(bind=engine) as session:
|
|
258
|
+
# One transaction per tool call: the storage methods no longer
|
|
259
|
+
# commit, so the tool call owns the unit of work -- committed on a
|
|
260
|
+
# clean response, rolled back if the handler raises.
|
|
261
|
+
async with session.begin():
|
|
262
|
+
yield agui_persistence.ThreadStorage(session)
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
async def _do_query(
|
|
266
|
+
the_threads: agui_persistence.ThreadStorage,
|
|
246
267
|
query: RecentRunFeedbackQuery,
|
|
247
268
|
) -> RecentRunFeedback:
|
|
248
|
-
the_threads = ctx.deps.the_threads
|
|
249
269
|
runs_w_recent_fb = await the_threads.list_recent_run_feedback(
|
|
250
270
|
**query.as_kwargs,
|
|
251
271
|
)
|
|
@@ -328,7 +348,8 @@ async def query_recent_feedback(
|
|
|
328
348
|
)
|
|
329
349
|
before_state = {} if raw_state is None else {STATE_NAMESPACE: raw_state}
|
|
330
350
|
|
|
331
|
-
|
|
351
|
+
async with _get_threads_storage(ctx) as the_threads:
|
|
352
|
+
entries = await _do_query(the_threads, query)
|
|
332
353
|
our_state.query = query
|
|
333
354
|
our_state.entries = entries
|
|
334
355
|
|
|
@@ -390,29 +411,29 @@ async def get_feedback_run_info(
|
|
|
390
411
|
|
|
391
412
|
to_query, _ = _find_feedback_by_run_id(our_state, run_id)
|
|
392
413
|
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
414
|
+
async with _get_threads_storage(ctx) as the_threads:
|
|
415
|
+
run = await the_threads.get_run(
|
|
416
|
+
user_name=to_query.user_name,
|
|
417
|
+
room_id=to_query.room_id,
|
|
418
|
+
thread_id=to_query.thread_id,
|
|
419
|
+
run_id=to_query.run_id,
|
|
420
|
+
)
|
|
400
421
|
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
422
|
+
thread = await run.awaitable_attrs.thread
|
|
423
|
+
email = await thread.awaitable_attrs.email
|
|
424
|
+
run_input = await run.awaitable_attrs.run_agent_input
|
|
425
|
+
parent_run_id = await run.awaitable_attrs.parent_run_id
|
|
426
|
+
events = await run.awaitable_attrs.events
|
|
406
427
|
|
|
407
|
-
|
|
428
|
+
esp = agui_parser.EventStreamParser(run_input)
|
|
408
429
|
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
430
|
+
for event in events:
|
|
431
|
+
try:
|
|
432
|
+
e_model = event.to_agui_model()
|
|
433
|
+
# agui_core.events.Event is not all-inclusive :<
|
|
434
|
+
except pydantic.ValidationError: # pragma: NO COVER
|
|
435
|
+
continue
|
|
436
|
+
esp(e_model)
|
|
416
437
|
|
|
417
438
|
user_prompts = [
|
|
418
439
|
message.content for message in esp.messages if message.role == "user"
|
|
@@ -471,17 +492,17 @@ async def review_recent_feedback(
|
|
|
471
492
|
our_state, review.run_id, [FromWhichAttrs.OPENED]
|
|
472
493
|
)
|
|
473
494
|
|
|
474
|
-
the_threads = ctx.deps.the_threads
|
|
475
495
|
user = ctx.deps.user
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
496
|
+
async with _get_threads_storage(ctx) as the_threads:
|
|
497
|
+
await the_threads.review_run_feedback(
|
|
498
|
+
reviewer_user_name=user.preferred_username,
|
|
499
|
+
reviewer_email=user.email,
|
|
500
|
+
note=review.note,
|
|
501
|
+
user_name=to_review.user_name,
|
|
502
|
+
room_id=to_review.room_id,
|
|
503
|
+
thread_id=to_review.thread_id,
|
|
504
|
+
run_id=to_review.run_id,
|
|
505
|
+
)
|
|
485
506
|
|
|
486
507
|
to_review.status = FRS.REVIEWED
|
|
487
508
|
to_review.note = review.note
|
|
@@ -515,17 +536,17 @@ async def resolve_recent_feedback(
|
|
|
515
536
|
[FromWhichAttrs.OPENED, FromWhichAttrs.REVIEWED],
|
|
516
537
|
)
|
|
517
538
|
|
|
518
|
-
the_threads = ctx.deps.the_threads
|
|
519
539
|
user = ctx.deps.user
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
540
|
+
async with _get_threads_storage(ctx) as the_threads:
|
|
541
|
+
await the_threads.resolve_run_feedback(
|
|
542
|
+
resolver_user_name=user.preferred_username,
|
|
543
|
+
resolver_email=user.email,
|
|
544
|
+
note=resolution.note,
|
|
545
|
+
user_name=to_resolve.user_name,
|
|
546
|
+
room_id=to_resolve.room_id,
|
|
547
|
+
thread_id=to_resolve.thread_id,
|
|
548
|
+
run_id=to_resolve.run_id,
|
|
549
|
+
)
|
|
529
550
|
|
|
530
551
|
to_resolve.status = FRS.RESOLVED
|
|
531
552
|
to_resolve.note = resolution.note
|
|
@@ -698,7 +698,6 @@ async def post_room_agui_thread_id_run_id(
|
|
|
698
698
|
thread_id: pydantic.UUID4,
|
|
699
699
|
run_id: pydantic.UUID4,
|
|
700
700
|
the_installation: installation.Installation = depend_the_installation,
|
|
701
|
-
the_threads: agui.ThreadStorage = depend_the_threads,
|
|
702
701
|
the_room_authz: authz.RoomAuthorizationPolicy = depend_the_room_authz,
|
|
703
702
|
the_user_claims: authn.UserClaims = depend_the_user_claims,
|
|
704
703
|
the_logger: loggers.LogWrapper = depend_the_logger,
|
|
@@ -788,13 +787,6 @@ async def post_room_agui_thread_id_run_id(
|
|
|
788
787
|
agent=agent,
|
|
789
788
|
)
|
|
790
789
|
|
|
791
|
-
# Persist the run input in its own short-lived, committed session
|
|
792
|
-
# rather than via the request-scoped 'the_threads'. This handler
|
|
793
|
-
# returns a long-lived StreamingResponse, so the request session's
|
|
794
|
-
# transaction stays open for the whole stream; committing the input
|
|
795
|
-
# write here releases its lock immediately (and keeps the request
|
|
796
|
-
# transaction from holding a writer lock behind the incremental
|
|
797
|
-
# per-event background saves).
|
|
798
790
|
try:
|
|
799
791
|
async with sqla_asyncio.AsyncSession(
|
|
800
792
|
bind=request.state.threads_engine,
|
|
@@ -818,7 +810,7 @@ async def post_room_agui_thread_id_run_id(
|
|
|
818
810
|
room_id=room_id,
|
|
819
811
|
user=user,
|
|
820
812
|
run_agent_input=agui_adapter.run_input,
|
|
821
|
-
|
|
813
|
+
thread_persistence_engine=request.state.threads_engine,
|
|
822
814
|
the_logger=the_logger,
|
|
823
815
|
)
|
|
824
816
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: soliplex
|
|
3
|
-
Version: 0.76
|
|
3
|
+
Version: 0.76.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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|