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 CHANGED
@@ -4,5 +4,5 @@ jvserve package initialization.
4
4
  This package provides the webserver for loading and interacting with JIVAS agents.
5
5
  """
6
6
 
7
- __version__ = "2.0.9"
7
+ __version__ = "2.0.11"
8
8
  __supported__jivas__versions__ = ["2.0.0"]
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
- app.mount(
144
- "/files",
145
- StaticFiles(
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
- app.mount(
152
- "/files",
153
- StaticFiles(
154
- directory=os.environ.get("JIVAS_FILES_ROOT_PATH", ".files")
155
- ),
156
- name="files",
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)
@@ -6,13 +6,14 @@ import os
6
6
  import string
7
7
  import time
8
8
  import traceback
9
- from typing import Any, Dict, List, Optional
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
- ).response
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
- ctx.close()
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:
@@ -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")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: jvserve
3
- Version: 2.0.9
3
+ Version: 2.0.11
4
4
  Summary: FastAPI webserver for loading and interaction with JIVAS agents.
5
5
  Home-page: https://github.com/TrueSelph/jvserve
6
6
  Author: TrueSelph Inc.
@@ -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,,
@@ -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,,