projectdavid 1.31.1__tar.gz → 1.32.0__tar.gz

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.

Files changed (69) hide show
  1. {projectdavid-1.31.1 → projectdavid-1.32.0}/CHANGELOG.md +7 -0
  2. {projectdavid-1.31.1/src/projectdavid.egg-info → projectdavid-1.32.0}/PKG-INFO +1 -1
  3. {projectdavid-1.31.1 → projectdavid-1.32.0}/pyproject.toml +1 -1
  4. projectdavid-1.32.0/src/projectdavid/clients/synchronous_inference_wrapper.py +133 -0
  5. projectdavid-1.32.0/src/projectdavid/utils/function_call_suppressor.py +41 -0
  6. projectdavid-1.32.0/src/projectdavid/utils/peek_gate.py +49 -0
  7. {projectdavid-1.31.1 → projectdavid-1.32.0/src/projectdavid.egg-info}/PKG-INFO +1 -1
  8. {projectdavid-1.31.1 → projectdavid-1.32.0}/src/projectdavid.egg-info/SOURCES.txt +2 -0
  9. projectdavid-1.31.1/src/projectdavid/clients/synchronous_inference_wrapper.py +0 -104
  10. {projectdavid-1.31.1 → projectdavid-1.32.0}/LICENSE +0 -0
  11. {projectdavid-1.31.1 → projectdavid-1.32.0}/MANIFEST.in +0 -0
  12. {projectdavid-1.31.1 → projectdavid-1.32.0}/README.md +0 -0
  13. {projectdavid-1.31.1 → projectdavid-1.32.0}/docs/assistants.md +0 -0
  14. {projectdavid-1.31.1 → projectdavid-1.32.0}/docs/code_interpretation.md +0 -0
  15. {projectdavid-1.31.1 → projectdavid-1.32.0}/docs/database.md +0 -0
  16. {projectdavid-1.31.1 → projectdavid-1.32.0}/docs/database_assistant_example.md +0 -0
  17. {projectdavid-1.31.1 → projectdavid-1.32.0}/docs/docker_comtainers.md +0 -0
  18. {projectdavid-1.31.1 → projectdavid-1.32.0}/docs/file_search.md +0 -0
  19. {projectdavid-1.31.1 → projectdavid-1.32.0}/docs/files.md +0 -0
  20. {projectdavid-1.31.1 → projectdavid-1.32.0}/docs/function_call_definition.md +0 -0
  21. {projectdavid-1.31.1 → projectdavid-1.32.0}/docs/function_calls.md +0 -0
  22. {projectdavid-1.31.1 → projectdavid-1.32.0}/docs/handling_function_calls.md +0 -0
  23. {projectdavid-1.31.1 → projectdavid-1.32.0}/docs/inference.md +0 -0
  24. {projectdavid-1.31.1 → projectdavid-1.32.0}/docs/messages.md +0 -0
  25. {projectdavid-1.31.1 → projectdavid-1.32.0}/docs/runs.md +0 -0
  26. {projectdavid-1.31.1 → projectdavid-1.32.0}/docs/streams.md +0 -0
  27. {projectdavid-1.31.1 → projectdavid-1.32.0}/docs/threads.md +0 -0
  28. {projectdavid-1.31.1 → projectdavid-1.32.0}/docs/tools.md +0 -0
  29. {projectdavid-1.31.1 → projectdavid-1.32.0}/docs/users.md +0 -0
  30. {projectdavid-1.31.1 → projectdavid-1.32.0}/docs/vector_store.md +0 -0
  31. {projectdavid-1.31.1 → projectdavid-1.32.0}/docs/versioning.md +0 -0
  32. {projectdavid-1.31.1 → projectdavid-1.32.0}/setup.cfg +0 -0
  33. {projectdavid-1.31.1 → projectdavid-1.32.0}/src/projectdavid/__init__.py +0 -0
  34. {projectdavid-1.31.1 → projectdavid-1.32.0}/src/projectdavid/_version.py +0 -0
  35. {projectdavid-1.31.1 → projectdavid-1.32.0}/src/projectdavid/clients/actions_client.py +0 -0
  36. {projectdavid-1.31.1 → projectdavid-1.32.0}/src/projectdavid/clients/api_key_client.py +0 -0
  37. {projectdavid-1.31.1 → projectdavid-1.32.0}/src/projectdavid/clients/assistants_client.py +0 -0
  38. {projectdavid-1.31.1 → projectdavid-1.32.0}/src/projectdavid/clients/base_client.py +0 -0
  39. {projectdavid-1.31.1 → projectdavid-1.32.0}/src/projectdavid/clients/base_vector_store.py +0 -0
  40. {projectdavid-1.31.1 → projectdavid-1.32.0}/src/projectdavid/clients/event_handler.py +0 -0
  41. {projectdavid-1.31.1 → projectdavid-1.32.0}/src/projectdavid/clients/file_processor.py +0 -0
  42. {projectdavid-1.31.1 → projectdavid-1.32.0}/src/projectdavid/clients/file_search.py +0 -0
  43. {projectdavid-1.31.1 → projectdavid-1.32.0}/src/projectdavid/clients/files_client.py +0 -0
  44. {projectdavid-1.31.1 → projectdavid-1.32.0}/src/projectdavid/clients/inference_client.py +0 -0
  45. {projectdavid-1.31.1 → projectdavid-1.32.0}/src/projectdavid/clients/messages_client.py +0 -0
  46. {projectdavid-1.31.1 → projectdavid-1.32.0}/src/projectdavid/clients/runs.py +0 -0
  47. {projectdavid-1.31.1 → projectdavid-1.32.0}/src/projectdavid/clients/threads_client.py +0 -0
  48. {projectdavid-1.31.1 → projectdavid-1.32.0}/src/projectdavid/clients/tools_client.py +0 -0
  49. {projectdavid-1.31.1 → projectdavid-1.32.0}/src/projectdavid/clients/users_client.py +0 -0
  50. {projectdavid-1.31.1 → projectdavid-1.32.0}/src/projectdavid/clients/vector_store_manager.py +0 -0
  51. {projectdavid-1.31.1 → projectdavid-1.32.0}/src/projectdavid/clients/vectors.py +0 -0
  52. {projectdavid-1.31.1 → projectdavid-1.32.0}/src/projectdavid/constants/platform.py +0 -0
  53. {projectdavid-1.31.1 → projectdavid-1.32.0}/src/projectdavid/entity.py +0 -0
  54. {projectdavid-1.31.1 → projectdavid-1.32.0}/src/projectdavid/events.py +0 -0
  55. {projectdavid-1.31.1 → projectdavid-1.32.0}/src/projectdavid/serializers.py +0 -0
  56. {projectdavid-1.31.1 → projectdavid-1.32.0}/src/projectdavid/services/logging_service.py +0 -0
  57. {projectdavid-1.31.1 → projectdavid-1.32.0}/src/projectdavid/synthesis/__init__.py +0 -0
  58. {projectdavid-1.31.1 → projectdavid-1.32.0}/src/projectdavid/synthesis/llm_synthesizer.py +0 -0
  59. {projectdavid-1.31.1 → projectdavid-1.32.0}/src/projectdavid/synthesis/prompt.py +0 -0
  60. {projectdavid-1.31.1 → projectdavid-1.32.0}/src/projectdavid/synthesis/reranker.py +0 -0
  61. {projectdavid-1.31.1 → projectdavid-1.32.0}/src/projectdavid/synthesis/retriever.py +0 -0
  62. {projectdavid-1.31.1 → projectdavid-1.32.0}/src/projectdavid/utils/__init__.py +0 -0
  63. {projectdavid-1.31.1 → projectdavid-1.32.0}/src/projectdavid/utils/monitor_launcher.py +0 -0
  64. {projectdavid-1.31.1 → projectdavid-1.32.0}/src/projectdavid/utils/run_monitor.py +0 -0
  65. {projectdavid-1.31.1 → projectdavid-1.32.0}/src/projectdavid/utils/vector_search_formatter.py +0 -0
  66. {projectdavid-1.31.1 → projectdavid-1.32.0}/src/projectdavid.egg-info/dependency_links.txt +0 -0
  67. {projectdavid-1.31.1 → projectdavid-1.32.0}/src/projectdavid.egg-info/requires.txt +0 -0
  68. {projectdavid-1.31.1 → projectdavid-1.32.0}/src/projectdavid.egg-info/top_level.txt +0 -0
  69. {projectdavid-1.31.1 → projectdavid-1.32.0}/tests/test_clients.py +0 -0
