soliplex 0.70.2__tar.gz → 0.72__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.70.2/src/soliplex.egg-info → soliplex-0.72}/PKG-INFO +2 -2
- {soliplex-0.70.2 → soliplex-0.72}/pyproject.toml +2 -5
- {soliplex-0.70.2 → soliplex-0.72}/src/soliplex/agents.py +12 -3
- {soliplex-0.70.2 → soliplex-0.72}/src/soliplex/agui/__init__.py +5 -1
- {soliplex-0.70.2 → soliplex-0.72}/src/soliplex/agui/parser.py +4 -4
- {soliplex-0.70.2 → soliplex-0.72}/src/soliplex/agui/persistence.py +168 -31
- {soliplex-0.70.2 → soliplex-0.72}/src/soliplex/agui/schema.py +4 -4
- {soliplex-0.70.2 → soliplex-0.72}/src/soliplex/authz/__init__.py +138 -23
- soliplex-0.72/src/soliplex/authz/persistence.py +506 -0
- {soliplex-0.70.2 → soliplex-0.72}/src/soliplex/authz/schema.py +47 -24
- {soliplex-0.70.2 → soliplex-0.72}/src/soliplex/cli/admin_users.py +80 -117
- soliplex-0.72/src/soliplex/cli/ask.py +311 -0
- {soliplex-0.70.2 → soliplex-0.72}/src/soliplex/cli/audit.py +48 -44
- soliplex-0.72/src/soliplex/cli/cli_util.py +243 -0
- {soliplex-0.70.2 → soliplex-0.72}/src/soliplex/cli/main.py +3 -3
- {soliplex-0.70.2 → soliplex-0.72}/src/soliplex/cli/room_authz.py +185 -397
- {soliplex-0.70.2 → soliplex-0.72}/src/soliplex/cli/serve.py +23 -6
- {soliplex-0.70.2 → soliplex-0.72}/src/soliplex/config/exceptions.py +8 -0
- {soliplex-0.70.2 → soliplex-0.72}/src/soliplex/config/installation.py +80 -22
- {soliplex-0.70.2 → soliplex-0.72}/src/soliplex/config/meta.py +3 -7
- soliplex-0.72/src/soliplex/config/middleware.py +115 -0
- {soliplex-0.70.2 → soliplex-0.72}/src/soliplex/config/rooms.py +5 -0
- {soliplex-0.70.2 → soliplex-0.72}/src/soliplex/config/routing.py +3 -1
- {soliplex-0.70.2 → soliplex-0.72}/src/soliplex/config/skills.py +18 -36
- {soliplex-0.70.2 → soliplex-0.72}/src/soliplex/examples.py +50 -3
- {soliplex-0.70.2 → soliplex-0.72}/src/soliplex/installation.py +29 -27
- soliplex-0.72/src/soliplex/loggers.py +705 -0
- {soliplex-0.70.2 → soliplex-0.72}/src/soliplex/main.py +43 -49
- {soliplex-0.70.2 → soliplex-0.72}/src/soliplex/models.py +53 -18
- soliplex-0.72/src/soliplex/rag_audit.py +125 -0
- soliplex-0.72/src/soliplex/sandbox_audit.py +65 -0
- {soliplex-0.70.2 → soliplex-0.72}/src/soliplex/skills/bwrap_sandbox/__init__.py +106 -19
- {soliplex-0.70.2 → soliplex-0.72}/src/soliplex/titles.py +9 -6
- {soliplex-0.70.2 → soliplex-0.72}/src/soliplex/tools/agui_run_feedback.py +3 -3
- {soliplex-0.70.2 → soliplex-0.72}/src/soliplex/tools/rag.py +18 -9
- {soliplex-0.70.2 → soliplex-0.72}/src/soliplex/views/__init__.py +40 -3
- {soliplex-0.70.2 → soliplex-0.72}/src/soliplex/views/agui.py +120 -174
- {soliplex-0.70.2 → soliplex-0.72}/src/soliplex/views/authz.py +38 -25
- {soliplex-0.70.2 → soliplex-0.72}/src/soliplex/views/installation.py +72 -13
- {soliplex-0.70.2 → soliplex-0.72}/src/soliplex/views/quizzes.py +19 -16
- soliplex-0.72/src/soliplex/views/room_file_uploads.py +209 -0
- {soliplex-0.70.2 → soliplex-0.72}/src/soliplex/views/rooms.py +74 -34
- soliplex-0.72/src/soliplex/views/sandbox_workdirs.py +139 -0
- {soliplex-0.70.2 → soliplex-0.72}/src/soliplex/views/stats.py +12 -12
- {soliplex-0.70.2 → soliplex-0.72}/src/soliplex/views/streaming.py +43 -23
- soliplex-0.72/src/soliplex/views/thread_file_uploads.py +202 -0
- {soliplex-0.70.2 → soliplex-0.72/src/soliplex.egg-info}/PKG-INFO +2 -2
- {soliplex-0.70.2 → soliplex-0.72}/src/soliplex.egg-info/SOURCES.txt +8 -3
- {soliplex-0.70.2 → soliplex-0.72}/src/soliplex.egg-info/requires.txt +1 -1
- soliplex-0.70.2/src/soliplex/auth.py +0 -10
- soliplex-0.70.2/src/soliplex/authz/persistence.py +0 -232
- soliplex-0.70.2/src/soliplex/cli/cli_util.py +0 -127
- soliplex-0.70.2/src/soliplex/loggers.py +0 -172
- soliplex-0.70.2/src/soliplex/views/file_uploads.py +0 -486
- {soliplex-0.70.2 → soliplex-0.72}/LICENSE +0 -0
- {soliplex-0.70.2 → soliplex-0.72}/MANIFEST.in +0 -0
- {soliplex-0.70.2 → soliplex-0.72}/README.md +0 -0
- {soliplex-0.70.2 → soliplex-0.72}/example/skills/bare-bones/SKILL.md +0 -0
- {soliplex-0.70.2 → soliplex-0.72}/setup.cfg +0 -0
- {soliplex-0.70.2 → soliplex-0.72}/skills/soliplex-docs/SKILL.md +0 -0
- {soliplex-0.70.2 → soliplex-0.72}/src/soliplex/agui/util.py +0 -0
- {soliplex-0.70.2 → soliplex-0.72}/src/soliplex/authn.py +0 -0
- {soliplex-0.70.2 → soliplex-0.72}/src/soliplex/cli/ollama.py +0 -0
- {soliplex-0.70.2 → soliplex-0.72}/src/soliplex/cli/types.py +0 -0
- {soliplex-0.70.2 → soliplex-0.72}/src/soliplex/completions.py +0 -0
- {soliplex-0.70.2 → soliplex-0.72}/src/soliplex/config/__init__.py +0 -0
- {soliplex-0.70.2 → soliplex-0.72}/src/soliplex/config/_utils.py +0 -0
- {soliplex-0.70.2 → soliplex-0.72}/src/soliplex/config/agents.py +0 -0
- {soliplex-0.70.2 → soliplex-0.72}/src/soliplex/config/agui.py +0 -0
- {soliplex-0.70.2 → soliplex-0.72}/src/soliplex/config/authsystem.py +0 -0
- {soliplex-0.70.2 → soliplex-0.72}/src/soliplex/config/completions.py +0 -0
- {soliplex-0.70.2 → soliplex-0.72}/src/soliplex/config/logfire.py +0 -0
- {soliplex-0.70.2 → soliplex-0.72}/src/soliplex/config/quizzes.py +0 -0
- {soliplex-0.70.2 → soliplex-0.72}/src/soliplex/config/rag.py +0 -0
- {soliplex-0.70.2 → soliplex-0.72}/src/soliplex/config/secrets.py +0 -0
- {soliplex-0.70.2 → soliplex-0.72}/src/soliplex/config/tools.py +0 -0
- {soliplex-0.70.2 → soliplex-0.72}/src/soliplex/log_ingest.py +0 -0
- {soliplex-0.70.2 → soliplex-0.72}/src/soliplex/mcp_auth.py +0 -0
- {soliplex-0.70.2 → soliplex-0.72}/src/soliplex/mcp_client.py +0 -0
- {soliplex-0.70.2 → soliplex-0.72}/src/soliplex/mcp_server.py +0 -0
- {soliplex-0.70.2 → soliplex-0.72}/src/soliplex/ollama.py +0 -0
- {soliplex-0.70.2 → soliplex-0.72}/src/soliplex/quizzes.py +0 -0
- {soliplex-0.70.2 → soliplex-0.72}/src/soliplex/secrets.py +0 -0
- {soliplex-0.70.2 → soliplex-0.72}/src/soliplex/skills/bwrap_sandbox/SKILL.md +0 -0
- {soliplex-0.70.2 → soliplex-0.72}/src/soliplex/tools/__init__.py +0 -0
- {soliplex-0.70.2 → soliplex-0.72}/src/soliplex/tools/file_uploads.py +0 -0
- {soliplex-0.70.2 → soliplex-0.72}/src/soliplex/tui/cli.py +0 -0
- {soliplex-0.70.2 → soliplex-0.72}/src/soliplex/tui/main.py +0 -0
- {soliplex-0.70.2 → soliplex-0.72}/src/soliplex/tui/rest_api.py +0 -0
- {soliplex-0.70.2 → soliplex-0.72}/src/soliplex/tui/serve.py +0 -0
- {soliplex-0.70.2 → soliplex-0.72}/src/soliplex/util.py +0 -0
- {soliplex-0.70.2 → soliplex-0.72}/src/soliplex/views/authn.py +0 -0
- {soliplex-0.70.2 → soliplex-0.72}/src/soliplex/views/completions.py +0 -0
- {soliplex-0.70.2 → soliplex-0.72}/src/soliplex/views/log_ingest.py +0 -0
- {soliplex-0.70.2 → soliplex-0.72}/src/soliplex.egg-info/dependency_links.txt +0 -0
- {soliplex-0.70.2 → soliplex-0.72}/src/soliplex.egg-info/entry_points.txt +0 -0
- {soliplex-0.70.2 → soliplex-0.72}/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.72
|
|
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
|
|
@@ -19,7 +19,7 @@ Requires-Dist: fastapi
|
|
|
19
19
|
Requires-Dist: fastmcp<3.4,>=3.3.0
|
|
20
20
|
Requires-Dist: fakeredis!=2.35.0
|
|
21
21
|
Requires-Dist: greenlet
|
|
22
|
-
Requires-Dist: haiku.rag-slim<0.
|
|
22
|
+
Requires-Dist: haiku.rag-slim<0.66,>=0.65
|
|
23
23
|
Requires-Dist: haiku-skills<0.19,>=0.18.0
|
|
24
24
|
Requires-Dist: idna>=3.15
|
|
25
25
|
Requires-Dist: itsdangerous
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "soliplex"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.72"
|
|
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"
|
|
@@ -25,7 +25,7 @@ dependencies = [
|
|
|
25
25
|
"fastmcp >= 3.3.0, < 3.4",
|
|
26
26
|
"fakeredis != 2.35.0", # brownbag
|
|
27
27
|
"greenlet",
|
|
28
|
-
"haiku.rag-slim >= 0.
|
|
28
|
+
"haiku.rag-slim >= 0.65, < 0.66",
|
|
29
29
|
"haiku-skills >= 0.18.0, < 0.19",
|
|
30
30
|
"idna >= 3.15",
|
|
31
31
|
"itsdangerous",
|
|
@@ -92,9 +92,6 @@ markers = [
|
|
|
92
92
|
[tool.coverage.run]
|
|
93
93
|
omit = [
|
|
94
94
|
"src/soliplex/cli/__init__.py",
|
|
95
|
-
"src/soliplex/cli/serve.py",
|
|
96
|
-
|
|
97
|
-
"src/soliplex/examples.py",
|
|
98
95
|
"src/soliplex/tui.py",
|
|
99
96
|
]
|
|
100
97
|
|
|
@@ -11,7 +11,7 @@ from pydantic_ai import agent as ai_agent
|
|
|
11
11
|
from pydantic_ai import tools as ai_tools
|
|
12
12
|
from pydantic_ai import toolsets as ai_toolsets
|
|
13
13
|
|
|
14
|
-
from soliplex import agui
|
|
14
|
+
from soliplex import agui
|
|
15
15
|
from soliplex import mcp_client
|
|
16
16
|
from soliplex import models
|
|
17
17
|
from soliplex.config import agents as config_agents
|
|
@@ -41,8 +41,8 @@ class AgentDependencies:
|
|
|
41
41
|
"""
|
|
42
42
|
|
|
43
43
|
the_installation: typing.Any # installation.Installation
|
|
44
|
-
the_threads:
|
|
45
|
-
state:
|
|
44
|
+
the_threads: agui.ThreadStorage = None
|
|
45
|
+
state: agui.AGUI_State = dataclasses.field(default_factory=dict)
|
|
46
46
|
room_id: str | None = None
|
|
47
47
|
thread_id: str | None = None
|
|
48
48
|
run_id: str | None = None
|
|
@@ -150,3 +150,12 @@ def get_agent_from_configs(
|
|
|
150
150
|
mcp_client_toolset_configs=mcp_client_toolset_configs,
|
|
151
151
|
skill_toolset_config=skill_toolset_config,
|
|
152
152
|
)
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def find_skill_toolset(
|
|
156
|
+
agent: pydantic_ai.Agent,
|
|
157
|
+
) -> hs_agent.SkillToolset | None:
|
|
158
|
+
for toolset in getattr(agent, "toolsets", ()):
|
|
159
|
+
if isinstance(toolset, hs_agent.SkillToolset):
|
|
160
|
+
return toolset
|
|
161
|
+
return None
|
|
@@ -587,7 +587,11 @@ async def get_the_threads(request: fastapi.Request) -> ThreadStorage:
|
|
|
587
587
|
|
|
588
588
|
engine = request.state.threads_engine
|
|
589
589
|
async with sqla_asyncio.AsyncSession(bind=engine) as session:
|
|
590
|
-
|
|
590
|
+
# One transaction per request: the storage methods no longer
|
|
591
|
+
# commit, so the request owns the unit of work -- committed on a
|
|
592
|
+
# clean response, rolled back if the handler raises.
|
|
593
|
+
async with session.begin():
|
|
594
|
+
yield persistence.ThreadStorage(session)
|
|
591
595
|
|
|
592
596
|
|
|
593
597
|
depend_the_threads = fastapi.Depends(get_the_threads)
|
|
@@ -10,7 +10,7 @@ from collections import abc
|
|
|
10
10
|
import jsonpatch
|
|
11
11
|
from ag_ui import core as agui_core
|
|
12
12
|
|
|
13
|
-
from soliplex import agui
|
|
13
|
+
from soliplex import agui
|
|
14
14
|
|
|
15
15
|
AGUI_EventIterator = abc.AsyncIterator[agui_core.Event]
|
|
16
16
|
AGUI_EventDictIterator = abc.AsyncIterator[agui_core.Event]
|
|
@@ -230,12 +230,12 @@ DEFAULT_STRIPPED_MESSAGE_TYPES: StrippedMessageTypes = None
|
|
|
230
230
|
@dataclasses.dataclass
|
|
231
231
|
class EventStreamParser:
|
|
232
232
|
run_agent_input: dataclasses.InitVar[agui_core.RunAgentInput] = None
|
|
233
|
-
run: dataclasses.InitVar[
|
|
233
|
+
run: dataclasses.InitVar[agui.Run] = None
|
|
234
234
|
|
|
235
235
|
_ = dataclasses.KW_ONLY
|
|
236
236
|
|
|
237
237
|
_run_input: agui_core.RunAgentInput = None
|
|
238
|
-
_the_run:
|
|
238
|
+
_the_run: agui.Run = None
|
|
239
239
|
|
|
240
240
|
run_status: RunStatus = RunStatus.INITIALIZED
|
|
241
241
|
active_steps: set[str] = dataclasses.field(default_factory=set)
|
|
@@ -282,7 +282,7 @@ class EventStreamParser:
|
|
|
282
282
|
self.messages_by_id = {msg.id: msg for msg in value.messages}
|
|
283
283
|
|
|
284
284
|
@property
|
|
285
|
-
def the_run(self) ->
|
|
285
|
+
def the_run(self) -> agui.Run:
|
|
286
286
|
return self._the_run
|
|
287
287
|
|
|
288
288
|
def _assert_running(self, event):
|
|
@@ -1,14 +1,20 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import collections.abc
|
|
3
4
|
import contextlib
|
|
4
5
|
import datetime
|
|
5
6
|
import typing
|
|
6
7
|
|
|
8
|
+
import logfire
|
|
7
9
|
from ag_ui import core as agui_core
|
|
10
|
+
from haiku.skills import agent as hs_agent
|
|
11
|
+
from sqlalchemy import exc as sqla_exc
|
|
8
12
|
from sqlalchemy import sql as sqla_sql
|
|
9
13
|
from sqlalchemy.ext import asyncio as sqla_asyncio
|
|
10
14
|
|
|
11
|
-
from soliplex import agui
|
|
15
|
+
from soliplex import agui
|
|
16
|
+
from soliplex import loggers
|
|
17
|
+
from soliplex import rag_audit
|
|
12
18
|
from soliplex.agui import persistence as agui_persistence
|
|
13
19
|
from soliplex.agui import schema as agui_schema
|
|
14
20
|
from soliplex.agui import util as agui_util
|
|
@@ -16,7 +22,7 @@ from soliplex.agui import util as agui_util
|
|
|
16
22
|
# Temporary backward-compatibility: to be removed in 'v0.45'
|
|
17
23
|
from soliplex.agui.schema import * # noqa F403
|
|
18
24
|
|
|
19
|
-
FeedbackReviewStatus =
|
|
25
|
+
FeedbackReviewStatus = agui.FeedbackReviewStatus
|
|
20
26
|
|
|
21
27
|
|
|
22
28
|
class NoFeedbackFound(ValueError):
|
|
@@ -48,15 +54,19 @@ _LAST_ACTIVITY = sqla_sql.func.max(
|
|
|
48
54
|
)
|
|
49
55
|
|
|
50
56
|
|
|
51
|
-
class ThreadStorage(
|
|
57
|
+
class ThreadStorage(agui.ThreadStorage):
|
|
52
58
|
def __init__(self, session: sqla_asyncio.AsyncSession):
|
|
53
59
|
self._session = session
|
|
54
60
|
|
|
55
61
|
@property
|
|
56
62
|
@contextlib.asynccontextmanager
|
|
57
63
|
async def session(self):
|
|
58
|
-
|
|
59
|
-
|
|
64
|
+
# Yield the caller-owned session as-is: the session owner (a
|
|
65
|
+
# FastAPI dependency, CLI context manager, or streaming helper)
|
|
66
|
+
# owns the transaction boundary and commits the unit of work
|
|
67
|
+
# exactly once. Methods here never open their own transaction or
|
|
68
|
+
# commit, so callers -- and tests -- need no interleaved commits.
|
|
69
|
+
yield self._session
|
|
60
70
|
|
|
61
71
|
async def _find_user_thread(
|
|
62
72
|
self,
|
|
@@ -74,12 +84,12 @@ class ThreadStorage(agui_package.ThreadStorage):
|
|
|
74
84
|
thread = (await session.scalars(query)).first()
|
|
75
85
|
|
|
76
86
|
if thread is None:
|
|
77
|
-
raise
|
|
87
|
+
raise agui.UnknownThread(user_name, thread_id)
|
|
78
88
|
|
|
79
89
|
t_room_id = await thread.awaitable_attrs.room_id
|
|
80
90
|
|
|
81
91
|
if t_room_id != room_id:
|
|
82
|
-
raise
|
|
92
|
+
raise agui.ThreadRoomMismatch(room_id, t_room_id)
|
|
83
93
|
|
|
84
94
|
return thread
|
|
85
95
|
|
|
@@ -91,7 +101,7 @@ class ThreadStorage(agui_package.ThreadStorage):
|
|
|
91
101
|
thread_id: str,
|
|
92
102
|
run_id: str,
|
|
93
103
|
session,
|
|
94
|
-
exc_type=
|
|
104
|
+
exc_type=agui.UnknownRun,
|
|
95
105
|
):
|
|
96
106
|
thread = await self._find_user_thread(
|
|
97
107
|
user_name=user_name,
|
|
@@ -294,7 +304,7 @@ class ThreadStorage(agui_package.ThreadStorage):
|
|
|
294
304
|
thread_id=thread_id,
|
|
295
305
|
run_id=parent_run_id,
|
|
296
306
|
session=session,
|
|
297
|
-
exc_type=
|
|
307
|
+
exc_type=agui.MissingParentRun,
|
|
298
308
|
)
|
|
299
309
|
else:
|
|
300
310
|
parent = None
|
|
@@ -362,7 +372,7 @@ class ThreadStorage(agui_package.ThreadStorage):
|
|
|
362
372
|
already = await run.awaitable_attrs.run_agent_input
|
|
363
373
|
|
|
364
374
|
if already is not None:
|
|
365
|
-
raise
|
|
375
|
+
raise agui.RunAlreadyStarted(
|
|
366
376
|
user_name,
|
|
367
377
|
thread_id,
|
|
368
378
|
run_id,
|
|
@@ -420,8 +430,6 @@ class ThreadStorage(agui_package.ThreadStorage):
|
|
|
420
430
|
event: agui_core.Event,
|
|
421
431
|
) -> None:
|
|
422
432
|
"""Save a single event for a run (incremental persistence)"""
|
|
423
|
-
await self._session.commit()
|
|
424
|
-
|
|
425
433
|
async with self.session as session:
|
|
426
434
|
run = await self._find_thread_run(
|
|
427
435
|
user_name=user_name,
|
|
@@ -448,8 +456,6 @@ class ThreadStorage(agui_package.ThreadStorage):
|
|
|
448
456
|
run_id: str,
|
|
449
457
|
) -> None:
|
|
450
458
|
"""Mark a run as finished"""
|
|
451
|
-
await self._session.commit()
|
|
452
|
-
|
|
453
459
|
async with self.session as session:
|
|
454
460
|
run = await self._find_thread_run(
|
|
455
461
|
user_name=user_name,
|
|
@@ -475,8 +481,6 @@ class ThreadStorage(agui_package.ThreadStorage):
|
|
|
475
481
|
|
|
476
482
|
Each element is a (event_index, event) tuple.
|
|
477
483
|
"""
|
|
478
|
-
await self._session.commit()
|
|
479
|
-
|
|
480
484
|
async with self.session as session:
|
|
481
485
|
run = await self._find_thread_run(
|
|
482
486
|
user_name=user_name,
|
|
@@ -504,8 +508,6 @@ class ThreadStorage(agui_package.ThreadStorage):
|
|
|
504
508
|
run_id: str,
|
|
505
509
|
) -> bool:
|
|
506
510
|
"""Return True if the run has a 'finished' timestamp"""
|
|
507
|
-
await self._session.commit()
|
|
508
|
-
|
|
509
511
|
async with self.session as session:
|
|
510
512
|
run = await self._find_thread_run(
|
|
511
513
|
user_name=user_name,
|
|
@@ -524,11 +526,9 @@ class ThreadStorage(agui_package.ThreadStorage):
|
|
|
524
526
|
room_id: str,
|
|
525
527
|
thread_id: str,
|
|
526
528
|
run_id: str,
|
|
527
|
-
events:
|
|
528
|
-
) ->
|
|
529
|
+
events: agui.AGUI_Events,
|
|
530
|
+
) -> agui.AGUI_Events:
|
|
529
531
|
"""Save the events for a gven run"""
|
|
530
|
-
await self._session.commit()
|
|
531
|
-
|
|
532
532
|
async with self.session as session:
|
|
533
533
|
run = await self._find_thread_run(
|
|
534
534
|
user_name=user_name,
|
|
@@ -560,8 +560,6 @@ class ThreadStorage(agui_package.ThreadStorage):
|
|
|
560
560
|
tool_calls: int,
|
|
561
561
|
):
|
|
562
562
|
"""Save the run usage statistics"""
|
|
563
|
-
await self._session.commit()
|
|
564
|
-
|
|
565
563
|
async with self.session as session:
|
|
566
564
|
run = await self._find_thread_run(
|
|
567
565
|
user_name=user_name,
|
|
@@ -591,8 +589,6 @@ class ThreadStorage(agui_package.ThreadStorage):
|
|
|
591
589
|
reason: str,
|
|
592
590
|
):
|
|
593
591
|
"""Save the run feedback"""
|
|
594
|
-
await self._session.commit()
|
|
595
|
-
|
|
596
592
|
async with self.session as session:
|
|
597
593
|
run = await self._find_thread_run(
|
|
598
594
|
user_name=user_name,
|
|
@@ -624,10 +620,8 @@ class ThreadStorage(agui_package.ThreadStorage):
|
|
|
624
620
|
room_id: str,
|
|
625
621
|
thread_id: str,
|
|
626
622
|
run_id: str,
|
|
627
|
-
) ->
|
|
623
|
+
) -> agui.RunFeedbackType | None:
|
|
628
624
|
"""Get the run feedback"""
|
|
629
|
-
await self._session.commit()
|
|
630
|
-
|
|
631
625
|
async with self.session as session:
|
|
632
626
|
run = await self._find_thread_run(
|
|
633
627
|
user_name=user_name,
|
|
@@ -658,8 +652,6 @@ class ThreadStorage(agui_package.ThreadStorage):
|
|
|
658
652
|
if limit is None and since is None:
|
|
659
653
|
limit = 20
|
|
660
654
|
|
|
661
|
-
await self._session.commit()
|
|
662
|
-
|
|
663
655
|
async with self.session as session:
|
|
664
656
|
query = (
|
|
665
657
|
sqla_sql.Select(
|
|
@@ -869,3 +861,148 @@ class ThreadStorage(agui_package.ThreadStorage):
|
|
|
869
861
|
)
|
|
870
862
|
|
|
871
863
|
return history_entry
|
|
864
|
+
|
|
865
|
+
|
|
866
|
+
# --------------------------------------------------------------------------
|
|
867
|
+
# Engine-owning run-persistence helpers
|
|
868
|
+
#
|
|
869
|
+
# Each builds its own short-lived session (the one bound to a request's
|
|
870
|
+
# lifetime may already be closed, e.g. after an early connection reset) and
|
|
871
|
+
# owns its commit: 'ThreadStorage' methods no longer commit on their own.
|
|
872
|
+
# Committing per event is what lets a reconnecting client's poll observe
|
|
873
|
+
# events incrementally.
|
|
874
|
+
# --------------------------------------------------------------------------
|
|
875
|
+
|
|
876
|
+
|
|
877
|
+
async def capture_usage_after_stream(
|
|
878
|
+
result,
|
|
879
|
+
*,
|
|
880
|
+
sqla_engine,
|
|
881
|
+
user_name: str,
|
|
882
|
+
room_id: str,
|
|
883
|
+
thread_id: str,
|
|
884
|
+
run_id: str,
|
|
885
|
+
):
|
|
886
|
+
"""Save the run usage to the database."""
|
|
887
|
+
usage = getattr(result, "usage", None)
|
|
888
|
+
|
|
889
|
+
if usage is not None:
|
|
890
|
+
async with sqla_asyncio.AsyncSession(bind=sqla_engine) as session:
|
|
891
|
+
the_threads = ThreadStorage(session)
|
|
892
|
+
|
|
893
|
+
async with session.begin():
|
|
894
|
+
await the_threads.save_run_usage(
|
|
895
|
+
user_name=user_name,
|
|
896
|
+
room_id=room_id,
|
|
897
|
+
thread_id=thread_id,
|
|
898
|
+
run_id=run_id,
|
|
899
|
+
input_tokens=usage.input_tokens,
|
|
900
|
+
output_tokens=usage.output_tokens,
|
|
901
|
+
requests=usage.requests,
|
|
902
|
+
tool_calls=usage.tool_calls,
|
|
903
|
+
)
|
|
904
|
+
|
|
905
|
+
|
|
906
|
+
async def save_single_event(
|
|
907
|
+
sqla_engine,
|
|
908
|
+
user_name: str,
|
|
909
|
+
room_id: str,
|
|
910
|
+
thread_id: str,
|
|
911
|
+
run_id: str,
|
|
912
|
+
event,
|
|
913
|
+
):
|
|
914
|
+
"""Save a single run event to the database (incremental persistence)."""
|
|
915
|
+
async with sqla_asyncio.AsyncSession(bind=sqla_engine) as session:
|
|
916
|
+
the_threads = ThreadStorage(session)
|
|
917
|
+
|
|
918
|
+
async with session.begin():
|
|
919
|
+
await the_threads.save_single_event(
|
|
920
|
+
user_name=user_name,
|
|
921
|
+
room_id=room_id,
|
|
922
|
+
thread_id=thread_id,
|
|
923
|
+
run_id=run_id,
|
|
924
|
+
event=event,
|
|
925
|
+
)
|
|
926
|
+
|
|
927
|
+
|
|
928
|
+
async def finish_run(
|
|
929
|
+
sqla_engine,
|
|
930
|
+
user_name: str,
|
|
931
|
+
room_id: str,
|
|
932
|
+
thread_id: str,
|
|
933
|
+
run_id: str,
|
|
934
|
+
):
|
|
935
|
+
"""Mark a run as finished in the database."""
|
|
936
|
+
async with sqla_asyncio.AsyncSession(bind=sqla_engine) as session:
|
|
937
|
+
the_threads = ThreadStorage(session)
|
|
938
|
+
|
|
939
|
+
async with session.begin():
|
|
940
|
+
await the_threads.finish_run(
|
|
941
|
+
user_name=user_name,
|
|
942
|
+
room_id=room_id,
|
|
943
|
+
thread_id=thread_id,
|
|
944
|
+
run_id=run_id,
|
|
945
|
+
)
|
|
946
|
+
|
|
947
|
+
|
|
948
|
+
async def drive_agui_turn(
|
|
949
|
+
*,
|
|
950
|
+
adapter,
|
|
951
|
+
skill_toolset: hs_agent.SkillToolset | None,
|
|
952
|
+
engine,
|
|
953
|
+
user_name: str,
|
|
954
|
+
room_id: str,
|
|
955
|
+
thread_id: str,
|
|
956
|
+
run_id: str,
|
|
957
|
+
claims: dict | None = None,
|
|
958
|
+
rag_db_paths: dict[str, str] | None = None,
|
|
959
|
+
run_stream_kwargs: dict | None = None,
|
|
960
|
+
) -> collections.abc.AsyncIterator:
|
|
961
|
+
"""Drive one room-agent turn as an AG-UI event stream.
|
|
962
|
+
|
|
963
|
+
An async generator: for each event it records any RAG access (skill
|
|
964
|
+
retrievals surface as AG-UI activity events) and persists the event
|
|
965
|
+
(via :func:`save_single_event`), then yields it. Callers own
|
|
966
|
+
presentation (an SSE queue, or collecting the assistant text) and any
|
|
967
|
+
post-run work such as :func:`finish_run`, usage capture, or title
|
|
968
|
+
generation.
|
|
969
|
+
|
|
970
|
+
``run_stream_kwargs`` is forwarded verbatim to
|
|
971
|
+
``haiku.skills.agent.run_agui_stream`` (``deps``, ``conversation_id``,
|
|
972
|
+
``on_complete``).
|
|
973
|
+
"""
|
|
974
|
+
auditor = rag_audit.RagAccessAuditor(
|
|
975
|
+
loggers.RAGAccessAuditLog(
|
|
976
|
+
claims=claims or {},
|
|
977
|
+
room_id=room_id,
|
|
978
|
+
thread_id=thread_id,
|
|
979
|
+
run_id=run_id,
|
|
980
|
+
),
|
|
981
|
+
(rag_db_paths or {}).get,
|
|
982
|
+
)
|
|
983
|
+
|
|
984
|
+
async with hs_agent.run_agui_stream(
|
|
985
|
+
adapter,
|
|
986
|
+
toolset=skill_toolset,
|
|
987
|
+
**(run_stream_kwargs or {}),
|
|
988
|
+
) as event_stream:
|
|
989
|
+
compacted = agui.compact_event_stream(event_stream)
|
|
990
|
+
async for event in compacted:
|
|
991
|
+
auditor.handle(event)
|
|
992
|
+
|
|
993
|
+
try:
|
|
994
|
+
await save_single_event(
|
|
995
|
+
engine,
|
|
996
|
+
user_name=user_name,
|
|
997
|
+
room_id=room_id,
|
|
998
|
+
thread_id=thread_id,
|
|
999
|
+
run_id=run_id,
|
|
1000
|
+
event=event,
|
|
1001
|
+
)
|
|
1002
|
+
except sqla_exc.SQLAlchemyError as sa_exc:
|
|
1003
|
+
logfire.error(
|
|
1004
|
+
"Error saving event: {error_message}",
|
|
1005
|
+
error_message=str(sa_exc),
|
|
1006
|
+
)
|
|
1007
|
+
|
|
1008
|
+
yield event
|
|
@@ -12,7 +12,7 @@ from sqlalchemy import schema as sqla_schema
|
|
|
12
12
|
from sqlalchemy.ext import asyncio as sqla_asyncio
|
|
13
13
|
from sqlalchemy.sql import sqltypes as sqla_sqltypes
|
|
14
14
|
|
|
15
|
-
from soliplex import agui
|
|
15
|
+
from soliplex import agui
|
|
16
16
|
from soliplex import util
|
|
17
17
|
from soliplex.agui import util as agui_util
|
|
18
18
|
from soliplex.config import installation as config_installation
|
|
@@ -42,7 +42,7 @@ JSON_Mapped_From = dict[str, typing.Any]
|
|
|
42
42
|
|
|
43
43
|
metadata = sqla_schema.MetaData(naming_convention=NAMING_CONVENTION)
|
|
44
44
|
|
|
45
|
-
FeedbackReviewStatus =
|
|
45
|
+
FeedbackReviewStatus = agui.FeedbackReviewStatus
|
|
46
46
|
|
|
47
47
|
|
|
48
48
|
class Base(AsyncAttrs, DeclarativeBase):
|
|
@@ -447,8 +447,8 @@ class RunUsage(Base):
|
|
|
447
447
|
requests: Mapped[int] = mapped_column()
|
|
448
448
|
tool_calls: Mapped[int] = mapped_column()
|
|
449
449
|
|
|
450
|
-
def as_tuple(self) ->
|
|
451
|
-
return
|
|
450
|
+
def as_tuple(self) -> agui.RunUsageStats:
|
|
451
|
+
return agui.RunUsageStats(
|
|
452
452
|
self.input_tokens,
|
|
453
453
|
self.output_tokens,
|
|
454
454
|
self.requests,
|