projectdavid 1.31.1__py3-none-any.whl → 1.32.0__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 projectdavid might be problematic. Click here for more details.

@@ -4,13 +4,19 @@ from typing import Generator, Optional
4
4
 
5
5
  from projectdavid_common import UtilsInterface
6
6
 
7
- logging_utility = UtilsInterface.LoggingUtility()
7
+ from projectdavid.utils.function_call_suppressor import FunctionCallSuppressor
8
+ from projectdavid.utils.peek_gate import PeekGate
9
+
10
+ LOG = UtilsInterface.LoggingUtility()
8
11
 
9
12
 
10
13
  class SynchronousInferenceStream:
11
14
  _GLOBAL_LOOP = asyncio.new_event_loop()
12
15
  asyncio.set_event_loop(_GLOBAL_LOOP)
13
16
 
17
+ # --------------------------------------------------------------
18
+ # ctor / setup
19
+ # --------------------------------------------------------------
14
20
  def __init__(self, inference) -> None:
15
21
  self.inference_client = inference
16
22
  self.user_id: Optional[str] = None
@@ -36,31 +42,24 @@ class SynchronousInferenceStream:
36
42
  self.run_id = run_id
37
43
  self.api_key = api_key
38
44
 
45
+ # --------------------------------------------------------------
46
+ # main streaming entry-point
47
+ # --------------------------------------------------------------
39
48
  def stream_chunks(
40
49
  self,
41
50
  provider: str,
42
51
  model: str,
43
- *, # Following parameters are keyword-only.
52
+ *, # keyword-only from here
44
53
  api_key: Optional[str] = None,
45
54
  timeout_per_chunk: float = 280.0,
55
+ suppress_fc: bool = True, # ← NEW switch
46
56
  ) -> Generator[dict, None, None]:
47
- """
48
- Streams inference response chunks synchronously by wrapping an async generator.
49
-
50
- Args:
51
- provider (str): The provider name.
52
- model (str): The model name.
53
- api_key (Optional[str]): API key for authentication. If not provided, falls back to the value from setup().
54
- timeout_per_chunk (float): Timeout per chunk in seconds.
55
-
56
- Yields:
57
- dict: A chunk of the inference response.
58
- """
59
- # Prefer direct input over self.api_key
57
+
60
58
  resolved_api_key = api_key or self.api_key
61
59
 