@@ -1,3 +1,10 @@
1
+ # [1.32.0](https://github.com/frankie336/projectdavid/compare/v1.31.1...v1.32.0) (2025-06-08)
2
+
3
+
4
+ ### Features
5
+
6
+ * Integrate function call suppression. The provides optional methods to clean <fc><\fc> wrapped function calls from stream. ([05b357e](https://github.com/frankie336/projectdavid/commit/05b357e17a5dfacc019bfea106d3be560878df4b))
7
+
1
8
  ## [1.31.1](https://github.com/frankie336/projectdavid/compare/v1.31.0...v1.31.1) (2025-05-26)
2
9
 
3
10
 
@@ -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
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "projectdavid"
7
- version = "1.31.1"
7
+ version = "1.32.0"
8
8
  description = "Python SDK for interacting with the Entities Assistant API."
9
9
  readme = "README.md"
10
10
  authors = [
@@ -0,0 +1,133 @@
1
+ import asyncio
2
+ from contextlib import suppress
3
+ from typing import Generator, Optional
4
+
5
+ from projectdavid_common import UtilsInterface
6
+
7
+ from projectdavid.utils.function_call_suppressor import FunctionCallSuppressor
8
+ from projectdavid.utils.peek_gate import PeekGate
9
+
10
+ LOG = UtilsInterface.LoggingUtility()
11
+
12
+
13
+ class SynchronousInferenceStream:
14
+ _GLOBAL_LOOP = asyncio.new_event_loop()
15
+ asyncio.set_event_loop(_GLOBAL_LOOP)
16
+
17
+ # --------------------------------------------------------------
18
+ # ctor / setup
19
+ # --------------------------------------------------------------
20
+ def __init__(self, inference) -> None:
21
+ self.inference_client = inference
22
+ self.user_id: Optional[str] = None
23
+ self.thread_id: Optional[str] = None
24
+ self.assistant_id: Optional[str] = None
25
+ self.message_id: Optional[str] = None
26
+ self.run_id: Optional[str] = None
27
+ self.api_key: Optional[str] = None
28
+
29
+ def setup(
30
+ self,
31
+ user_id: str,
32
+ thread_id: str,
33
+ assistant_id: str,
34
+ message_id: str,
35
+ run_id: str,
36
+ api_key: str,
37
+ ) -> None:
38
+ self.user_id = user_id
39
+ self.thread_id = thread_id
40
+ self.assistant_id = assistant_id
41
+ self.message_id = message_id
42
+ self.run_id = run_id
43
+ self.api_key = api_key
44
+
45
+ # --------------------------------------------------------------
46
+ # main streaming entry-point
47
+ # --------------------------------------------------------------
48
+ def stream_chunks(
49
+ self,
50
+ provider: str,
51
+ model: str,
52
+ *, # keyword-only from here
53
+ api_key: Optional[str] = None,
54
+ timeout_per_chunk: float = 280.0,
55
+ suppress_fc: bool = True, # ← NEW switch
56
+ ) -> Generator[dict, None, None]:
57
+
58
+ resolved_api_key = api_key or self.api_key
59
+
60
+ # ------------- build async generator ----------------------
61
+ async def _stream_chunks_async():
62
+ async for chk in self.inference_client.stream_inference_response(
63
+ provider=provider,
64
+ model=model,
65
+ api_key=resolved_api_key,
66
+ thread_id=self.thread_id,
67
+ message_id=self.message_id,
68
+ run_id=self.run_id,
69
+ assistant_id=self.assistant_id,
70
+ ):
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:
84
+
85
+ def _filter_text(txt: str) -> str: # no-op
86
+ return txt
87
+
88
+ # ----------------------------------------------------------
89
+
90
+ while True:
91
+ try:
92
+ chunk = self._GLOBAL_LOOP.run_until_complete(
93
+ asyncio.wait_for(agen.__anext__(), timeout=timeout_per_chunk)
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
+
110
+ yield chunk
111
+
112
+ except StopAsyncIteration:
113
+ LOG.info("Stream completed normally.")
114
+ break
115
+ except asyncio.TimeoutError:
116
+ LOG.error("[TimeoutError] Timeout occurred, stopping stream.")
117
+ break
118
+ except Exception as e:
119
+ LOG.error("Unexpected error during streaming completions: %s", e)
120
+ break
121
+
122
+ # --------------------------------------------------------------
123
+ # housekeeping
124
+ # --------------------------------------------------------------
125
+ @classmethod
126
+ def shutdown_loop(cls) -> None:
127
+ if cls._GLOBAL_LOOP and not cls._GLOBAL_LOOP.is_closed():
128
+ cls._GLOBAL_LOOP.stop()
129
+ cls._GLOBAL_LOOP.close()
130
+
131
+ def close(self) -> None:
132
+ with suppress(Exception):
133
+ self.inference_client.close()
@@ -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
@@ -58,7 +58,9 @@ src/projectdavid/synthesis/prompt.py
58
58
  src/projectdavid/synthesis/reranker.py
59
59
  src/projectdavid/synthesis/retriever.py
60
60
  src/projectdavid/utils/__init__.py
61
+ src/projectdavid/utils/function_call_suppressor.py
61
62
  src/projectdavid/utils/monitor_launcher.py
63
+ src/projectdavid/utils/peek_gate.py
62
64
  src/projectdavid/utils/run_monitor.py
63
65
  src/projectdavid/utils/vector_search_formatter.py
64
66
  tests/test_clients.py
@@ -1,104 +0,0 @@
1
- import asyncio
2
- from contextlib import suppress
3
- from typing import Generator, Optional
4
-
5
- from projectdavid_common import UtilsInterface
6
-
7
- logging_utility = UtilsInterface.LoggingUtility()
8
-
9
-
10
- class SynchronousInferenceStream:
11
- _GLOBAL_LOOP = asyncio.new_event_loop()
12
- asyncio.set_event_loop(_GLOBAL_LOOP)
13
-
14
- def __init__(self, inference) -> None:
15
- self.inference_client = inference
16
- self.user_id: Optional[str] = None
17
- self.thread_id: Optional[str] = None
18
- self.assistant_id: Optional[str] = None
19
- self.message_id: Optional[str] = None
20
- self.run_id: Optional[str] = None
21
- self.api_key: Optional[str] = None
22
-
23
- def setup(
24
- self,
25
- user_id: str,
26
- thread_id: str,
27
- assistant_id: str,
28
- message_id: str,
29
- run_id: str,
30
- api_key: str,
31
- ) -> None:
32
- self.user_id = user_id
33
- self.thread_id = thread_id
34
- self.assistant_id = assistant_id
35
- self.message_id = message_id
36
- self.run_id = run_id
37
- self.api_key = api_key
38
-
39
- def stream_chunks(
40
- self,
41
- provider: str,
42
- model: str,
43
- *, # Following parameters are keyword-only.
44
- api_key: Optional[str] = None,
45
- timeout_per_chunk: float = 280.0,
46
- ) -> 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
60
- resolved_api_key = api_key or self.api_key
61
-
62
- async def _stream_chunks_async() -> Generator[dict, None, None]:
63
- async for chunk in self.inference_client.stream_inference_response(
64
- provider=provider,
65
- model=model,
66
- api_key=resolved_api_key,
67
- thread_id=self.thread_id,
68
- message_id=self.message_id,
69
- run_id=self.run_id,
70
- assistant_id=self.assistant_id,
71
- ):
72
- yield chunk
73
-
74
- gen = _stream_chunks_async().__aiter__()
75
-
76
- while True:
77
- try:
78
- chunk = self._GLOBAL_LOOP.run_until_complete(
79
- asyncio.wait_for(gen.__anext__(), timeout=timeout_per_chunk)
80
- )
81
- yield chunk
82
- except StopAsyncIteration:
83
- logging_utility.info("Stream completed normally.")
84
- break
85
- except asyncio.TimeoutError:
86
- logging_utility.error(
87
- "[TimeoutError] Timeout occurred, stopping stream."
88
- )
89
- break
90
- except Exception as e:
91
- logging_utility.error(
92
- "Unexpected error during streaming completions: %s", e
93
- )
94
- break
95
-
96
- @classmethod
97
- def shutdown_loop(cls) -> None:
98
- if cls._GLOBAL_LOOP and not cls._GLOBAL_LOOP.is_closed():
99
- cls._GLOBAL_LOOP.stop()
100
- cls._GLOBAL_LOOP.close()
101
-
102
- def close(self) -> None:
103
- with suppress(Exception):
104
- self.inference_client.close()
File without changes
File without changes
File without changes
File without changes