hyperforge 1.0.0.post19__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.
- hyperforge/__init__.py +16 -0
- hyperforge/agent.py +81 -0
- hyperforge/api/__init__.py +20 -0
- hyperforge/api/app.py +155 -0
- hyperforge/api/authentication.py +271 -0
- hyperforge/api/commands.py +33 -0
- hyperforge/api/internal/__init__.py +4 -0
- hyperforge/api/internal/inspect.py +30 -0
- hyperforge/api/internal/router.py +3 -0
- hyperforge/api/logging.py +18 -0
- hyperforge/api/models.py +129 -0
- hyperforge/api/session.py +197 -0
- hyperforge/api/settings.py +38 -0
- hyperforge/api/utils.py +354 -0
- hyperforge/api/v1/__init__.py +23 -0
- hyperforge/api/v1/agents.py +531 -0
- hyperforge/api/v1/interaction.py +430 -0
- hyperforge/api/v1/mcp_content.py +311 -0
- hyperforge/api/v1/mcp_interaction.py +322 -0
- hyperforge/api/v1/oauth.py +60 -0
- hyperforge/api/v1/prompt.py +129 -0
- hyperforge/api/v1/router.py +3 -0
- hyperforge/api/v1/schema.py +56 -0
- hyperforge/api/v1/session.py +182 -0
- hyperforge/api/v1/utils.py +12 -0
- hyperforge/api/v1/workflows.py +643 -0
- hyperforge/arag.py +28 -0
- hyperforge/broker/__init__.py +52 -0
- hyperforge/broker/local.py +116 -0
- hyperforge/broker/redis.py +161 -0
- hyperforge/configure.py +571 -0
- hyperforge/context/__init__.py +0 -0
- hyperforge/context/agent.py +377 -0
- hyperforge/context/config.py +103 -0
- hyperforge/database.py +3 -0
- hyperforge/db/__init__.py +6 -0
- hyperforge/db/agents.py +1521 -0
- hyperforge/db/encryption.py +91 -0
- hyperforge/db/exceptions.py +26 -0
- hyperforge/db/settings.py +16 -0
- hyperforge/db/workflow_cleanup.py +69 -0
- hyperforge/definition.py +13 -0
- hyperforge/driver.py +31 -0
- hyperforge/dummy.py +28 -0
- hyperforge/engine.py +189 -0
- hyperforge/exceptions.py +14 -0
- hyperforge/feature_flag.py +105 -0
- hyperforge/fixtures.py +602 -0
- hyperforge/interaction.py +116 -0
- hyperforge/llm.py +75 -0
- hyperforge/manager.py +432 -0
- hyperforge/memory/__init__.py +5 -0
- hyperforge/memory/memory.py +974 -0
- hyperforge/minimal_fixtures.py +75 -0
- hyperforge/models.py +336 -0
- hyperforge/nua.py +336 -0
- hyperforge/openapi.py +63 -0
- hyperforge/prompts.py +188 -0
- hyperforge/pubsub.py +90 -0
- hyperforge/py.typed +0 -0
- hyperforge/redis_utils.py +82 -0
- hyperforge/retrieval/__init__.py +0 -0
- hyperforge/retrieval/agent.py +169 -0
- hyperforge/retrieval/config.py +94 -0
- hyperforge/server/__init__.py +5 -0
- hyperforge/server/cache.py +131 -0
- hyperforge/server/run.py +109 -0
- hyperforge/server/sandbox.py +60 -0
- hyperforge/server/session.py +421 -0
- hyperforge/server/settings.py +47 -0
- hyperforge/server/utils.py +57 -0
- hyperforge/server/web.py +31 -0
- hyperforge/settings.py +18 -0
- hyperforge/standalone/__init__.py +5 -0
- hyperforge/standalone/agent.py +189 -0
- hyperforge/standalone/app.py +264 -0
- hyperforge/standalone/config.py +137 -0
- hyperforge/standalone/const.py +1 -0
- hyperforge/standalone/run.py +60 -0
- hyperforge/standalone/settings.py +133 -0
- hyperforge/standalone/ui_router.py +241 -0
- hyperforge/trace.py +42 -0
- hyperforge/utils/__init__.py +112 -0
- hyperforge/utils/http.py +48 -0
- hyperforge/workflows.py +44 -0
- hyperforge-1.0.0.post19.dist-info/METADATA +95 -0
- hyperforge-1.0.0.post19.dist-info/RECORD +90 -0
- hyperforge-1.0.0.post19.dist-info/WHEEL +5 -0
- hyperforge-1.0.0.post19.dist-info/entry_points.txt +8 -0
- hyperforge-1.0.0.post19.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,430 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import uuid
|
|
3
|
+
from enum import Enum
|
|
4
|
+
from typing import TYPE_CHECKING, AsyncIterator, Optional
|
|
5
|
+
|
|
6
|
+
import opentelemetry.propagate
|
|
7
|
+
from fastapi import (
|
|
8
|
+
Header,
|
|
9
|
+
Query,
|
|
10
|
+
Request,
|
|
11
|
+
WebSocket,
|
|
12
|
+
WebSocketDisconnect,
|
|
13
|
+
)
|
|
14
|
+
from fastapi.responses import StreamingResponse
|
|
15
|
+
from nucliadb_sdk import NucliaDBAsync
|
|
16
|
+
from pydantic import ValidationError
|
|
17
|
+
|
|
18
|
+
from hyperforge import logger
|
|
19
|
+
from hyperforge.api.authentication import requires_one
|
|
20
|
+
from hyperforge.api.models import AgentRole, InteractionRequest
|
|
21
|
+
from hyperforge.api.session import create_session_resource, session_exists
|
|
22
|
+
from hyperforge.api.settings import Settings
|
|
23
|
+
from hyperforge.api.utils import agent_has_nucliadb_memory
|
|
24
|
+
from hyperforge.api.v1.router import router
|
|
25
|
+
from hyperforge.api.v1.utils import tracer
|
|
26
|
+
from hyperforge.broker import AgentTimeoutError
|
|
27
|
+
from hyperforge.db import exceptions
|
|
28
|
+
from hyperforge.db.agents import AgentManager
|
|
29
|
+
from hyperforge.interaction import (
|
|
30
|
+
AnswerOperation,
|
|
31
|
+
AragAnswer,
|
|
32
|
+
ARAGException,
|
|
33
|
+
)
|
|
34
|
+
from hyperforge.pubsub import (
|
|
35
|
+
AgentAnswer,
|
|
36
|
+
AgentDone,
|
|
37
|
+
AgentPing,
|
|
38
|
+
AgentToUserRequest,
|
|
39
|
+
OAuthRequest,
|
|
40
|
+
StartInteraction,
|
|
41
|
+
UserToAgentInteraction,
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
if TYPE_CHECKING:
|
|
45
|
+
from hyperforge.api.app import HTTPApplication
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
async def ensure_session_exists(
|
|
49
|
+
ndb: NucliaDBAsync,
|
|
50
|
+
agent_id: str,
|
|
51
|
+
session: str,
|
|
52
|
+
create_if_not_exists: bool,
|
|
53
|
+
) -> Optional[str]:
|
|
54
|
+
"""
|
|
55
|
+
Check if session exists and create it if needed.
|
|
56
|
+
|
|
57
|
+
Returns:
|
|
58
|
+
None if session exists or was created successfully.
|
|
59
|
+
Error message string if session doesn't exist and shouldn't be created.
|
|
60
|
+
"""
|
|
61
|
+
# Check if session exists
|
|
62
|
+
if await session_exists(ndb, agent_id, session):
|
|
63
|
+
return None
|
|
64
|
+
|
|
65
|
+
# Session doesn't exist
|
|
66
|
+
if not create_if_not_exists:
|
|
67
|
+
return f"Session '{session}' does not exist"
|
|
68
|
+
|
|
69
|
+
# Create the session
|
|
70
|
+
try:
|
|
71
|
+
await create_session_resource(
|
|
72
|
+
ndb=ndb,
|
|
73
|
+
agent_id=agent_id,
|
|
74
|
+
slug=session,
|
|
75
|
+
title=f"Session {session}",
|
|
76
|
+
summary="Auto-created session",
|
|
77
|
+
data="",
|
|
78
|
+
)
|
|
79
|
+
return None
|
|
80
|
+
except Exception as e:
|
|
81
|
+
logger.exception(f"Error creating session {session} for agent {agent_id}: {e}")
|
|
82
|
+
return f"Failed to create session: {str(e)}"
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
class Shutdown:
|
|
86
|
+
pass
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
class WebsocketReceiver:
|
|
90
|
+
class Expecting(str, Enum):
|
|
91
|
+
NOTHING = "nothing"
|
|
92
|
+
QUESTION = "question"
|
|
93
|
+
FEEDBACK = "feedback"
|
|
94
|
+
|
|
95
|
+
expecting: Expecting
|
|
96
|
+
websocket: WebSocket | None
|
|
97
|
+
queue: asyncio.Queue[InteractionRequest | UserToAgentInteraction | Shutdown]
|
|
98
|
+
|
|
99
|
+
def __init__(self, websocket: WebSocket | None):
|
|
100
|
+
self.expecting = WebsocketReceiver.Expecting.QUESTION
|
|
101
|
+
self.websocket = websocket
|
|
102
|
+
self.queue = asyncio.Queue(maxsize=1)
|
|
103
|
+
|
|
104
|
+
async def receive_question(self) -> InteractionRequest:
|
|
105
|
+
self.expecting = WebsocketReceiver.Expecting.QUESTION
|
|
106
|
+
msg = await self.queue.get()
|
|
107
|
+
if isinstance(msg, Shutdown):
|
|
108
|
+
raise WebSocketDisconnect()
|
|
109
|
+
if not isinstance(msg, InteractionRequest):
|
|
110
|
+
raise ValueError(f"Expected question but got {type(msg).__name__}")
|
|
111
|
+
return msg
|
|
112
|
+
|
|
113
|
+
async def receive_feedback(self) -> UserToAgentInteraction:
|
|
114
|
+
self.expecting = WebsocketReceiver.Expecting.FEEDBACK
|
|
115
|
+
msg = await self.queue.get()
|
|
116
|
+
if isinstance(msg, Shutdown):
|
|
117
|
+
raise WebSocketDisconnect()
|
|
118
|
+
if not isinstance(msg, UserToAgentInteraction):
|
|
119
|
+
raise ValueError(f"Expected feedback but got {type(msg).__name__}")
|
|
120
|
+
return msg
|
|
121
|
+
|
|
122
|
+
async def run(self):
|
|
123
|
+
if self.websocket is None:
|
|
124
|
+
raise ValueError("No websocket provided")
|
|
125
|
+
try:
|
|
126
|
+
async for message in self.websocket.iter_json():
|
|
127
|
+
match self.expecting:
|
|
128
|
+
case WebsocketReceiver.Expecting.NOTHING:
|
|
129
|
+
await self.websocket.send_json(
|
|
130
|
+
AragAnswer(
|
|
131
|
+
exception=ARAGException(
|
|
132
|
+
detail="Unexpected message from user"
|
|
133
|
+
),
|
|
134
|
+
operation=AnswerOperation.ERROR,
|
|
135
|
+
).model_dump()
|
|
136
|
+
)
|
|
137
|
+
case WebsocketReceiver.Expecting.QUESTION:
|
|
138
|
+
try:
|
|
139
|
+
await self.queue.put(
|
|
140
|
+
InteractionRequest.model_validate(message)
|
|
141
|
+
)
|
|
142
|
+
self.expecting = WebsocketReceiver.Expecting.NOTHING
|
|
143
|
+
except ValidationError as e:
|
|
144
|
+
await self.websocket.send_json(
|
|
145
|
+
AragAnswer(
|
|
146
|
+
exception=ARAGException(
|
|
147
|
+
detail="Invalid request payload",
|
|
148
|
+
extra={"validation_errors": e.errors()},
|
|
149
|
+
),
|
|
150
|
+
operation=AnswerOperation.ERROR,
|
|
151
|
+
).model_dump()
|
|
152
|
+
)
|
|
153
|
+
case WebsocketReceiver.Expecting.FEEDBACK:
|
|
154
|
+
try:
|
|
155
|
+
await self.queue.put(
|
|
156
|
+
UserToAgentInteraction.model_validate(message)
|
|
157
|
+
)
|
|
158
|
+
self.expecting = WebsocketReceiver.Expecting.NOTHING
|
|
159
|
+
except ValidationError as e:
|
|
160
|
+
await self.websocket.send_json(
|
|
161
|
+
AragAnswer(
|
|
162
|
+
exception=ARAGException(
|
|
163
|
+
detail="Invalid request payload",
|
|
164
|
+
extra={"validation_errors": e.errors()},
|
|
165
|
+
),
|
|
166
|
+
operation=AnswerOperation.ERROR,
|
|
167
|
+
).model_dump()
|
|
168
|
+
)
|
|
169
|
+
finally:
|
|
170
|
+
await self.queue.put(Shutdown())
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
async def stream_response(
|
|
174
|
+
app: "HTTPApplication",
|
|
175
|
+
websocket: Optional[WebsocketReceiver],
|
|
176
|
+
account: str,
|
|
177
|
+
agent_id: str,
|
|
178
|
+
session: str,
|
|
179
|
+
interaction: InteractionRequest,
|
|
180
|
+
workflow_id: str = "default",
|
|
181
|
+
) -> AsyncIterator[AragAnswer]:
|
|
182
|
+
settings: Settings = app.settings
|
|
183
|
+
agent_manager: AgentManager = app.agent_manager
|
|
184
|
+
|
|
185
|
+
try:
|
|
186
|
+
await agent_manager.ensure_workflow_active(account, agent_id, workflow_id)
|
|
187
|
+
except exceptions.NotFoundError as exc:
|
|
188
|
+
yield AragAnswer(
|
|
189
|
+
exception=ARAGException(detail=str(exc)),
|
|
190
|
+
operation=AnswerOperation.ERROR,
|
|
191
|
+
)
|
|
192
|
+
return
|
|
193
|
+
|
|
194
|
+
question_id = uuid.uuid4().hex
|
|
195
|
+
subject = settings.answers_subject.format(
|
|
196
|
+
account=account,
|
|
197
|
+
agent_id=agent_id,
|
|
198
|
+
session=session,
|
|
199
|
+
question=question_id,
|
|
200
|
+
workflow_id=workflow_id,
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
request = StartInteraction(
|
|
204
|
+
account=account,
|
|
205
|
+
agent_id=agent_id,
|
|
206
|
+
session=session,
|
|
207
|
+
question_id=question_id,
|
|
208
|
+
question=interaction.question,
|
|
209
|
+
headers=interaction.headers,
|
|
210
|
+
arguments=interaction.arguments,
|
|
211
|
+
workflow_id=workflow_id,
|
|
212
|
+
streaming=interaction.streaming,
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
with tracer().start_as_current_span("Request activation"):
|
|
216
|
+
trace_headers: dict[str, str] = {}
|
|
217
|
+
opentelemetry.propagate.inject(trace_headers)
|
|
218
|
+
await app.broker.publish_activation(request, trace_headers)
|
|
219
|
+
try:
|
|
220
|
+
async for _cursor, obj in app.broker.subscribe(subject):
|
|
221
|
+
if isinstance(obj, AgentAnswer):
|
|
222
|
+
yield obj.answer
|
|
223
|
+
elif isinstance(obj, OAuthRequest):
|
|
224
|
+
if websocket is None:
|
|
225
|
+
yield AragAnswer(
|
|
226
|
+
exception=ARAGException(
|
|
227
|
+
detail="Agent requires OAuth which is only supported via websocket"
|
|
228
|
+
),
|
|
229
|
+
operation=AnswerOperation.ERROR,
|
|
230
|
+
)
|
|
231
|
+
return
|
|
232
|
+
|
|
233
|
+
yield AragAnswer(
|
|
234
|
+
operation=AnswerOperation.AGENT_REQUEST, oauth=obj.oauth
|
|
235
|
+
)
|
|
236
|
+
elif isinstance(obj, AgentToUserRequest):
|
|
237
|
+
if websocket is None:
|
|
238
|
+
yield AragAnswer(
|
|
239
|
+
exception=ARAGException(
|
|
240
|
+
detail="Agent requires elicitation which is only supported via websocket"
|
|
241
|
+
),
|
|
242
|
+
operation=AnswerOperation.ERROR,
|
|
243
|
+
)
|
|
244
|
+
return
|
|
245
|
+
|
|
246
|
+
yield AragAnswer(
|
|
247
|
+
operation=AnswerOperation.AGENT_REQUEST, feedback=obj.feedback
|
|
248
|
+
)
|
|
249
|
+
try:
|
|
250
|
+
user_response = await websocket.receive_feedback()
|
|
251
|
+
except ValueError as e:
|
|
252
|
+
# Wrong message type received
|
|
253
|
+
yield AragAnswer(
|
|
254
|
+
exception=ARAGException(detail=f"Unexpected message: {str(e)}"),
|
|
255
|
+
operation=AnswerOperation.ERROR,
|
|
256
|
+
)
|
|
257
|
+
return
|
|
258
|
+
except WebSocketDisconnect:
|
|
259
|
+
return
|
|
260
|
+
await app.broker.send_reply(
|
|
261
|
+
obj.feedback.feedback_id, user_response.model_dump_json()
|
|
262
|
+
)
|
|
263
|
+
elif isinstance(obj, AgentDone):
|
|
264
|
+
yield AragAnswer(operation=AnswerOperation.DONE)
|
|
265
|
+
break
|
|
266
|
+
elif isinstance(obj, AgentPing):
|
|
267
|
+
pass
|
|
268
|
+
elif isinstance(obj, UserToAgentInteraction):
|
|
269
|
+
# TODO: Stream the user response for full history when resuming
|
|
270
|
+
pass
|
|
271
|
+
else:
|
|
272
|
+
yield AragAnswer(
|
|
273
|
+
exception=ARAGException(detail="Unknown message from agent"),
|
|
274
|
+
operation=AnswerOperation.ERROR,
|
|
275
|
+
)
|
|
276
|
+
raise Exception("Unknown message from agent")
|
|
277
|
+
except AgentTimeoutError:
|
|
278
|
+
yield AragAnswer(
|
|
279
|
+
exception=ARAGException(
|
|
280
|
+
detail="Agent has stopped responding. Please, try again."
|
|
281
|
+
),
|
|
282
|
+
operation=AnswerOperation.ERROR,
|
|
283
|
+
)
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
async def close_websocket_with_error(
|
|
287
|
+
websocket: WebSocket, task: asyncio.Task, error_message: str
|
|
288
|
+
):
|
|
289
|
+
"""Send error message and close websocket connection."""
|
|
290
|
+
try:
|
|
291
|
+
await websocket.send_json(
|
|
292
|
+
AragAnswer(
|
|
293
|
+
exception=ARAGException(detail=error_message),
|
|
294
|
+
operation=AnswerOperation.ERROR,
|
|
295
|
+
).model_dump()
|
|
296
|
+
)
|
|
297
|
+
except (RuntimeError, WebSocketDisconnect):
|
|
298
|
+
pass
|
|
299
|
+
finally:
|
|
300
|
+
task.cancel()
|
|
301
|
+
try:
|
|
302
|
+
await websocket.close()
|
|
303
|
+
except RuntimeError:
|
|
304
|
+
pass
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
@router.websocket("/api/v1/agent/{agent_id}/session/{session}/ws")
|
|
308
|
+
@router.websocket(
|
|
309
|
+
"/api/v1/agent/{agent_id}/workflow/{workflow_id}/session/{session}/ws"
|
|
310
|
+
)
|
|
311
|
+
@requires_one([AgentRole.MEMBER])
|
|
312
|
+
async def websocket_endpoint(
|
|
313
|
+
websocket: WebSocket,
|
|
314
|
+
agent_id: str,
|
|
315
|
+
session: str,
|
|
316
|
+
keep_open: bool = False,
|
|
317
|
+
workflow_id: str = "default",
|
|
318
|
+
create_session_if_not_exists: bool = Query(True),
|
|
319
|
+
x_stf_user: str = Header(..., include_in_schema=False),
|
|
320
|
+
x_stf_account: str = Header(..., include_in_schema=False),
|
|
321
|
+
x_stf_account_type: str = Header(..., include_in_schema=False),
|
|
322
|
+
):
|
|
323
|
+
await websocket.accept()
|
|
324
|
+
receiver = WebsocketReceiver(websocket)
|
|
325
|
+
task = asyncio.create_task(receiver.run())
|
|
326
|
+
|
|
327
|
+
# Validate session if agent uses NucliaDB memory (skip for ephemeral sessions)
|
|
328
|
+
agent_manager: AgentManager = websocket.app.agent_manager
|
|
329
|
+
try:
|
|
330
|
+
await agent_manager.ensure_workflow_active(x_stf_account, agent_id, workflow_id)
|
|
331
|
+
if session != "ephemeral" and await agent_has_nucliadb_memory(
|
|
332
|
+
agent_manager, x_stf_account, agent_id, workflow_id
|
|
333
|
+
):
|
|
334
|
+
ndb: NucliaDBAsync = websocket.app.arag_reader
|
|
335
|
+
error_message = await ensure_session_exists(
|
|
336
|
+
ndb, agent_id, session, create_session_if_not_exists
|
|
337
|
+
)
|
|
338
|
+
if error_message:
|
|
339
|
+
await close_websocket_with_error(websocket, task, error_message)
|
|
340
|
+
return
|
|
341
|
+
except Exception as e:
|
|
342
|
+
logger.exception(f"Error checking agent memory config: {e}")
|
|
343
|
+
await close_websocket_with_error(
|
|
344
|
+
websocket, task, f"Failed to verify agent configuration: {str(e)}"
|
|
345
|
+
)
|
|
346
|
+
return
|
|
347
|
+
|
|
348
|
+
# Wait for questions
|
|
349
|
+
first_question = True
|
|
350
|
+
while True:
|
|
351
|
+
if not keep_open and not first_question:
|
|
352
|
+
break
|
|
353
|
+
try:
|
|
354
|
+
interaction = await receiver.receive_question()
|
|
355
|
+
except WebSocketDisconnect:
|
|
356
|
+
break
|
|
357
|
+
except ValueError as e:
|
|
358
|
+
# Wrong message type received
|
|
359
|
+
await websocket.send_json(
|
|
360
|
+
AragAnswer(
|
|
361
|
+
exception=ARAGException(detail=f"Unexpected message: {str(e)}"),
|
|
362
|
+
operation=AnswerOperation.ERROR,
|
|
363
|
+
).model_dump()
|
|
364
|
+
)
|
|
365
|
+
break
|
|
366
|
+
|
|
367
|
+
first_question = False
|
|
368
|
+
for header, header_value in websocket.headers.items():
|
|
369
|
+
interaction.headers[header] = header_value
|
|
370
|
+
|
|
371
|
+
async for msg in stream_response(
|
|
372
|
+
websocket.app,
|
|
373
|
+
receiver,
|
|
374
|
+
x_stf_account,
|
|
375
|
+
agent_id,
|
|
376
|
+
session,
|
|
377
|
+
interaction,
|
|
378
|
+
workflow_id=workflow_id,
|
|
379
|
+
):
|
|
380
|
+
try:
|
|
381
|
+
await websocket.send_text(msg.model_dump_json())
|
|
382
|
+
except (RuntimeError, WebSocketDisconnect):
|
|
383
|
+
# WebSocket already closed
|
|
384
|
+
pass
|
|
385
|
+
|
|
386
|
+
try:
|
|
387
|
+
task.cancel()
|
|
388
|
+
await websocket.close()
|
|
389
|
+
except RuntimeError:
|
|
390
|
+
# WebSocket already closed
|
|
391
|
+
pass
|
|
392
|
+
|
|
393
|
+
|
|
394
|
+
@router.post(
|
|
395
|
+
"/api/v1/agent/{agent_id}/session/{session}",
|
|
396
|
+
status_code=200,
|
|
397
|
+
description="Interact session",
|
|
398
|
+
tags=["Retrieval Agent"],
|
|
399
|
+
)
|
|
400
|
+
@router.post(
|
|
401
|
+
"/api/v1/agent/{agent_id}/workflow/{workflow_id}/session/{session}",
|
|
402
|
+
status_code=200,
|
|
403
|
+
description="Interact session",
|
|
404
|
+
tags=["Retrieval Agent"],
|
|
405
|
+
)
|
|
406
|
+
@requires_one([AgentRole.MEMBER])
|
|
407
|
+
async def interaction(
|
|
408
|
+
request: Request,
|
|
409
|
+
agent_id: str,
|
|
410
|
+
session: str,
|
|
411
|
+
item: InteractionRequest,
|
|
412
|
+
workflow_id: str = "default",
|
|
413
|
+
x_stf_user: str = Header(..., include_in_schema=False),
|
|
414
|
+
x_stf_account: str = Header(..., include_in_schema=False),
|
|
415
|
+
x_stf_account_type: str = Header(..., include_in_schema=False),
|
|
416
|
+
):
|
|
417
|
+
async def responder():
|
|
418
|
+
async for msg in stream_response(
|
|
419
|
+
request.app,
|
|
420
|
+
None,
|
|
421
|
+
x_stf_account,
|
|
422
|
+
agent_id,
|
|
423
|
+
session,
|
|
424
|
+
item,
|
|
425
|
+
workflow_id=workflow_id,
|
|
426
|
+
):
|
|
427
|
+
yield msg.model_dump_json() + "\n"
|
|
428
|
+
|
|
429
|
+
# subscribe to
|
|
430
|
+
return StreamingResponse(responder(), media_type="application/x-ndjson")
|