jaf-py 2.5.8__py3-none-any.whl → 2.5.10__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.
jaf/__init__.py CHANGED
@@ -191,7 +191,7 @@ def generate_run_id() -> RunId:
191
191
  """Generate a new run ID."""
192
192
  return create_run_id(str(uuid.uuid4()))
193
193
 
194
- __version__ = "2.5.8"
194
+ __version__ = "2.5.10"
195
195
  __all__ = [
196
196
  # Core types and functions
197
197
  "TraceId", "RunId", "ValidationResult", "Message", "ModelConfig",
jaf/core/tracing.py CHANGED
@@ -443,7 +443,7 @@ class LangfuseTraceCollector:
443
443
  public_key=public_key,
444
444
  secret_key=secret_key,
445
445
  host=host,
446
- release="jaf-py-v2.5.8",
446
+ release="jaf-py-v2.5.10",
447
447
  httpx_client=client
448
448
  )
449
449
  self._httpx_client = client
jaf/providers/model.py CHANGED
@@ -6,13 +6,12 @@ starting with LiteLLM for multi-provider support.
6
6
  """
7
7
 
8
8
  from typing import Any, Dict, Optional, TypeVar, AsyncIterator
9
- import asyncio
10
9
  import httpx
11
10
  import time
12
11
  import os
13
12
  import base64
14
13
 
15
- from openai import OpenAI
14
+ from openai import AsyncOpenAI
16
15
  from pydantic import BaseModel
17
16
  import litellm
18
17
 
@@ -144,13 +143,13 @@ def make_litellm_provider(
144
143
  # Use the https proxy if available, otherwise http proxy
145
144
  proxy_url = proxies.get('https://') or proxies.get('http://')
146
145
  if proxy_url:
147
- http_client = httpx.Client(proxy=proxy_url)
146
+ http_client = httpx.AsyncClient(proxy=proxy_url)
148
147
  client_kwargs["http_client"] = http_client
149
148
  except Exception as e:
150
149
  print(f"Warning: Could not configure proxy: {e}")
151
150
  # Fall back to environment variables for proxy
152
151
 
153
- self.client = OpenAI(**client_kwargs)
152
+ self.client = AsyncOpenAI(**client_kwargs)
154
153
  self.default_timeout = default_timeout
155
154
 
156
155
  async def get_completion(
@@ -238,7 +237,7 @@ def make_litellm_provider(
238
237
  request_params["response_format"] = {"type": "json_object"}
239
238
 
240
239
  # Make the API call
241
- response = self.client.chat.completions.create(**request_params)
240
+ response = await self.client.chat.completions.create(**request_params)
242
241
 
243
242
  # Return in the expected format that the engine expects
244
243
  choice = response.choices[0]
@@ -351,99 +350,68 @@ def make_litellm_provider(
351
350
  # Enable streaming
352
351
  request_params["stream"] = True
353
352
 
354
- loop = asyncio.get_running_loop()
355
- queue: asyncio.Queue = asyncio.Queue(maxsize=256)
356
- SENTINEL = object()
353
+ # Use async streaming directly with AsyncOpenAI
354
+ stream = await self.client.chat.completions.create(**request_params)
357
355
 
358
- def _put(item: CompletionStreamChunk):
356
+ async for chunk in stream:
359
357
  try:
360
- asyncio.run_coroutine_threadsafe(queue.put(item), loop)
361
- except RuntimeError:
362
- # Event loop closed; drop silently
363
- pass
358
+ # Best-effort extraction of raw for debugging
359
+ try:
360
+ raw_obj = chunk.model_dump() # pydantic BaseModel
361
+ except Exception:
362
+ raw_obj = None
364
363
 
365
- def _producer():
366
- try:
367
- stream = self.client.chat.completions.create(**request_params)
368
- for chunk in stream:
369
- try:
370
- # Best-effort extraction of raw for debugging
371
- try:
372
- raw_obj = chunk.model_dump() # pydantic BaseModel
373
- except Exception:
374
- raw_obj = None
375
-
376
- choice = None
377
- if getattr(chunk, "choices", None):
378
- choice = chunk.choices[0]
379
-
380
- if choice is None:
381
- continue
382
-
383
- delta = getattr(choice, "delta", None)
384
- finish_reason = getattr(choice, "finish_reason", None)
385
-
386
- # Text content delta
387
- if delta is not None:
388
- content_delta = getattr(delta, "content", None)
389
- if content_delta:
390
- _put(CompletionStreamChunk(delta=content_delta, raw=raw_obj))
391
-
392
- # Tool call deltas
393
- tool_calls = getattr(delta, "tool_calls", None)
394
- if isinstance(tool_calls, list):
395
- for tc in tool_calls:
396
- # Each tc is likely a pydantic model with .index/.id/.function
397
- try:
398
- idx = getattr(tc, "index", 0) or 0
399
- tc_id = getattr(tc, "id", None)
400
- fn = getattr(tc, "function", None)
401
- fn_name = getattr(fn, "name", None) if fn is not None else None
402
- # OpenAI streams "arguments" as incremental deltas
403
- args_delta = getattr(fn, "arguments", None) if fn is not None else None
404
-
405
- _put(CompletionStreamChunk(
406
- tool_call_delta=ToolCallDelta(
407
- index=idx,
408
- id=tc_id,
409
- type='function',
410
- function=ToolCallFunctionDelta(
411
- name=fn_name,
412
- arguments_delta=args_delta
413
- )
414
- ),
415
- raw=raw_obj
416
- ))
417
- except Exception:
418
- # Skip malformed tool-call deltas
419
- continue
420
-
421
- # Completion ended
422
- if finish_reason:
423
- _put(CompletionStreamChunk(is_done=True, finish_reason=finish_reason, raw=raw_obj))
424
- except Exception:
425
- # Skip individual chunk errors, keep streaming
426
- continue
364
+ choice = None
365
+ if getattr(chunk, "choices", None):
366
+ choice = chunk.choices[0]
367
+
368
+ if choice is None:
369
+ continue
370
+
371
+ delta = getattr(choice, "delta", None)
372
+ finish_reason = getattr(choice, "finish_reason", None)
373
+
374
+ # Text content delta
375
+ if delta is not None:
376
+ content_delta = getattr(delta, "content", None)
377
+ if content_delta:
378
+ yield CompletionStreamChunk(delta=content_delta, raw=raw_obj)
379
+
380
+ # Tool call deltas
381
+ tool_calls = getattr(delta, "tool_calls", None)
382
+ if isinstance(tool_calls, list):
383
+ for tc in tool_calls:
384
+ # Each tc is likely a pydantic model with .index/.id/.function
385
+ try:
386
+ idx = getattr(tc, "index", 0) or 0
387
+ tc_id = getattr(tc, "id", None)
388
+ fn = getattr(tc, "function", None)
389
+ fn_name = getattr(fn, "name", None) if fn is not None else None
390
+ # OpenAI streams "arguments" as incremental deltas
391
+ args_delta = getattr(fn, "arguments", None) if fn is not None else None
392
+
393
+ yield CompletionStreamChunk(
394
+ tool_call_delta=ToolCallDelta(
395
+ index=idx,
396
+ id=tc_id,
397
+ type='function',
398
+ function=ToolCallFunctionDelta(
399
+ name=fn_name,
400
+ arguments_delta=args_delta
401
+ )
402
+ ),
403
+ raw=raw_obj
404
+ )
405
+ except Exception:
406
+ # Skip malformed tool-call deltas
407
+ continue
408
+
409
+ # Completion ended
410
+ if finish_reason:
411
+ yield CompletionStreamChunk(is_done=True, finish_reason=finish_reason, raw=raw_obj)
427
412
  except Exception:
428
- # On top-level stream error, signal done
429
- pass
430
- finally:
431
- try:
432
- asyncio.run_coroutine_threadsafe(queue.put(SENTINEL), loop)
433
- except RuntimeError:
434
- pass
435
-
436
- # Start producer in background
437
- loop.run_in_executor(None, _producer)
438
-
439
- # Consume queue and yield
440
- while True:
441
- item = await queue.get()
442
- if item is SENTINEL:
443
- break
444
- # Guarantee type for consumers
445
- if isinstance(item, CompletionStreamChunk):
446
- yield item
413
+ # Skip individual chunk errors, keep streaming
414
+ continue
447
415
 
448
416
  return LiteLLMProvider()
449
417
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: jaf-py
3
- Version: 2.5.8
3
+ Version: 2.5.10
4
4
  Summary: A purely functional agent framework with immutable state and composable tools - Python implementation
5
5
  Author: JAF Contributors
6
6
  Maintainer: JAF Contributors
@@ -82,7 +82,7 @@ Dynamic: license-file
82
82
 
83
83
  <!-- ![JAF Banner](docs/cover.png) -->
84
84
 
85
- [![Version](https://img.shields.io/badge/version-2.5.8-blue.svg)](https://github.com/xynehq/jaf-py)
85
+ [![Version](https://img.shields.io/badge/version-2.5.10-blue.svg)](https://github.com/xynehq/jaf-py)
86
86
  [![Python](https://img.shields.io/badge/python-3.10%2B-blue.svg)](https://www.python.org/)
87
87
  [![Docs](https://img.shields.io/badge/Docs-Live-brightgreen)](https://xynehq.github.io/jaf-py/)
88
88
 
@@ -1,4 +1,4 @@
1
- jaf/__init__.py,sha256=PjbDVwTdBQtoum-MrpwN8GznsNQVX5R4uQjoJnPBdqI,8260
1
+ jaf/__init__.py,sha256=Z5aoCNRq2FLGOTYxBkp05eIxjHeaL8My7iUPhA8V2gs,8261
2
2
  jaf/cli.py,sha256=Af4di_NZ7rZ4wFl0R4EZh611NgJ--TL03vNyZ2M1_FY,8477
3
3
  jaf/exceptions.py,sha256=nl8JY355u7oTXB3PmC_LhnUaL8fzk2K4EaWM4fVpMPE,9196
4
4
  jaf/a2a/__init__.py,sha256=p4YVthZH0ow1ZECqWTQ0aQl8JWySYZb25jlzZJ09na4,7662
@@ -55,7 +55,7 @@ jaf/core/state.py,sha256=oNCVXPWLkqnBQObdQX10TcmZ0eOF3wKG6DtL3kF6ohw,9649
55
55
  jaf/core/streaming.py,sha256=wDjbxGdLehPs2ImzKAftEvb7ruOBldSvYYnrSeD2xJg,17084
56
56
  jaf/core/tool_results.py,sha256=-bTOqOX02lMyslp5Z4Dmuhx0cLd5o7kgR88qK2HO_sw,11323
57
57
  jaf/core/tools.py,sha256=84N9A7QQ3xxcOs2eUUot3nmCnt5i7iZT9VwkuzuFBxQ,16274
58
- jaf/core/tracing.py,sha256=cDjrqdxJla6srWIKb3aKoYQwew4Zu3i2qeORc-haZYA,53367
58
+ jaf/core/tracing.py,sha256=HMxaKbk6VguKAcTlUxnc1ayw_vQcu0tT0h0zhHUtdmE,53368
59
59
  jaf/core/types.py,sha256=RLfFS5TpJiog5-mAVMH_7oM7ZEJqGsZBbrOQiUpGUM4,33042
60
60
  jaf/core/workflows.py,sha256=Ul-82gzjIXtkhnSMSPv-8igikjkMtW1EBo9yrfodtvI,26294
61
61
  jaf/memory/__init__.py,sha256=-L98xlvihurGAzF0DnXtkueDVvO_wV2XxxEwAWdAj50,1400
@@ -74,7 +74,7 @@ jaf/policies/handoff.py,sha256=KJYYuL9T6v6DECRhnsS2Je6q4Aj9_zC5d_KBnvEnZNE,8318
74
74
  jaf/policies/validation.py,sha256=wn-7ynH10E5nk-_r1_kHIYHrBGmLX0EFr-FUTHrsxvc,10903
75
75
  jaf/providers/__init__.py,sha256=lIbl1JvGrDhI9CzEk79N8yJNhf7ww_aWD-F40MnG3vY,2174
76
76
  jaf/providers/mcp.py,sha256=WxcC8gUFpDBBYyhorMcc1jHq3xMDMBtnwyRPthfL0S0,13074
77
- jaf/providers/model.py,sha256=477Ly6D9OaZsUGsAoJtpSvdY_rLZ07ZjhlhapRzSxTI,39368
77
+ jaf/providers/model.py,sha256=dzPB-6SNWzExQwr2X5pL03Z1StjrnjUCGGfq6T6akOc,37843
78
78
  jaf/server/__init__.py,sha256=fMPnLZBRm6t3yQrr7-PnoHAQ8qj9o6Z1AJLM1M6bIS0,392
79
79
  jaf/server/main.py,sha256=CTb0ywbPIq9ELfay5MKChVR7BpIQOoEbPjPfpzo2aBQ,2152
80
80
  jaf/server/server.py,sha256=cbdhVTPoWa7rVIX3DDLoXjppeGTKQWNFj3NA1jrZN88,46830
@@ -88,9 +88,9 @@ jaf/visualization/functional_core.py,sha256=zedMDZbvjuOugWwnh6SJ2stvRNQX1Hlkb9Ab
88
88
  jaf/visualization/graphviz.py,sha256=WTOM6UP72-lVKwI4_SAr5-GCC3ouckxHv88ypCDQWJ0,12056
89
89
  jaf/visualization/imperative_shell.py,sha256=GpMrAlMnLo2IQgyB2nardCz09vMvAzaYI46MyrvJ0i4,2593
90
90
  jaf/visualization/types.py,sha256=QQcbVeQJLuAOXk8ynd08DXIS-PVCnv3R-XVE9iAcglw,1389
91
- jaf_py-2.5.8.dist-info/licenses/LICENSE,sha256=LXUQBJxdyr-7C4bk9cQBwvsF_xwA-UVstDTKabpcjlI,1063
92
- jaf_py-2.5.8.dist-info/METADATA,sha256=ddV0T51h4mawm5t4-1L9zTnPM-IrSpIdN9aBw79mz-M,27743
93
- jaf_py-2.5.8.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
94
- jaf_py-2.5.8.dist-info/entry_points.txt,sha256=OtIJeNJpb24kgGrqRx9szGgDx1vL9ayq8uHErmu7U5w,41
95
- jaf_py-2.5.8.dist-info/top_level.txt,sha256=Xu1RZbGaM4_yQX7bpalo881hg7N_dybaOW282F15ruE,4
96
- jaf_py-2.5.8.dist-info/RECORD,,
91
+ jaf_py-2.5.10.dist-info/licenses/LICENSE,sha256=LXUQBJxdyr-7C4bk9cQBwvsF_xwA-UVstDTKabpcjlI,1063
92
+ jaf_py-2.5.10.dist-info/METADATA,sha256=IgMM-npnDF6GN3korYat-SyZTwdXlym_d6KPh61r3V0,27745
93
+ jaf_py-2.5.10.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
94
+ jaf_py-2.5.10.dist-info/entry_points.txt,sha256=OtIJeNJpb24kgGrqRx9szGgDx1vL9ayq8uHErmu7U5w,41
95
+ jaf_py-2.5.10.dist-info/top_level.txt,sha256=Xu1RZbGaM4_yQX7bpalo881hg7N_dybaOW282F15ruE,4
96
+ jaf_py-2.5.10.dist-info/RECORD,,