unique_toolkit 0.7.9__py3-none-any.whl → 1.33.3__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.
- unique_toolkit/__init__.py +36 -3
- unique_toolkit/_common/api_calling/human_verification_manager.py +357 -0
- unique_toolkit/_common/base_model_type_attribute.py +303 -0
- unique_toolkit/_common/chunk_relevancy_sorter/config.py +49 -0
- unique_toolkit/_common/chunk_relevancy_sorter/exception.py +5 -0
- unique_toolkit/_common/chunk_relevancy_sorter/schemas.py +46 -0
- unique_toolkit/_common/chunk_relevancy_sorter/service.py +374 -0
- unique_toolkit/_common/chunk_relevancy_sorter/tests/test_service.py +275 -0
- unique_toolkit/_common/default_language_model.py +12 -0
- unique_toolkit/_common/docx_generator/__init__.py +7 -0
- unique_toolkit/_common/docx_generator/config.py +12 -0
- unique_toolkit/_common/docx_generator/schemas.py +80 -0
- unique_toolkit/_common/docx_generator/service.py +225 -0
- unique_toolkit/_common/docx_generator/template/Doc Template.docx +0 -0
- unique_toolkit/_common/endpoint_builder.py +368 -0
- unique_toolkit/_common/endpoint_requestor.py +480 -0
- unique_toolkit/_common/exception.py +24 -0
- unique_toolkit/_common/experimental/endpoint_builder.py +368 -0
- unique_toolkit/_common/experimental/endpoint_requestor.py +488 -0
- unique_toolkit/_common/feature_flags/schema.py +9 -0
- unique_toolkit/_common/pydantic/rjsf_tags.py +936 -0
- unique_toolkit/_common/pydantic_helpers.py +174 -0
- unique_toolkit/_common/referencing.py +53 -0
- unique_toolkit/_common/string_utilities.py +140 -0
- unique_toolkit/_common/tests/test_referencing.py +521 -0
- unique_toolkit/_common/tests/test_string_utilities.py +506 -0
- unique_toolkit/_common/token/image_token_counting.py +67 -0
- unique_toolkit/_common/token/token_counting.py +204 -0
- unique_toolkit/_common/utils/__init__.py +1 -0
- unique_toolkit/_common/utils/files.py +43 -0
- unique_toolkit/_common/utils/image/encode.py +25 -0
- unique_toolkit/_common/utils/jinja/helpers.py +10 -0
- unique_toolkit/_common/utils/jinja/render.py +18 -0
- unique_toolkit/_common/utils/jinja/schema.py +65 -0
- unique_toolkit/_common/utils/jinja/utils.py +80 -0
- unique_toolkit/_common/utils/structured_output/__init__.py +1 -0
- unique_toolkit/_common/utils/structured_output/schema.py +5 -0
- unique_toolkit/_common/utils/write_configuration.py +51 -0
- unique_toolkit/_common/validators.py +101 -4
- unique_toolkit/agentic/__init__.py +1 -0
- unique_toolkit/agentic/debug_info_manager/debug_info_manager.py +28 -0
- unique_toolkit/agentic/debug_info_manager/test/test_debug_info_manager.py +278 -0
- unique_toolkit/agentic/evaluation/config.py +36 -0
- unique_toolkit/{evaluators → agentic/evaluation}/context_relevancy/prompts.py +25 -0
- unique_toolkit/agentic/evaluation/context_relevancy/schema.py +80 -0
- unique_toolkit/agentic/evaluation/context_relevancy/service.py +273 -0
- unique_toolkit/agentic/evaluation/evaluation_manager.py +218 -0
- unique_toolkit/agentic/evaluation/hallucination/constants.py +61 -0
- unique_toolkit/agentic/evaluation/hallucination/hallucination_evaluation.py +112 -0
- unique_toolkit/{evaluators → agentic/evaluation}/hallucination/prompts.py +1 -1
- unique_toolkit/{evaluators → agentic/evaluation}/hallucination/service.py +20 -16
- unique_toolkit/{evaluators → agentic/evaluation}/hallucination/utils.py +32 -21
- unique_toolkit/{evaluators → agentic/evaluation}/output_parser.py +20 -2
- unique_toolkit/{evaluators → agentic/evaluation}/schemas.py +27 -7
- unique_toolkit/agentic/evaluation/tests/test_context_relevancy_service.py +253 -0
- unique_toolkit/agentic/evaluation/tests/test_output_parser.py +87 -0
- unique_toolkit/agentic/history_manager/history_construction_with_contents.py +298 -0
- unique_toolkit/agentic/history_manager/history_manager.py +241 -0
- unique_toolkit/agentic/history_manager/loop_token_reducer.py +484 -0
- unique_toolkit/agentic/history_manager/utils.py +96 -0
- unique_toolkit/agentic/message_log_manager/__init__.py +5 -0
- unique_toolkit/agentic/message_log_manager/service.py +93 -0
- unique_toolkit/agentic/postprocessor/postprocessor_manager.py +212 -0
- unique_toolkit/agentic/reference_manager/reference_manager.py +103 -0
- unique_toolkit/agentic/responses_api/__init__.py +19 -0
- unique_toolkit/agentic/responses_api/postprocessors/code_display.py +71 -0
- unique_toolkit/agentic/responses_api/postprocessors/generated_files.py +297 -0
- unique_toolkit/agentic/responses_api/stream_handler.py +15 -0
- unique_toolkit/agentic/short_term_memory_manager/persistent_short_term_memory_manager.py +141 -0
- unique_toolkit/agentic/thinking_manager/thinking_manager.py +103 -0
- unique_toolkit/agentic/tools/__init__.py +1 -0
- unique_toolkit/agentic/tools/a2a/__init__.py +36 -0
- unique_toolkit/agentic/tools/a2a/config.py +17 -0
- unique_toolkit/agentic/tools/a2a/evaluation/__init__.py +15 -0
- unique_toolkit/agentic/tools/a2a/evaluation/_utils.py +66 -0
- unique_toolkit/agentic/tools/a2a/evaluation/config.py +55 -0
- unique_toolkit/agentic/tools/a2a/evaluation/evaluator.py +260 -0
- unique_toolkit/agentic/tools/a2a/evaluation/summarization_user_message.j2 +9 -0
- unique_toolkit/agentic/tools/a2a/manager.py +55 -0
- unique_toolkit/agentic/tools/a2a/postprocessing/__init__.py +21 -0
- unique_toolkit/agentic/tools/a2a/postprocessing/_display_utils.py +240 -0
- unique_toolkit/agentic/tools/a2a/postprocessing/_ref_utils.py +84 -0
- unique_toolkit/agentic/tools/a2a/postprocessing/config.py +78 -0
- unique_toolkit/agentic/tools/a2a/postprocessing/display.py +264 -0
- unique_toolkit/agentic/tools/a2a/postprocessing/references.py +101 -0
- unique_toolkit/agentic/tools/a2a/postprocessing/test/test_display.py +421 -0
- unique_toolkit/agentic/tools/a2a/postprocessing/test/test_display_utils.py +2103 -0
- unique_toolkit/agentic/tools/a2a/postprocessing/test/test_ref_utils.py +603 -0
- unique_toolkit/agentic/tools/a2a/prompts.py +46 -0
- unique_toolkit/agentic/tools/a2a/response_watcher/__init__.py +6 -0
- unique_toolkit/agentic/tools/a2a/response_watcher/service.py +91 -0
- unique_toolkit/agentic/tools/a2a/tool/__init__.py +4 -0
- unique_toolkit/agentic/tools/a2a/tool/_memory.py +26 -0
- unique_toolkit/agentic/tools/a2a/tool/_schema.py +9 -0
- unique_toolkit/agentic/tools/a2a/tool/config.py +158 -0
- unique_toolkit/agentic/tools/a2a/tool/service.py +393 -0
- unique_toolkit/agentic/tools/agent_chunks_hanlder.py +65 -0
- unique_toolkit/agentic/tools/config.py +128 -0
- unique_toolkit/agentic/tools/factory.py +44 -0
- unique_toolkit/agentic/tools/mcp/__init__.py +4 -0
- unique_toolkit/agentic/tools/mcp/manager.py +71 -0
- unique_toolkit/agentic/tools/mcp/models.py +28 -0
- unique_toolkit/agentic/tools/mcp/tool_wrapper.py +234 -0
- unique_toolkit/agentic/tools/openai_builtin/__init__.py +11 -0
- unique_toolkit/agentic/tools/openai_builtin/base.py +46 -0
- unique_toolkit/agentic/tools/openai_builtin/code_interpreter/__init__.py +8 -0
- unique_toolkit/agentic/tools/openai_builtin/code_interpreter/config.py +88 -0
- unique_toolkit/agentic/tools/openai_builtin/code_interpreter/service.py +250 -0
- unique_toolkit/agentic/tools/openai_builtin/manager.py +79 -0
- unique_toolkit/agentic/tools/schemas.py +145 -0
- unique_toolkit/agentic/tools/test/test_mcp_manager.py +536 -0
- unique_toolkit/agentic/tools/test/test_tool_progress_reporter.py +445 -0
- unique_toolkit/agentic/tools/tool.py +187 -0
- unique_toolkit/agentic/tools/tool_manager.py +492 -0
- unique_toolkit/agentic/tools/tool_progress_reporter.py +285 -0
- unique_toolkit/agentic/tools/utils/__init__.py +19 -0
- unique_toolkit/agentic/tools/utils/execution/__init__.py +1 -0
- unique_toolkit/agentic/tools/utils/execution/execution.py +286 -0
- unique_toolkit/agentic/tools/utils/source_handling/__init__.py +0 -0
- unique_toolkit/agentic/tools/utils/source_handling/schema.py +21 -0
- unique_toolkit/agentic/tools/utils/source_handling/source_formatting.py +207 -0
- unique_toolkit/agentic/tools/utils/source_handling/tests/test_source_formatting.py +216 -0
- unique_toolkit/app/__init__.py +9 -0
- unique_toolkit/app/dev_util.py +180 -0
- unique_toolkit/app/fast_api_factory.py +131 -0
- unique_toolkit/app/init_sdk.py +32 -1
- unique_toolkit/app/schemas.py +206 -31
- unique_toolkit/app/unique_settings.py +367 -0
- unique_toolkit/app/webhook.py +77 -0
- unique_toolkit/chat/__init__.py +8 -1
- unique_toolkit/chat/deprecated/service.py +232 -0
- unique_toolkit/chat/functions.py +648 -78
- unique_toolkit/chat/rendering.py +34 -0
- unique_toolkit/chat/responses_api.py +461 -0
- unique_toolkit/chat/schemas.py +134 -2
- unique_toolkit/chat/service.py +115 -767
- unique_toolkit/content/functions.py +353 -8
- unique_toolkit/content/schemas.py +128 -15
- unique_toolkit/content/service.py +321 -45
- unique_toolkit/content/smart_rules.py +301 -0
- unique_toolkit/content/utils.py +10 -3
- unique_toolkit/data_extraction/README.md +96 -0
- unique_toolkit/data_extraction/__init__.py +11 -0
- unique_toolkit/data_extraction/augmented/__init__.py +5 -0
- unique_toolkit/data_extraction/augmented/service.py +93 -0
- unique_toolkit/data_extraction/base.py +25 -0
- unique_toolkit/data_extraction/basic/__init__.py +11 -0
- unique_toolkit/data_extraction/basic/config.py +18 -0
- unique_toolkit/data_extraction/basic/prompt.py +13 -0
- unique_toolkit/data_extraction/basic/service.py +55 -0
- unique_toolkit/embedding/service.py +103 -12
- unique_toolkit/framework_utilities/__init__.py +1 -0
- unique_toolkit/framework_utilities/langchain/__init__.py +10 -0
- unique_toolkit/framework_utilities/langchain/client.py +71 -0
- unique_toolkit/framework_utilities/langchain/history.py +19 -0
- unique_toolkit/framework_utilities/openai/__init__.py +6 -0
- unique_toolkit/framework_utilities/openai/client.py +84 -0
- unique_toolkit/framework_utilities/openai/message_builder.py +229 -0
- unique_toolkit/framework_utilities/utils.py +23 -0
- unique_toolkit/language_model/__init__.py +3 -0
- unique_toolkit/language_model/_responses_api_utils.py +93 -0
- unique_toolkit/language_model/builder.py +27 -11
- unique_toolkit/language_model/default_language_model.py +3 -0
- unique_toolkit/language_model/functions.py +345 -43
- unique_toolkit/language_model/infos.py +1288 -46
- unique_toolkit/language_model/reference.py +242 -0
- unique_toolkit/language_model/schemas.py +481 -49
- unique_toolkit/language_model/service.py +229 -28
- unique_toolkit/protocols/support.py +145 -0
- unique_toolkit/services/__init__.py +7 -0
- unique_toolkit/services/chat_service.py +1631 -0
- unique_toolkit/services/knowledge_base.py +1094 -0
- unique_toolkit/short_term_memory/service.py +178 -41
- unique_toolkit/smart_rules/__init__.py +0 -0
- unique_toolkit/smart_rules/compile.py +56 -0
- unique_toolkit/test_utilities/events.py +197 -0
- unique_toolkit-1.33.3.dist-info/METADATA +1145 -0
- unique_toolkit-1.33.3.dist-info/RECORD +205 -0
- unique_toolkit/evaluators/__init__.py +0 -1
- unique_toolkit/evaluators/config.py +0 -35
- unique_toolkit/evaluators/constants.py +0 -1
- unique_toolkit/evaluators/context_relevancy/constants.py +0 -32
- unique_toolkit/evaluators/context_relevancy/service.py +0 -53
- unique_toolkit/evaluators/context_relevancy/utils.py +0 -142
- unique_toolkit/evaluators/hallucination/constants.py +0 -41
- unique_toolkit-0.7.9.dist-info/METADATA +0 -413
- unique_toolkit-0.7.9.dist-info/RECORD +0 -64
- /unique_toolkit/{evaluators → agentic/evaluation}/exception.py +0 -0
- {unique_toolkit-0.7.9.dist-info → unique_toolkit-1.33.3.dist-info}/LICENSE +0 -0
- {unique_toolkit-0.7.9.dist-info → unique_toolkit-1.33.3.dist-info}/WHEEL +0 -0
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
from abc import ABC
|
|
3
|
+
from logging import Logger
|
|
4
|
+
|
|
5
|
+
from unique_toolkit.agentic.tools.utils.execution.execution import SafeTaskExecutor
|
|
6
|
+
from unique_toolkit.chat.service import ChatService
|
|
7
|
+
from unique_toolkit.language_model.schemas import (
|
|
8
|
+
LanguageModelStreamResponse,
|
|
9
|
+
ResponsesLanguageModelStreamResponse,
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class Postprocessor(ABC):
|
|
14
|
+
def __init__(self, name: str):
|
|
15
|
+
self.name = name
|
|
16
|
+
|
|
17
|
+
def get_name(self) -> str:
|
|
18
|
+
return self.name
|
|
19
|
+
|
|
20
|
+
async def run(self, loop_response: LanguageModelStreamResponse) -> None:
|
|
21
|
+
raise NotImplementedError("Subclasses must implement this method.")
|
|
22
|
+
|
|
23
|
+
def apply_postprocessing_to_response(
|
|
24
|
+
self, loop_response: LanguageModelStreamResponse
|
|
25
|
+
) -> bool:
|
|
26
|
+
raise NotImplementedError(
|
|
27
|
+
"Subclasses must implement this method to apply post-processing to the response."
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
async def remove_from_text(self, text: str) -> str:
|
|
31
|
+
raise NotImplementedError(
|
|
32
|
+
"Subclasses must implement this method to remove post-processing from the message."
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class ResponsesApiPostprocessor(ABC):
|
|
37
|
+
def __init__(self, name: str):
|
|
38
|
+
self.name = name
|
|
39
|
+
|
|
40
|
+
def get_name(self) -> str:
|
|
41
|
+
return self.name
|
|
42
|
+
|
|
43
|
+
async def run(self, loop_response: ResponsesLanguageModelStreamResponse) -> None:
|
|
44
|
+
raise NotImplementedError("Subclasses must implement this method.")
|
|
45
|
+
|
|
46
|
+
def apply_postprocessing_to_response(
|
|
47
|
+
self, loop_response: ResponsesLanguageModelStreamResponse
|
|
48
|
+
) -> bool:
|
|
49
|
+
raise NotImplementedError(
|
|
50
|
+
"Subclasses must implement this method to apply post-processing to the response."
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
async def remove_from_text(self, text: str) -> str:
|
|
54
|
+
raise NotImplementedError(
|
|
55
|
+
"Subclasses must implement this method to remove post-processing from the message."
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class PostprocessorManager:
|
|
60
|
+
"""
|
|
61
|
+
Manages and executes postprocessors for modifying and refining responses.
|
|
62
|
+
|
|
63
|
+
This class is responsible for:
|
|
64
|
+
- Storing and managing a collection of postprocessor instances.
|
|
65
|
+
- Executing postprocessors asynchronously to refine loop responses.
|
|
66
|
+
- Applying modifications to assistant messages based on postprocessor results.
|
|
67
|
+
- Providing utility methods for text manipulation using postprocessors.
|
|
68
|
+
|
|
69
|
+
Key Features:
|
|
70
|
+
- Postprocessor Management: Allows adding and retrieving postprocessor instances.
|
|
71
|
+
- Asynchronous Execution: Runs all postprocessors concurrently for efficiency.
|
|
72
|
+
- Response Modification: Applies postprocessing changes to assistant messages when necessary.
|
|
73
|
+
- Text Cleanup: Supports removing specific patterns or content from text using postprocessors.
|
|
74
|
+
- Error Handling: Logs warnings for any postprocessors that fail during execution.
|
|
75
|
+
|
|
76
|
+
The PostprocessorManager serves as a centralized system for managing and applying postprocessing logic to enhance response quality and consistency.
|
|
77
|
+
"""
|
|
78
|
+
|
|
79
|
+
def __init__(
|
|
80
|
+
self,
|
|
81
|
+
logger: Logger,
|
|
82
|
+
chat_service: ChatService,
|
|
83
|
+
):
|
|
84
|
+
self._logger = logger
|
|
85
|
+
self._chat_service = chat_service
|
|
86
|
+
self._postprocessors: list[Postprocessor | ResponsesApiPostprocessor] = []
|
|
87
|
+
|
|
88
|
+
# Allow to add postprocessors that should be run before or after the others.
|
|
89
|
+
self._first_postprocessor: Postprocessor | ResponsesApiPostprocessor | None = (
|
|
90
|
+
None
|
|
91
|
+
)
|
|
92
|
+
self._last_postprocessor: Postprocessor | ResponsesApiPostprocessor | None = (
|
|
93
|
+
None
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
def add_postprocessor(
|
|
97
|
+
self, postprocessor: Postprocessor | ResponsesApiPostprocessor
|
|
98
|
+
):
|
|
99
|
+
self._postprocessors.append(postprocessor)
|
|
100
|
+
|
|
101
|
+
def set_first_postprocessor(
|
|
102
|
+
self, postprocessor: Postprocessor | ResponsesApiPostprocessor
|
|
103
|
+
) -> None:
|
|
104
|
+
if self._first_postprocessor is not None:
|
|
105
|
+
raise ValueError("Cannot set first postprocessor if already set.")
|
|
106
|
+
|
|
107
|
+
self._first_postprocessor = postprocessor
|
|
108
|
+
|
|
109
|
+
def set_last_postprocessor(
|
|
110
|
+
self, postprocessor: Postprocessor | ResponsesApiPostprocessor
|
|
111
|
+
) -> None:
|
|
112
|
+
if self._last_postprocessor is not None:
|
|
113
|
+
raise ValueError("Cannot set last postprocessor if already set.")
|
|
114
|
+
|
|
115
|
+
self._last_postprocessor = postprocessor
|
|
116
|
+
|
|
117
|
+
def get_postprocessors(
|
|
118
|
+
self, name: str
|
|
119
|
+
) -> list[Postprocessor | ResponsesApiPostprocessor]:
|
|
120
|
+
return self._get_all_postprocessors()
|
|
121
|
+
|
|
122
|
+
async def run_postprocessors(
|
|
123
|
+
self,
|
|
124
|
+
loop_response: LanguageModelStreamResponse,
|
|
125
|
+
) -> None:
|
|
126
|
+
task_executor = SafeTaskExecutor(
|
|
127
|
+
logger=self._logger,
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
postprocessors = self._get_valid_postprocessors_for_loop_response(loop_response)
|
|
131
|
+
|
|
132
|
+
tasks = [
|
|
133
|
+
task_executor.execute_async(
|
|
134
|
+
self.execute_postprocessors,
|
|
135
|
+
loop_response=loop_response,
|
|
136
|
+
postprocessor_instance=postprocessor,
|
|
137
|
+
)
|
|
138
|
+
for postprocessor in postprocessors
|
|
139
|
+
]
|
|
140
|
+
postprocessor_results = await asyncio.gather(*tasks)
|
|
141
|
+
|
|
142
|
+
successful_postprocessors: list[Postprocessor | ResponsesApiPostprocessor] = []
|
|
143
|
+
for i in range(len(postprocessors)):
|
|
144
|
+
if postprocessor_results[i].success:
|
|
145
|
+
successful_postprocessors.append(postprocessors[i])
|
|
146
|
+
|
|
147
|
+
modification_results = [
|
|
148
|
+
postprocessor.apply_postprocessing_to_response(loop_response) # type: ignore (checked in `get_valid_postprocessors_for_loop_response`)
|
|
149
|
+
for postprocessor in successful_postprocessors
|
|
150
|
+
]
|
|
151
|
+
|
|
152
|
+
has_been_modified = any(modification_results)
|
|
153
|
+
|
|
154
|
+
if has_been_modified:
|
|
155
|
+
self._chat_service.modify_assistant_message(
|
|
156
|
+
content=loop_response.message.text,
|
|
157
|
+
message_id=loop_response.message.id,
|
|
158
|
+
references=loop_response.message.references,
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
async def execute_postprocessors(
|
|
162
|
+
self,
|
|
163
|
+
loop_response: LanguageModelStreamResponse,
|
|
164
|
+
postprocessor_instance: Postprocessor | ResponsesApiPostprocessor,
|
|
165
|
+
) -> None:
|
|
166
|
+
await postprocessor_instance.run(loop_response) # type: ignore
|
|
167
|
+
|
|
168
|
+
async def remove_from_text(
|
|
169
|
+
self,
|
|
170
|
+
text: str,
|
|
171
|
+
) -> str:
|
|
172
|
+
for postprocessor in self._get_all_postprocessors():
|
|
173
|
+
text = await postprocessor.remove_from_text(text)
|
|
174
|
+
return text
|
|
175
|
+
|
|
176
|
+
def _get_all_postprocessors(
|
|
177
|
+
self,
|
|
178
|
+
) -> list[Postprocessor | ResponsesApiPostprocessor]:
|
|
179
|
+
return [
|
|
180
|
+
postprocessor
|
|
181
|
+
for postprocessor in [
|
|
182
|
+
self._first_postprocessor,
|
|
183
|
+
*self._postprocessors,
|
|
184
|
+
self._last_postprocessor,
|
|
185
|
+
]
|
|
186
|
+
if postprocessor is not None
|
|
187
|
+
]
|
|
188
|
+
|
|
189
|
+
def _get_valid_postprocessors_for_loop_response(
|
|
190
|
+
self, loop_response: LanguageModelStreamResponse
|
|
191
|
+
) -> list[Postprocessor | ResponsesApiPostprocessor]:
|
|
192
|
+
all_postprocessors = self._get_all_postprocessors()
|
|
193
|
+
|
|
194
|
+
postprocessors: list[Postprocessor | ResponsesApiPostprocessor] = []
|
|
195
|
+
|
|
196
|
+
if isinstance(loop_response, ResponsesLanguageModelStreamResponse):
|
|
197
|
+
"""
|
|
198
|
+
All processore can be executed, since `ResponsesLanguageModelStreamResponse`
|
|
199
|
+
is a subclass of `LanguageModelStreamResponse`
|
|
200
|
+
"""
|
|
201
|
+
postprocessors = all_postprocessors
|
|
202
|
+
else:
|
|
203
|
+
"""
|
|
204
|
+
Cannot execute Responses API-specific postprocessors
|
|
205
|
+
"""
|
|
206
|
+
postprocessors = [
|
|
207
|
+
postprocessor
|
|
208
|
+
for postprocessor in all_postprocessors
|
|
209
|
+
if isinstance(postprocessor, Postprocessor)
|
|
210
|
+
]
|
|
211
|
+
|
|
212
|
+
return postprocessors
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
from unique_toolkit.agentic.tools.schemas import ToolCallResponse
|
|
2
|
+
from unique_toolkit.content.schemas import ContentChunk, ContentReference
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class tool_chunks:
|
|
6
|
+
def __init__(self, name: str, chunks: list[ContentChunk]) -> None:
|
|
7
|
+
self.name = name
|
|
8
|
+
self.chunks = chunks
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class ReferenceManager:
|
|
12
|
+
"""
|
|
13
|
+
Manages content chunks and references extracted from tool responses.
|
|
14
|
+
|
|
15
|
+
This class is responsible for:
|
|
16
|
+
- Extracting and storing referenceable content chunks from tool responses.
|
|
17
|
+
- Managing a collection of content chunks and their associated references.
|
|
18
|
+
- Providing methods to retrieve, replace, and manipulate chunks and references.
|
|
19
|
+
- Supporting the retrieval of the latest references and their corresponding chunks.
|
|
20
|
+
|
|
21
|
+
Key Features:
|
|
22
|
+
- Chunk Extraction: Extracts content chunks from tool responses and organizes them for reference.
|
|
23
|
+
- Reference Management: Tracks references to content chunks and allows for easy retrieval.
|
|
24
|
+
- Latest Reference Access: Provides methods to fetch the most recent references and their associated chunks.
|
|
25
|
+
- Flexible Chunk Replacement: Allows for replacing the current set of chunks with a new list.
|
|
26
|
+
- Reference-to-Chunk Mapping: Matches references to their corresponding chunks based on source IDs.
|
|
27
|
+
|
|
28
|
+
The ReferenceManager serves as a utility for managing and linking content chunks with references, enabling efficient content tracking and retrieval.
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
def __init__(self):
|
|
32
|
+
self._tool_chunks: dict[str, tool_chunks] = {}
|
|
33
|
+
self._chunks: list[ContentChunk] = []
|
|
34
|
+
self._references: list[list[ContentReference]] = []
|
|
35
|
+
|
|
36
|
+
def extract_referenceable_chunks(
|
|
37
|
+
self, tool_responses: list[ToolCallResponse]
|
|
38
|
+
) -> None:
|
|
39
|
+
for tool_response in tool_responses:
|
|
40
|
+
if not tool_response.content_chunks:
|
|
41
|
+
continue
|
|
42
|
+
self._chunks.extend(tool_response.content_chunks or [])
|
|
43
|
+
self._tool_chunks[tool_response.id] = tool_chunks(
|
|
44
|
+
tool_response.name, tool_response.content_chunks
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
def get_chunks(self) -> list[ContentChunk]:
|
|
48
|
+
return self._chunks
|
|
49
|
+
|
|
50
|
+
def get_tool_chunks(self) -> dict[str, tool_chunks]:
|
|
51
|
+
return self._tool_chunks
|
|
52
|
+
|
|
53
|
+
def get_chunks_of_all_tools(self) -> list[list[ContentChunk]]:
|
|
54
|
+
return [tool_chunks.chunks for tool_chunks in self._tool_chunks.values()]
|
|
55
|
+
|
|
56
|
+
def get_chunks_of_tool(self, tool_call_id: str) -> list[ContentChunk]:
|
|
57
|
+
return self._tool_chunks.get(tool_call_id, tool_chunks("", [])).chunks
|
|
58
|
+
|
|
59
|
+
def replace_chunks_of_tool(
|
|
60
|
+
self, tool_call_id: str, chunks: list[ContentChunk]
|
|
61
|
+
) -> None:
|
|
62
|
+
if tool_call_id in self._tool_chunks:
|
|
63
|
+
self._tool_chunks[tool_call_id].chunks = chunks
|
|
64
|
+
|
|
65
|
+
def replace(self, chunks: list[ContentChunk]):
|
|
66
|
+
self._chunks = chunks
|
|
67
|
+
|
|
68
|
+
def add_references(
|
|
69
|
+
self,
|
|
70
|
+
references: list[ContentReference],
|
|
71
|
+
):
|
|
72
|
+
self._references.append(references)
|
|
73
|
+
|
|
74
|
+
def get_references(
|
|
75
|
+
self,
|
|
76
|
+
) -> list[list[ContentReference]]:
|
|
77
|
+
return self._references
|
|
78
|
+
|
|
79
|
+
def get_latest_references(
|
|
80
|
+
self,
|
|
81
|
+
) -> list[ContentReference]:
|
|
82
|
+
if not self._references:
|
|
83
|
+
return []
|
|
84
|
+
return self._references[-1]
|
|
85
|
+
|
|
86
|
+
def get_latest_referenced_chunks(self) -> list[ContentChunk]:
|
|
87
|
+
if not self._references:
|
|
88
|
+
return []
|
|
89
|
+
return self._get_referenced_chunks_from_references(self._references[-1])
|
|
90
|
+
|
|
91
|
+
def _get_referenced_chunks_from_references(
|
|
92
|
+
self,
|
|
93
|
+
references: list[ContentReference],
|
|
94
|
+
) -> list[ContentChunk]:
|
|
95
|
+
"""
|
|
96
|
+
Get _referenced_chunks by matching sourceId from _references with merged id and chunk_id from _chunks.
|
|
97
|
+
"""
|
|
98
|
+
referenced_chunks: list[ContentChunk] = []
|
|
99
|
+
for ref in references:
|
|
100
|
+
for chunk in self._chunks:
|
|
101
|
+
if ref.source_id == f"{chunk.id}_{chunk.chunk_id}":
|
|
102
|
+
referenced_chunks.append(chunk)
|
|
103
|
+
return referenced_chunks
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
from unique_toolkit.agentic.responses_api.postprocessors.code_display import (
|
|
2
|
+
ShowExecutedCodePostprocessor,
|
|
3
|
+
ShowExecutedCodePostprocessorConfig,
|
|
4
|
+
)
|
|
5
|
+
from unique_toolkit.agentic.responses_api.postprocessors.generated_files import (
|
|
6
|
+
DisplayCodeInterpreterFilesPostProcessor,
|
|
7
|
+
DisplayCodeInterpreterFilesPostProcessorConfig,
|
|
8
|
+
)
|
|
9
|
+
from unique_toolkit.agentic.responses_api.stream_handler import (
|
|
10
|
+
ResponsesStreamingHandler,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
__all__ = [
|
|
14
|
+
"ShowExecutedCodePostprocessor",
|
|
15
|
+
"ShowExecutedCodePostprocessorConfig",
|
|
16
|
+
"DisplayCodeInterpreterFilesPostProcessorConfig",
|
|
17
|
+
"DisplayCodeInterpreterFilesPostProcessor",
|
|
18
|
+
"ResponsesStreamingHandler",
|
|
19
|
+
]
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import logging
|
|
3
|
+
import re
|
|
4
|
+
from typing import override
|
|
5
|
+
|
|
6
|
+
from pydantic import BaseModel, Field
|
|
7
|
+
from pydantic.json_schema import SkipJsonSchema
|
|
8
|
+
|
|
9
|
+
from unique_toolkit.agentic.postprocessor.postprocessor_manager import (
|
|
10
|
+
ResponsesApiPostprocessor,
|
|
11
|
+
)
|
|
12
|
+
from unique_toolkit.agentic.tools.config import get_configuration_dict
|
|
13
|
+
from unique_toolkit.language_model.schemas import ResponsesLanguageModelStreamResponse
|
|
14
|
+
|
|
15
|
+
_TEMPLATE = """
|
|
16
|
+
<details><summary>Code Interpreter Call</summary>
|
|
17
|
+
|
|
18
|
+
```python
|
|
19
|
+
{code}
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
</details>
|
|
23
|
+
</br>
|
|
24
|
+
|
|
25
|
+
""".lstrip()
|
|
26
|
+
|
|
27
|
+
logger = logging.getLogger(__name__)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class ShowExecutedCodePostprocessorConfig(BaseModel):
|
|
31
|
+
model_config = get_configuration_dict()
|
|
32
|
+
remove_from_history: SkipJsonSchema[bool] = (
|
|
33
|
+
Field( # At the moment, it's not possible to keep executed code in the history
|
|
34
|
+
default=True,
|
|
35
|
+
description="If set, the code interpreter call will be removed from the history on subsequent calls to the assistant.",
|
|
36
|
+
)
|
|
37
|
+
)
|
|
38
|
+
sleep_time_before_display: float = Field(
|
|
39
|
+
default=0.2,
|
|
40
|
+
description="Time to sleep before displaying the executed code. Please increase this value if you experience rendering issues.",
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class ShowExecutedCodePostprocessor(ResponsesApiPostprocessor):
|
|
45
|
+
def __init__(self, config: ShowExecutedCodePostprocessorConfig):
|
|
46
|
+
super().__init__(self.__class__.__name__)
|
|
47
|
+
self._config = config
|
|
48
|
+
|
|
49
|
+
@override
|
|
50
|
+
async def run(self, loop_response: ResponsesLanguageModelStreamResponse) -> None:
|
|
51
|
+
await asyncio.sleep(self._config.sleep_time_before_display)
|
|
52
|
+
|
|
53
|
+
@override
|
|
54
|
+
def apply_postprocessing_to_response(
|
|
55
|
+
self, loop_response: ResponsesLanguageModelStreamResponse
|
|
56
|
+
) -> bool:
|
|
57
|
+
prepended_text = ""
|
|
58
|
+
for output in loop_response.code_interpreter_calls:
|
|
59
|
+
prepended_text += _TEMPLATE.format(code=output.code)
|
|
60
|
+
|
|
61
|
+
loop_response.message.text = prepended_text + loop_response.message.text
|
|
62
|
+
|
|
63
|
+
return prepended_text != ""
|
|
64
|
+
|
|
65
|
+
@override
|
|
66
|
+
async def remove_from_text(self, text) -> str:
|
|
67
|
+
if not self._config.remove_from_history:
|
|
68
|
+
return text
|
|
69
|
+
# Remove code interpreter blocks using regex
|
|
70
|
+
pattern = r"<details><summary>Code Interpreter Call</summary>.*?</details>"
|
|
71
|
+
return re.sub(pattern, "", text, flags=re.DOTALL)
|
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import logging
|
|
3
|
+
import re
|
|
4
|
+
from mimetypes import guess_type
|
|
5
|
+
from typing import NamedTuple, override
|
|
6
|
+
|
|
7
|
+
from openai import AsyncOpenAI
|
|
8
|
+
from openai.types.responses.response_output_text import AnnotationContainerFileCitation
|
|
9
|
+
from pydantic import BaseModel, Field, RootModel
|
|
10
|
+
|
|
11
|
+
from unique_toolkit.agentic.postprocessor.postprocessor_manager import (
|
|
12
|
+
ResponsesApiPostprocessor,
|
|
13
|
+
)
|
|
14
|
+
from unique_toolkit.agentic.short_term_memory_manager.persistent_short_term_memory_manager import (
|
|
15
|
+
PersistentShortMemoryManager,
|
|
16
|
+
)
|
|
17
|
+
from unique_toolkit.agentic.tools.config import get_configuration_dict
|
|
18
|
+
from unique_toolkit.agentic.tools.utils import failsafe_async
|
|
19
|
+
from unique_toolkit.content.schemas import ContentReference
|
|
20
|
+
from unique_toolkit.content.service import ContentService
|
|
21
|
+
from unique_toolkit.language_model.schemas import ResponsesLanguageModelStreamResponse
|
|
22
|
+
from unique_toolkit.services.knowledge_base import KnowledgeBaseService
|
|
23
|
+
from unique_toolkit.short_term_memory.service import ShortTermMemoryService
|
|
24
|
+
|
|
25
|
+
logger = logging.getLogger(__name__)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class DisplayCodeInterpreterFilesPostProcessorConfig(BaseModel):
|
|
29
|
+
model_config = get_configuration_dict()
|
|
30
|
+
upload_scope_id: str = Field(
|
|
31
|
+
default="<SCOPE_ID_PLACEHOLDER>",
|
|
32
|
+
description="The scope ID where the generated files will be uploaded.",
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
file_download_failed_message: str = Field(
|
|
36
|
+
default="⚠️ File download failed ...",
|
|
37
|
+
description="The message to display when a file download fails.",
|
|
38
|
+
)
|
|
39
|
+
max_concurrent_file_downloads: int = Field(
|
|
40
|
+
default=10,
|
|
41
|
+
description="The maximum number of concurrent file downloads.",
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class _ContentInfo(NamedTuple):
|
|
46
|
+
filename: str
|
|
47
|
+
content_id: str
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
_SHORT_TERM_MEMORY_KEY = "code_interpreter_files"
|
|
51
|
+
_DisplayedFilesShortTermMemorySchema = RootModel[list[_ContentInfo]]
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _init_short_term_memory_manager(
|
|
55
|
+
company_id: str, user_id: str, chat_id: str
|
|
56
|
+
) -> PersistentShortMemoryManager[_DisplayedFilesShortTermMemorySchema]:
|
|
57
|
+
short_term_memory_service = ShortTermMemoryService(
|
|
58
|
+
company_id=company_id,
|
|
59
|
+
user_id=user_id,
|
|
60
|
+
chat_id=chat_id,
|
|
61
|
+
message_id=None,
|
|
62
|
+
)
|
|
63
|
+
return PersistentShortMemoryManager(
|
|
64
|
+
short_term_memory_service=short_term_memory_service,
|
|
65
|
+
short_term_memory_schema=_DisplayedFilesShortTermMemorySchema,
|
|
66
|
+
short_term_memory_name=_SHORT_TERM_MEMORY_KEY,
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class DisplayCodeInterpreterFilesPostProcessor(
|
|
71
|
+
ResponsesApiPostprocessor,
|
|
72
|
+
):
|
|
73
|
+
def __init__(
|
|
74
|
+
self,
|
|
75
|
+
client: AsyncOpenAI,
|
|
76
|
+
content_service: ContentService | KnowledgeBaseService,
|
|
77
|
+
config: DisplayCodeInterpreterFilesPostProcessorConfig,
|
|
78
|
+
# Short term memory arguments, we prefer to explicitely pass the required auth variables
|
|
79
|
+
# as it is crucial that we use chat-level short term memory not to leak user files to other chats.
|
|
80
|
+
# Technically, short term memory can be scoped company-level, we would like to ensure this case is avoided.
|
|
81
|
+
company_id: str | None = None,
|
|
82
|
+
user_id: str | None = None,
|
|
83
|
+
chat_id: str | None = None,
|
|
84
|
+
) -> None:
|
|
85
|
+
super().__init__(self.__class__.__name__)
|
|
86
|
+
self._content_service = content_service
|
|
87
|
+
self._config = config
|
|
88
|
+
self._client = client
|
|
89
|
+
|
|
90
|
+
self._short_term_memory_manager = None
|
|
91
|
+
if chat_id is not None and user_id is not None and company_id is not None:
|
|
92
|
+
self._short_term_memory_manager = _init_short_term_memory_manager(
|
|
93
|
+
company_id=company_id,
|
|
94
|
+
user_id=user_id,
|
|
95
|
+
chat_id=chat_id,
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
@override
|
|
99
|
+
async def run(self, loop_response: ResponsesLanguageModelStreamResponse) -> None:
|
|
100
|
+
logger.info("Fetching and adding code interpreter files to the response")
|
|
101
|
+
|
|
102
|
+
container_files = loop_response.container_files
|
|
103
|
+
logger.info("Found %s container files", len(container_files))
|
|
104
|
+
|
|
105
|
+
self._content_map: dict[str, str | None] = await self._load_previous_files() # type: ignore
|
|
106
|
+
|
|
107
|
+
semaphore = asyncio.Semaphore(self._config.max_concurrent_file_downloads)
|
|
108
|
+
tasks = [
|
|
109
|
+
self._download_and_upload_container_files_to_knowledge_base(
|
|
110
|
+
citation, semaphore
|
|
111
|
+
)
|
|
112
|
+
for citation in container_files
|
|
113
|
+
]
|
|
114
|
+
results = await asyncio.gather(*tasks)
|
|
115
|
+
|
|
116
|
+
for citation, result in zip(container_files, results):
|
|
117
|
+
# Overwrite if file has been re-generated
|
|
118
|
+
self._content_map[citation.filename] = (
|
|
119
|
+
result.content_id if result is not None else None
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
await self._save_generated_files(
|
|
123
|
+
[
|
|
124
|
+
_ContentInfo(filename=filename, content_id=content_id)
|
|
125
|
+
for filename, content_id in self._content_map.items()
|
|
126
|
+
if content_id is not None
|
|
127
|
+
]
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
@override
|
|
131
|
+
def apply_postprocessing_to_response(
|
|
132
|
+
self, loop_response: ResponsesLanguageModelStreamResponse
|
|
133
|
+
) -> bool:
|
|
134
|
+
ref_number = _get_next_ref_number(loop_response.message.references)
|
|
135
|
+
changed = False
|
|
136
|
+
|
|
137
|
+
for filename, content_id in self._content_map.items():
|
|
138
|
+
if content_id is None:
|
|
139
|
+
loop_response.message.text, replaced = _replace_container_file_error(
|
|
140
|
+
text=loop_response.message.text,
|
|
141
|
+
filename=filename,
|
|
142
|
+
error_message=self._config.file_download_failed_message,
|
|
143
|
+
)
|
|
144
|
+
changed |= replaced
|
|
145
|
+
continue
|
|
146
|
+
|
|
147
|
+
is_image = (guess_type(filename)[0] or "").startswith("image/")
|
|
148
|
+
|
|
149
|
+
# Images
|
|
150
|
+
if is_image:
|
|
151
|
+
loop_response.message.text, replaced = (
|
|
152
|
+
_replace_container_image_citation(
|
|
153
|
+
text=loop_response.message.text,
|
|
154
|
+
filename=filename,
|
|
155
|
+
content_id=content_id,
|
|
156
|
+
)
|
|
157
|
+
)
|
|
158
|
+
changed |= replaced
|
|
159
|
+
|
|
160
|
+
# Files
|
|
161
|
+
else:
|
|
162
|
+
loop_response.message.text, replaced = _replace_container_file_citation(
|
|
163
|
+
text=loop_response.message.text,
|
|
164
|
+
filename=filename,
|
|
165
|
+
ref_number=ref_number,
|
|
166
|
+
)
|
|
167
|
+
changed |= replaced
|
|
168
|
+
|
|
169
|
+
if replaced and not is_image:
|
|
170
|
+
loop_response.message.references.append(
|
|
171
|
+
ContentReference(
|
|
172
|
+
sequence_number=ref_number,
|
|
173
|
+
source_id=content_id,
|
|
174
|
+
source="node-ingestion-chunks",
|
|
175
|
+
url=f"unique://content/{content_id}",
|
|
176
|
+
name=filename,
|
|
177
|
+
)
|
|
178
|
+
)
|
|
179
|
+
ref_number += 1
|
|
180
|
+
return changed
|
|
181
|
+
|
|
182
|
+
@override
|
|
183
|
+
async def remove_from_text(self, text) -> str:
|
|
184
|
+
return text
|
|
185
|
+
|
|
186
|
+
@failsafe_async(failure_return_value=None, logger=logger)
|
|
187
|
+
async def _download_and_upload_container_files_to_knowledge_base(
|
|
188
|
+
self,
|
|
189
|
+
container_file: AnnotationContainerFileCitation,
|
|
190
|
+
semaphore: asyncio.Semaphore,
|
|
191
|
+
) -> _ContentInfo | None:
|
|
192
|
+
async with semaphore:
|
|
193
|
+
logger.info("Fetching file content for %s", container_file.filename)
|
|
194
|
+
file_content = await self._client.containers.files.content.retrieve(
|
|
195
|
+
container_id=container_file.container_id, file_id=container_file.file_id
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
logger.info(
|
|
199
|
+
"Uploading file content for %s to knowledge base",
|
|
200
|
+
container_file.filename,
|
|
201
|
+
)
|
|
202
|
+
content = await self._content_service.upload_content_from_bytes_async(
|
|
203
|
+
content=file_content.content,
|
|
204
|
+
content_name=container_file.filename,
|
|
205
|
+
skip_ingestion=True,
|
|
206
|
+
mime_type=guess_type(container_file.filename)[0] or "text/plain",
|
|
207
|
+
scope_id=self._config.upload_scope_id,
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
return _ContentInfo(filename=container_file.filename, content_id=content.id)
|
|
211
|
+
|
|
212
|
+
async def _load_previous_files(self) -> dict[str, str]:
|
|
213
|
+
if self._short_term_memory_manager is None:
|
|
214
|
+
return {}
|
|
215
|
+
|
|
216
|
+
logger.info(
|
|
217
|
+
"Loading previously generated code interpreter files from short term memory"
|
|
218
|
+
)
|
|
219
|
+
memory = await self._short_term_memory_manager.load_async()
|
|
220
|
+
|
|
221
|
+
if memory is None:
|
|
222
|
+
logger.info(
|
|
223
|
+
"No previously generated code interpreter files found in short term memory"
|
|
224
|
+
)
|
|
225
|
+
return {}
|
|
226
|
+
|
|
227
|
+
logger.info(
|
|
228
|
+
"Found %s previously generated code interpreter files", len(memory.root)
|
|
229
|
+
)
|
|
230
|
+
|
|
231
|
+
return {content.filename: content.content_id for content in memory.root}
|
|
232
|
+
|
|
233
|
+
async def _save_generated_files(self, content_infos: list[_ContentInfo]) -> None:
|
|
234
|
+
if self._short_term_memory_manager is None or len(content_infos) == 0:
|
|
235
|
+
return
|
|
236
|
+
|
|
237
|
+
await self._short_term_memory_manager.save_async(
|
|
238
|
+
_DisplayedFilesShortTermMemorySchema(root=content_infos)
|
|
239
|
+
)
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
def _get_next_ref_number(references: list[ContentReference]) -> int:
|
|
243
|
+
max_ref_number = 0
|
|
244
|
+
for ref in references:
|
|
245
|
+
max_ref_number = max(max_ref_number, ref.sequence_number)
|
|
246
|
+
return max_ref_number + 1
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
def _replace_container_file_error(
|
|
250
|
+
text: str, filename: str, error_message: str
|
|
251
|
+
) -> tuple[str, bool]:
|
|
252
|
+
image_markdown = rf"!?\[.*?\]\(sandbox:/mnt/data/{re.escape(filename)}\)"
|
|
253
|
+
|
|
254
|
+
if not re.search(image_markdown, text):
|
|
255
|
+
logger.info("No image markdown found for %s", filename)
|
|
256
|
+
return text, False
|
|
257
|
+
|
|
258
|
+
logger.info("Displaying image %s", filename)
|
|
259
|
+
return re.sub(
|
|
260
|
+
image_markdown,
|
|
261
|
+
error_message,
|
|
262
|
+
text,
|
|
263
|
+
), True
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
def _replace_container_image_citation(
|
|
267
|
+
text: str, filename: str, content_id: str
|
|
268
|
+
) -> tuple[str, bool]:
|
|
269
|
+
image_markdown = rf"!?\[.*?\]\(sandbox:/mnt/data/{re.escape(filename)}\)"
|
|
270
|
+
|
|
271
|
+
if not re.search(image_markdown, text):
|
|
272
|
+
logger.info("No image markdown found for %s", filename)
|
|
273
|
+
return text, False
|
|
274
|
+
|
|
275
|
+
logger.info("Displaying image %s", filename)
|
|
276
|
+
return re.sub(
|
|
277
|
+
image_markdown,
|
|
278
|
+
f"",
|
|
279
|
+
text,
|
|
280
|
+
), True
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
def _replace_container_file_citation(
|
|
284
|
+
text: str, filename: str, ref_number: int
|
|
285
|
+
) -> tuple[str, bool]:
|
|
286
|
+
file_markdown = rf"\[.*?\]\(sandbox:/mnt/data/{re.escape(filename)}\)"
|
|
287
|
+
|
|
288
|
+
if not re.search(file_markdown, text):
|
|
289
|
+
logger.info("No file markdown found for %s", filename)
|
|
290
|
+
return text, False
|
|
291
|
+
|
|
292
|
+
logger.info("Displaying file %s", filename)
|
|
293
|
+
return re.sub(
|
|
294
|
+
file_markdown,
|
|
295
|
+
f"<sup>{ref_number}</sup>",
|
|
296
|
+
text,
|
|
297
|
+
), True
|