62
- async def _stream_chunks_async() -> Generator[dict, None, None]:
63
- async for chunk in self.inference_client.stream_inference_response(
60
+ # ------------- build async generator ----------------------
61
+ async def _stream_chunks_async():
62
+ async for chk in self.inference_client.stream_inference_response(
64
63
  provider=provider,
65
64
  model=model,
66
65
  api_key=resolved_api_key,
@@ -69,30 +68,60 @@ class SynchronousInferenceStream:
69
68
  run_id=self.run_id,
70
69
  assistant_id=self.assistant_id,
71
70
  ):
72
- yield chunk
71
+ yield chk
72
+
73
+ agen = _stream_chunks_async().__aiter__()
74
+
75
+ # ------------- optional suppression chain -----------------
76
+ if suppress_fc:
77
+ _suppressor = FunctionCallSuppressor()
78
+ _peek_gate = PeekGate(_suppressor) # 2 KB default
79
+
80
+ def _filter_text(txt: str) -> str:
81
+ return _peek_gate.feed(txt)
82
+
83
+ else:
73
84
 
74
- gen = _stream_chunks_async().__aiter__()
85
+ def _filter_text(txt: str) -> str: # no-op
86
+ return txt
87
+
88
+ # ----------------------------------------------------------
75
89
 
76
90
  while True:
77
91
  try:
78
92
  chunk = self._GLOBAL_LOOP.run_until_complete(
79
- asyncio.wait_for(gen.__anext__(), timeout=timeout_per_chunk)
93
+ asyncio.wait_for(agen.__anext__(), timeout=timeout_per_chunk)
80
94
  )
95
+
96
+ # --- provider-labelled function_call blocks -------
97
+ if suppress_fc and chunk.get("type") == "function_call":
98
+ LOG.debug(
99
+ "[SUPPRESSOR] blocked provider-labelled function_call chunk"
100
+ )
101
+ continue
102
+
103
+ # --- inline content filtering --------------------
104
+ if isinstance(chunk.get("content"), str):
105
+ chunk["content"] = _filter_text(chunk["content"])
106
+ if not chunk["content"]:
107
+ # fully suppressed → skip forwarding
108
+ continue
109
+
81
110
  yield chunk
111
+
82
112
  except StopAsyncIteration:
83
- logging_utility.info("Stream completed normally.")
113
+ LOG.info("Stream completed normally.")
84
114
  break
85
115
  except asyncio.TimeoutError:
86
- logging_utility.error(
87
- "[TimeoutError] Timeout occurred, stopping stream."
88
- )
116
+ LOG.error("[TimeoutError] Timeout occurred, stopping stream.")
89
117
  break
90
118
  except Exception as e:
91
- logging_utility.error(
92
- "Unexpected error during streaming completions: %s", e
93
- )
119
+ LOG.error("Unexpected error during streaming completions: %s", e)
94
120
  break
95
121
 
122
+ # --------------------------------------------------------------
123
+ # housekeeping
124
+ # --------------------------------------------------------------
96
125
  @classmethod
97
126
  def shutdown_loop(cls) -> None:
98
127
  if cls._GLOBAL_LOOP and not cls._GLOBAL_LOOP.is_closed():
@@ -0,0 +1,41 @@
1
+ import re
2
+
3
+ from projectdavid_common.utilities.logging_service import LoggingUtility
4
+
5
+ LOG = LoggingUtility()
6
+
7
+
8
+ # ─────────────────────────────────────────────────────────────────────
9
+ # function-call filter helpers (unchanged except for logger)
10
+ # ─────────────────────────────────────────────────────────────────────
11
+ class FunctionCallSuppressor:
12
+ OPEN_RE = re.compile(r"<\s*fc\s*>", re.I)
13
+ CLOSE_RE = re.compile(r"</\s*fc\s*>", re.I)
14
+
15
+ def __init__(self):
16
+ self.in_fc = False
17
+ self.buf = ""
18
+
19
+ def filter_chunk(self, chunk: str) -> str:
20
+ self.buf += chunk
21
+ out = ""
22
+
23
+ while self.buf:
24
+ if not self.in_fc:
25
+ m = self.OPEN_RE.search(self.buf)
26
+ if not m:
27
+ out += self.buf
28
+ self.buf = ""
29
+ break
30
+ out += self.buf[: m.start()]
31
+ LOG.debug("[SUPPRESSOR] <fc> detected")
32
+ self.buf = self.buf[m.end() :]
33
+ self.in_fc = True
34
+ else:
35
+ m = self.CLOSE_RE.search(self.buf)
36
+ if not m:
37
+ break # wait for more tokens
38
+ LOG.debug("[SUPPRESSOR] </fc> detected — block suppressed")
39
+ self.buf = self.buf[m.end() :]
40
+ self.in_fc = False
41
+ return out
@@ -0,0 +1,49 @@
1
+ import re
2
+
3
+ from projectdavid_common.utilities.logging_service import LoggingUtility
4
+
5
+ from .function_call_suppressor import FunctionCallSuppressor
6
+
7
+ LOG = LoggingUtility()
8
+
9
+
10
+ class PeekGate:
11
+ """
12
+ Turns the suppressor on only if a <fc> block appears in the first
13
+ `peek_limit` characters; otherwise passes text through unchanged.
14
+ """
15
+
16
+ def __init__(self, downstream: FunctionCallSuppressor, peek_limit: int = 2048):
17
+ self.downstream = downstream
18
+ self.peek_limit = peek_limit
19
+ self.buf = ""
20
+ self.mode = "peeking" # -> "normal" after decision
21
+ self.suppressing = False
22
+
23
+ def feed(self, txt: str) -> str:
24
+ # decision already taken
25
+ if self.mode == "normal":
26
+ return self.downstream.filter_chunk(txt) if self.suppressing else txt
27
+
28
+ # still peeking …
29
+ self.buf += txt
30
+ m = re.search(r"<\s*fc\s*>", self.buf, flags=re.I)
31
+ if m: # found a tag
32
+ head = self.buf[: m.start()]
33
+ LOG.debug("[PEEK] <fc> located after leading text – engaging suppressor")
34
+ self.suppressing = True
35
+ self.mode = "normal"
36
+ tail = self.buf[m.start() :]
37
+ self.buf = ""
38
+ return head + self.downstream.filter_chunk(tail)
39
+
40
+ if len(self.buf) >= self.peek_limit: # give up
41
+ LOG.debug(
42
+ "[PEEK] no <fc> tag within first %d chars – no suppression",
43
+ self.peek_limit,
44
+ )
45
+ self.mode = "normal"
46
+ self.suppressing = False
47
+ out, self.buf = self.buf, ""
48
+ return out
49
+ return ""
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: projectdavid
3
- Version: 1.31.1
3
+ Version: 1.32.0
4
4
  Summary: Python SDK for interacting with the Entities Assistant API.
5
5
  Author-email: Francis Neequaye Armah <francis.neequaye@projectdavid.co.uk>
6
6
  License: PolyForm Noncommercial License 1.0.0
@@ -15,7 +15,7 @@ projectdavid/clients/files_client.py,sha256=XkIDzbQFGDrd88taf0Kouc_4YJOPIYEHiIyW
15
15
  projectdavid/clients/inference_client.py,sha256=xz4ACPv5Tkis604QxO5mJX1inH_TGDfQP-31geETYpE,6609
16
16
  projectdavid/clients/messages_client.py,sha256=467xeIt3VYs6cG8-bl-eDRi_auWOPmfd5tSJDmQSJUI,17232
17
17
  projectdavid/clients/runs.py,sha256=-fXOq5L9w2efDPmZkNxb0s2yjl6oN0XN4_aLXqaeceo,25270
18
- projectdavid/clients/synchronous_inference_wrapper.py,sha256=25dNSel5DVieu2aFg6G1e4ENo5WWB4EB8_jugIiVzlo,3433
18
+ projectdavid/clients/synchronous_inference_wrapper.py,sha256=sikHDDmx3NFhK9eoFL0PVlZ0CaFG-CIWfDmgFkH48Do,4594
19
19
  projectdavid/clients/threads_client.py,sha256=ekzU5w14zftmtmFkiec3NC90Of-_KVSUY1qH9cmfSFg,6771
20
20
  projectdavid/clients/tools_client.py,sha256=GkCVOmwpAoPqVt6aYmH0G1HIFha3iEwR9IIf9teR0j8,11487
21
21
  projectdavid/clients/users_client.py,sha256=eCuUb9qvyH1GUFhZu6TRL9zdoK-qzHSs8-Vmrk_0mmg,13729
@@ -29,11 +29,13 @@ projectdavid/synthesis/prompt.py,sha256=Jlya6avnWV5ryaJaibt4W29XYxRlJRV6rwHfjgYy
29
29
  projectdavid/synthesis/reranker.py,sha256=aFjdLDoqnhgzxPIoRfs4y_Z_F6T88g5Sg8R5m0HMxdU,868
30
30
  projectdavid/synthesis/retriever.py,sha256=8R5HIgPXMKIQxV5KsN8u_gVVP4JY-wsDwJd92A-74-o,2300
31
31
  projectdavid/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
32
+ projectdavid/utils/function_call_suppressor.py,sha256=uTjB6e1JDs_nkSVtRuqretb-zYDbAAC3jN3gpzqVauo,1616
32
33
  projectdavid/utils/monitor_launcher.py,sha256=3YAgJdeuaUvq3JGvpA4ymqFsAnk29nH5q93cwStP4hc,2836
34
+ projectdavid/utils/peek_gate.py,sha256=odQIZJYTygRPu5IfOBBgqqeSXiWfQ73-lRBYM2KbY4I,1630
33
35
  projectdavid/utils/run_monitor.py,sha256=F_WkqIP-qnWH-4llIbileWWLfRj2Q1Cg-ni23SR1rec,3786
34
36
  projectdavid/utils/vector_search_formatter.py,sha256=YTe3HPGec26qGY7uxY8_GS8lc4QaN6aNXMzkl29nZpI,1735
35
- projectdavid-1.31.1.dist-info/licenses/LICENSE,sha256=_8yjiEGttpS284BkfhXxfERqTRZW_tUaHiBB0GTJTMg,4563
36
- projectdavid-1.31.1.dist-info/METADATA,sha256=f-SkJ06HipWaVJZ0W-bECBP7-2OjCNqTNc58kN7A0qw,10781
37
- projectdavid-1.31.1.dist-info/WHEEL,sha256=zaaOINJESkSfm_4HQVc5ssNzHCPXhJm0kEUakpsEHaU,91
38
- projectdavid-1.31.1.dist-info/top_level.txt,sha256=kil8GU4s7qYRfNnzGnFHhZnSNRSxgNG-J4HLgQMmMtw,13
39
- projectdavid-1.31.1.dist-info/RECORD,,
37
+ projectdavid-1.32.0.dist-info/licenses/LICENSE,sha256=_8yjiEGttpS284BkfhXxfERqTRZW_tUaHiBB0GTJTMg,4563
38
+ projectdavid-1.32.0.dist-info/METADATA,sha256=F3ZVLx0ylA3omK6BEsxsTrbaroIwcE42FO0GUcT0EkU,10781
39
+ projectdavid-1.32.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
40
+ projectdavid-1.32.0.dist-info/top_level.txt,sha256=kil8GU4s7qYRfNnzGnFHhZnSNRSxgNG-J4HLgQMmMtw,13
41
+ projectdavid-1.32.0.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (80.8.0)
2
+ Generator: setuptools (80.9.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5