jvserve 2.0.9__py3-none-any.whl → 2.0.11__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.
Potentially problematic release.
This version of jvserve might be problematic. Click here for more details.
- jvserve/__init__.py +1 -1
- jvserve/cli.py +100 -20
- jvserve/lib/agent_interface.py +95 -7
- jvserve/lib/file_interface.py +4 -0
- {jvserve-2.0.9.dist-info → jvserve-2.0.11.dist-info}/METADATA +1 -1
- jvserve-2.0.11.dist-info/RECORD +13 -0
- jvserve-2.0.9.dist-info/RECORD +0 -13
- {jvserve-2.0.9.dist-info → jvserve-2.0.11.dist-info}/WHEEL +0 -0
- {jvserve-2.0.9.dist-info → jvserve-2.0.11.dist-info}/entry_points.txt +0 -0
- {jvserve-2.0.9.dist-info → jvserve-2.0.11.dist-info}/licenses/LICENSE +0 -0
- {jvserve-2.0.9.dist-info → jvserve-2.0.11.dist-info}/top_level.txt +0 -0
jvserve/__init__.py
CHANGED
jvserve/cli.py
CHANGED
|
@@ -7,20 +7,80 @@ from contextlib import asynccontextmanager
|
|
|
7
7
|
from typing import AsyncIterator, Optional
|
|
8
8
|
|
|
9
9
|
from dotenv import load_dotenv
|
|
10
|
+
from fastapi.responses import FileResponse, StreamingResponse
|
|
10
11
|
from jac_cloud.jaseci.security import authenticator
|
|
12
|
+
from jac_cloud.plugin.jaseci import NodeAnchor
|
|
11
13
|
from jaclang.cli.cmdreg import cmd_registry
|
|
12
14
|
from jaclang.plugin.default import hookimpl
|
|
13
15
|
from jaclang.runtimelib.context import ExecutionContext
|
|
14
16
|
from jaclang.runtimelib.machine import JacMachine
|
|
17
|
+
from requests import Response
|
|
15
18
|
from uvicorn import run as _run
|
|
16
19
|
|
|
17
20
|
from jvserve.lib.agent_interface import AgentInterface
|
|
18
21
|
from jvserve.lib.agent_pulse import AgentPulse
|
|
22
|
+
from jvserve.lib.file_interface import (
|
|
23
|
+
DEFAULT_FILES_ROOT,
|
|
24
|
+
FILE_INTERFACE,
|
|
25
|
+
file_interface,
|
|
26
|
+
)
|
|
19
27
|
from jvserve.lib.jvlogger import JVLogger
|
|
20
28
|
|
|
21
29
|
load_dotenv(".env")
|
|
22
30
|
|
|
23
31
|
|
|
32
|
+
def serve_proxied_file(file_path: str) -> FileResponse | StreamingResponse:
|
|
33
|
+
"""Serve a proxied file from a remote or local URL."""
|
|
34
|
+
import mimetypes
|
|
35
|
+
|
|
36
|
+
import requests
|
|
37
|
+
from fastapi import HTTPException
|
|
38
|
+
|
|
39
|
+
if FILE_INTERFACE == "local":
|
|
40
|
+
return FileResponse(path=os.path.join(DEFAULT_FILES_ROOT, file_path))
|
|
41
|
+
|
|
42
|
+
file_url = file_interface.get_file_url(file_path)
|
|
43
|
+
|
|
44
|
+
if file_url and ("localhost" in file_url or "127.0.0.1" in file_url):
|
|
45
|
+
# prevent recusive calls when env vars are not detected
|
|
46
|
+
raise HTTPException(status_code=500, detail="Environment not set up correctly")
|
|
47
|
+
|
|
48
|
+
if not file_url:
|
|
49
|
+
raise HTTPException(status_code=404, detail="File not found")
|
|
50
|
+
|
|
51
|
+
file_extension = os.path.splitext(file_path)[1].lower()
|
|
52
|
+
|
|
53
|
+
# List of extensions to serve directly
|
|
54
|
+
direct_serve_extensions = [
|
|
55
|
+
".pdf",
|
|
56
|
+
".html",
|
|
57
|
+
".txt",
|
|
58
|
+
".js",
|
|
59
|
+
".css",
|
|
60
|
+
".json",
|
|
61
|
+
".xml",
|
|
62
|
+
".svg",
|
|
63
|
+
".csv",
|
|
64
|
+
".ico",
|
|
65
|
+
]
|
|
66
|
+
|
|
67
|
+
if file_extension in direct_serve_extensions:
|
|
68
|
+
file_response = requests.get(file_url)
|
|
69
|
+
file_response.raise_for_status() # Raise HTTPError for bad responses (4XX or 5XX)
|
|
70
|
+
|
|
71
|
+
mime_type = mimetypes.guess_type(file_path)[0] or "application/octet-stream"
|
|
72
|
+
|
|
73
|
+
return StreamingResponse(iter([file_response.content]), media_type=mime_type)
|
|
74
|
+
|
|
75
|
+
file_response = requests.get(file_url, stream=True)
|
|
76
|
+
file_response.raise_for_status()
|
|
77
|
+
|
|
78
|
+
return StreamingResponse(
|
|
79
|
+
file_response.iter_content(chunk_size=1024),
|
|
80
|
+
media_type="application/octet-stream",
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
|
|
24
84
|
class JacCmd:
|
|
25
85
|
"""Jac CLI."""
|
|
26
86
|
|
|
@@ -122,12 +182,6 @@ class JacCmd:
|
|
|
122
182
|
from fastapi.middleware.cors import CORSMiddleware
|
|
123
183
|
from fastapi.staticfiles import StaticFiles
|
|
124
184
|
|
|
125
|
-
if directory:
|
|
126
|
-
os.environ["JIVAS_FILES_ROOT_PATH"] = directory
|
|
127
|
-
|
|
128
|
-
if not os.path.exists(directory):
|
|
129
|
-
os.makedirs(directory)
|
|
130
|
-
|
|
131
185
|
# Setup custom routes
|
|
132
186
|
app = FastAPI()
|
|
133
187
|
|
|
@@ -140,21 +194,47 @@ class JacCmd:
|
|
|
140
194
|
allow_headers=["*"],
|
|
141
195
|
)
|
|
142
196
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
directory=os.environ.get("JIVAS_FILES_ROOT_PATH", ".files")
|
|
147
|
-
),
|
|
148
|
-
name="files",
|
|
149
|
-
)
|
|
197
|
+
if FILE_INTERFACE == "local":
|
|
198
|
+
if directory:
|
|
199
|
+
os.environ["JIVAS_FILES_ROOT_PATH"] = directory
|
|
150
200
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
201
|
+
if not os.path.exists(directory):
|
|
202
|
+
os.makedirs(directory)
|
|
203
|
+
|
|
204
|
+
app.mount(
|
|
205
|
+
"/files",
|
|
206
|
+
StaticFiles(
|
|
207
|
+
directory=os.environ.get("JIVAS_FILES_ROOT_PATH", ".files")
|
|
208
|
+
),
|
|
209
|
+
name="files",
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
if FILE_INTERFACE == "s3":
|
|
213
|
+
|
|
214
|
+
@app.get("/files/{file_path:path}", response_model=None)
|
|
215
|
+
async def serve_file(
|
|
216
|
+
file_path: str,
|
|
217
|
+
) -> Response:
|
|
218
|
+
return serve_proxied_file(file_path)
|
|
219
|
+
|
|
220
|
+
@app.get("/f/{file_id:path}", response_model=None)
|
|
221
|
+
async def get_proxied_file(
|
|
222
|
+
file_id: str,
|
|
223
|
+
) -> Response:
|
|
224
|
+
from bson import ObjectId
|
|
225
|
+
from fastapi import HTTPException
|
|
226
|
+
|
|
227
|
+
params = file_id.split("/")
|
|
228
|
+
object_id = params[0]
|
|
229
|
+
|
|
230
|
+
# mongo db collection
|
|
231
|
+
collection = NodeAnchor.Collection.get_collection("url_proxies")
|
|
232
|
+
file_details = collection.find_one({"_id": ObjectId(object_id)})
|
|
233
|
+
|
|
234
|
+
if file_details:
|
|
235
|
+
return serve_proxied_file(file_details["path"])
|
|
236
|
+
|
|
237
|
+
raise HTTPException(status_code=404, detail="File not found")
|
|
158
238
|
|
|
159
239
|
# run the app
|
|
160
240
|
_run(app, host=host, port=port)
|
jvserve/lib/agent_interface.py
CHANGED
|
@@ -6,13 +6,14 @@ import os
|
|
|
6
6
|
import string
|
|
7
7
|
import time
|
|
8
8
|
import traceback
|
|
9
|
-
from
|
|
9
|
+
from asyncio import sleep
|
|
10
|
+
from typing import Any, AsyncGenerator, Dict, Iterator, List, Optional
|
|
10
11
|
from urllib.parse import quote, unquote
|
|
11
12
|
|
|
12
13
|
import aiohttp
|
|
13
14
|
import requests
|
|
14
15
|
from fastapi import File, Form, Request, UploadFile
|
|
15
|
-
from fastapi.responses import JSONResponse
|
|
16
|
+
from fastapi.responses import JSONResponse, StreamingResponse
|
|
16
17
|
from jac_cloud.core.architype import AnchorState, Permission, Root
|
|
17
18
|
from jac_cloud.core.context import (
|
|
18
19
|
JASECI_CONTEXT,
|
|
@@ -244,7 +245,6 @@ class AgentInterface:
|
|
|
244
245
|
@staticmethod
|
|
245
246
|
def interact(payload: InteractPayload) -> dict:
|
|
246
247
|
"""Interact with the agent."""
|
|
247
|
-
|
|
248
248
|
response = None
|
|
249
249
|
ctx = AgentInterface.load_context()
|
|
250
250
|
session_id = payload.session_id if payload.session_id else ""
|
|
@@ -274,15 +274,103 @@ class AgentInterface:
|
|
|
274
274
|
},
|
|
275
275
|
module_name="jivas.agent.action.interact",
|
|
276
276
|
),
|
|
277
|
-
)
|
|
277
|
+
)
|
|
278
|
+
|
|
279
|
+
if payload.streaming:
|
|
280
|
+
# since streaming occurs asynchronously, we'll need to close the context for writebacks here
|
|
281
|
+
# at this point of closure, there will be an open interaction_node without a response
|
|
282
|
+
# our job hereafter is to stream to completion and then update and close this interaction_node with the final result
|
|
283
|
+
|
|
284
|
+
ctx.close()
|
|
285
|
+
if (
|
|
286
|
+
response is not None
|
|
287
|
+
and hasattr(response, "generator")
|
|
288
|
+
and hasattr(response, "interaction_node")
|
|
289
|
+
):
|
|
290
|
+
|
|
291
|
+
interaction_node = response.interaction_node
|
|
292
|
+
|
|
293
|
+
async def generate(
|
|
294
|
+
generator: Iterator,
|
|
295
|
+
) -> AsyncGenerator[str, None]:
|
|
296
|
+
"""
|
|
297
|
+
Asynchronously yield data chunks from a response generator in Server-Sent Events (SSE) format.
|
|
298
|
+
|
|
299
|
+
Accumulates the full text content and yields each chunk as a JSON-encoded SSE message.
|
|
300
|
+
After all chunks are processed, updates the interaction node with the complete generated text and triggers an update in the graph context.
|
|
301
|
+
|
|
302
|
+
Yields:
|
|
303
|
+
str: A JSON-encoded string representing the current chunk of data in SSE format.
|
|
304
|
+
"""
|
|
305
|
+
full_text = ""
|
|
306
|
+
total_tokens = 0
|
|
307
|
+
try:
|
|
308
|
+
for chunk in generator:
|
|
309
|
+
full_text += chunk.content
|
|
310
|
+
total_tokens += 1 # each chunk is a token, let's tally
|
|
311
|
+
await sleep(0.025)
|
|
312
|
+
yield (
|
|
313
|
+
"data: "
|
|
314
|
+
+ json.dumps(
|
|
315
|
+
{
|
|
316
|
+
"id": interaction_node.id,
|
|
317
|
+
"content": chunk.content,
|
|
318
|
+
"session_id": interaction_node.response.get(
|
|
319
|
+
"session_id"
|
|
320
|
+
),
|
|
321
|
+
"type": chunk.type,
|
|
322
|
+
"metadata": chunk.response_metadata,
|
|
323
|
+
}
|
|
324
|
+
)
|
|
325
|
+
+ "\n\n"
|
|
326
|
+
)
|
|
327
|
+
# Update the interaction node with the fully generated text
|
|
328
|
+
actx = await AgentInterface.load_context_async()
|
|
329
|
+
try:
|
|
330
|
+
interaction_node.set_text_message(message=full_text)
|
|
331
|
+
interaction_node.add_tokens(total_tokens)
|
|
332
|
+
_Jac.spawn_call(
|
|
333
|
+
NodeAnchor.ref(interaction_node.id).architype,
|
|
334
|
+
AgentInterface.spawn_walker(
|
|
335
|
+
walker_name="update_interaction",
|
|
336
|
+
attributes={
|
|
337
|
+
"interaction_data": interaction_node.export(),
|
|
338
|
+
},
|
|
339
|
+
module_name="jivas.agent.action.update_interaction",
|
|
340
|
+
),
|
|
341
|
+
)
|
|
342
|
+
finally:
|
|
343
|
+
if actx:
|
|
344
|
+
actx.close()
|
|
345
|
+
|
|
346
|
+
except Exception as e:
|
|
347
|
+
AgentInterface.LOGGER.error(
|
|
348
|
+
f"Exception in streaming generator: {e}, {traceback.format_exc()}"
|
|
349
|
+
)
|
|
350
|
+
|
|
351
|
+
return StreamingResponse(
|
|
352
|
+
generate(response.generator),
|
|
353
|
+
media_type="text/event-stream",
|
|
354
|
+
)
|
|
355
|
+
|
|
356
|
+
else:
|
|
357
|
+
AgentInterface.LOGGER.error(
|
|
358
|
+
"Response is None or missing required attributes for streaming."
|
|
359
|
+
)
|
|
360
|
+
return {}
|
|
361
|
+
|
|
362
|
+
else:
|
|
363
|
+
response = response.response
|
|
364
|
+
ctx.close()
|
|
365
|
+
return response if response else {}
|
|
366
|
+
|
|
278
367
|
except Exception as e:
|
|
279
368
|
AgentInterface.EXPIRATION = None
|
|
280
369
|
AgentInterface.LOGGER.error(
|
|
281
370
|
f"an exception occurred: {e}, {traceback.format_exc()}"
|
|
282
371
|
)
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
return response if response else {}
|
|
372
|
+
ctx.close()
|
|
373
|
+
return {}
|
|
286
374
|
|
|
287
375
|
@staticmethod
|
|
288
376
|
def pulse(action_label: str, agent_id: str = "") -> dict:
|
jvserve/lib/file_interface.py
CHANGED
|
@@ -7,6 +7,10 @@ import logging
|
|
|
7
7
|
import os
|
|
8
8
|
from abc import ABC, abstractmethod
|
|
9
9
|
|
|
10
|
+
from dotenv import load_dotenv
|
|
11
|
+
|
|
12
|
+
load_dotenv(".env")
|
|
13
|
+
|
|
10
14
|
# Interface type determined by environment variable, defaults to local
|
|
11
15
|
FILE_INTERFACE = os.environ.get("JIVAS_FILE_INTERFACE", "local")
|
|
12
16
|
DEFAULT_FILES_ROOT = os.environ.get("JIVAS_FILES_ROOT_PATH", ".files")
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
jvserve/__init__.py,sha256=sJ_mBKN30rjktWYox9bigWcSsNjhSIbEq-EWFvhYAAc,191
|
|
2
|
+
jvserve/cli.py,sha256=ptbu2_u6fZHXyIPPYRt5xKJL_l0OVhq8hETGkAGuvqk,7699
|
|
3
|
+
jvserve/lib/__init__.py,sha256=cnzfSHLoTWG9Ygut2nOpDys5aPlQz-m0BSkB-nd7OMs,31
|
|
4
|
+
jvserve/lib/agent_interface.py,sha256=vGJQ9A_9kWfa6YoW7BiidZO6Huf-0XSlunMLMNGqgws,31053
|
|
5
|
+
jvserve/lib/agent_pulse.py,sha256=6hBF6KQYr6Z9Mi_yoWKGfdnW7gg84kK20Slu-bLR_m8,2067
|
|
6
|
+
jvserve/lib/file_interface.py,sha256=sqwTmBnZsVFQUGt7mE1EWA-jHnZlRtBAFeb4Rb_QgQ8,6079
|
|
7
|
+
jvserve/lib/jvlogger.py,sha256=RNiB9PHuBzTvNIQWhxoDgrDlNYA0PYm1SVpvzlqu8mE,4180
|
|
8
|
+
jvserve-2.0.11.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
|
9
|
+
jvserve-2.0.11.dist-info/METADATA,sha256=avBB2xIYh266s2UxgR9MF3hVK4dUzWBxgYPJr0Fn2qc,4791
|
|
10
|
+
jvserve-2.0.11.dist-info/WHEEL,sha256=Nw36Djuh_5VDukK0H78QzOX-_FQEo6V37m3nkm96gtU,91
|
|
11
|
+
jvserve-2.0.11.dist-info/entry_points.txt,sha256=HYyg1QXoLs0JRb004L300VeLOZyDLY27ynD1tnTnEN4,35
|
|
12
|
+
jvserve-2.0.11.dist-info/top_level.txt,sha256=afoCXZv-zXNBuhVIvfJGjafXKEiJl_ooy4BtgQwAG4Q,8
|
|
13
|
+
jvserve-2.0.11.dist-info/RECORD,,
|
jvserve-2.0.9.dist-info/RECORD
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
jvserve/__init__.py,sha256=nxazVqbIeC0Orvk372cl5arEJiKYDj0AmAecgNpgAXQ,190
|
|
2
|
-
jvserve/cli.py,sha256=ltg-n-ZFsvitAGIhxY8xgwPI2CETLanZ_0bqrvsqqg8,4994
|
|
3
|
-
jvserve/lib/__init__.py,sha256=cnzfSHLoTWG9Ygut2nOpDys5aPlQz-m0BSkB-nd7OMs,31
|
|
4
|
-
jvserve/lib/agent_interface.py,sha256=zPaF8Yz3V36BJ5BulqwYwBSiToVligVXoKk8R0x8xRQ,26552
|
|
5
|
-
jvserve/lib/agent_pulse.py,sha256=6hBF6KQYr6Z9Mi_yoWKGfdnW7gg84kK20Slu-bLR_m8,2067
|
|
6
|
-
jvserve/lib/file_interface.py,sha256=ccnMw5k1NtfK5ylNcvlCJE-Ene3tZigh6Q1gHRy7Sow,6026
|
|
7
|
-
jvserve/lib/jvlogger.py,sha256=RNiB9PHuBzTvNIQWhxoDgrDlNYA0PYm1SVpvzlqu8mE,4180
|
|
8
|
-
jvserve-2.0.9.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
|
9
|
-
jvserve-2.0.9.dist-info/METADATA,sha256=yhKfhNnOZJGO1zJGQ7Q5sE7vQDXPXIYvC_cApjRPuoQ,4790
|
|
10
|
-
jvserve-2.0.9.dist-info/WHEEL,sha256=Nw36Djuh_5VDukK0H78QzOX-_FQEo6V37m3nkm96gtU,91
|
|
11
|
-
jvserve-2.0.9.dist-info/entry_points.txt,sha256=HYyg1QXoLs0JRb004L300VeLOZyDLY27ynD1tnTnEN4,35
|
|
12
|
-
jvserve-2.0.9.dist-info/top_level.txt,sha256=afoCXZv-zXNBuhVIvfJGjafXKEiJl_ooy4BtgQwAG4Q,8
|
|
13
|
-
jvserve-2.0.9.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|