unique_toolkit 1.16.5__py3-none-any.whl → 1.17.1__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 unique_toolkit might be problematic. Click here for more details.

@@ -1,3 +1,5 @@
1
+ import regex as re
2
+
1
3
  from unique_toolkit.agentic.evaluation.evaluation_manager import Evaluation
2
4
  from unique_toolkit.agentic.evaluation.hallucination.constants import (
3
5
  HallucinationConfig,
@@ -40,13 +42,20 @@ class HallucinationEvaluation(Evaluation):
40
42
  async def run(
41
43
  self, loop_response: LanguageModelStreamResponse
42
44
  ) -> EvaluationMetricResult: # type: ignore
43
- chunks = self._reference_manager.get_latest_referenced_chunks()
45
+ all_chunks = self._reference_manager.get_chunks()
46
+ # source numbers from original text
47
+ ref_pattern = r"\[source(\d+)\]"
48
+ original_text = loop_response.message.original_text
49
+ source_number_matches = re.findall(ref_pattern, original_text)
50
+ source_numbers = {int(num) for num in source_number_matches}
51
+
52
+ referenced_chunks = [all_chunks[idx] for idx in source_numbers]
44
53
 
45
54
  evaluation_result: EvaluationMetricResult = await check_hallucination(
46
55
  company_id=self._company_id,
47
56
  input=EvaluationMetricInput(
48
57
  input_text=self._user_message,
49
- context_texts=[context.text for context in chunks],
58
+ context_texts=[context.text for context in referenced_chunks],
50
59
  history_messages=[], # TODO include loop_history messages
51
60
  output_text=loop_response.message.text,
52
61
  ),
@@ -1,122 +1,174 @@
1
1
  import re
2
- from abc import ABC, abstractmethod
3
- from typing import Literal, override
2
+ from typing import Literal
4
3
 
5
4
  from unique_toolkit.agentic.tools.a2a.postprocessing.config import (
6
5
  SubAgentResponseDisplayMode,
7
6
  )
8
7
 
9
8
 
10
- class _ResponseDisplayHandler(ABC):
11
- @abstractmethod
12
- def build_response_display(
13
- self, display_name: str, assistant_id: str, answer: str
14
- ) -> str:
15
- raise NotImplementedError()
9
+ def _wrap_text(text: str, start_text: str, end_text: str) -> str:
10
+ text = text.strip()
11
+ start_text = start_text.strip()
12
+ end_text = end_text.strip()
16
13
 
17
- @abstractmethod
18
- def remove_response_display(self, assistant_id: str, text: str) -> str:
19
- raise NotImplementedError()
14
+ if start_text != "":
15
+ start_text = f"{start_text}\n"
20
16
 
17
+ if end_text != "":
18
+ end_text = f"\n{end_text}"
21
19
 
22
- class _DetailsResponseDisplayHandler(_ResponseDisplayHandler):
23
- def __init__(self, mode: Literal["open", "closed"]) -> None:
24
- self._mode = mode
20
+ return f"{start_text}{text}{end_text}"
25
21
 
26
- DETAILS_CLOSED_TEMPLATE = (
27
- "<details><summary>{display_name}</summary>\n"
28
- "\n"
29
- '<div style="display: none;">{assistant_id}</div>\n'
30
- "\n"
31
- "{answer}\n"
32
- "</details>\n"
33
- "<br>\n"
34
- "\n"
35
- )
36
22
 
37
- DETAILS_OPEN_TEMPLATE = (
38
- "<details open><summary>{display_name}</summary>\n"
39
- "\n"
40
- '<div style="display: none;">{assistant_id}</div>\n'
41
- "\n"
42
- "{answer}\n"
43
- "\n"
44
- "</details>\n"
45
- "<br>\n"
46
- "\n"
47
- )
23
+ def _join_text_blocks(*blocks: str, sep: str = "\n") -> str:
24
+ return sep.join(block.strip() for block in blocks)
25
+
26
+
27
+ def _wrap_with_details_tag(
28
+ text, mode: Literal["open", "closed"], summary_name: str | None = None
29
+ ) -> str:
30
+ if summary_name is not None:
31
+ summary_tag = _wrap_text(summary_name, "<summary>", "</summary>")
32
+ text = _join_text_blocks(summary_tag, text)
33
+
34
+ if mode == "open":
35
+ text = _wrap_text(text, "<details open>", "</details>")
36
+ else:
37
+ text = _wrap_text(text, "<details>", "</details>")
38
+
39
+ return text
40
+
41
+
42
+ _BLOCK_BORDER_STYLE = (
43
+ "overflow-y: auto; border: 1px solid #ccc; padding: 8px; margin-top: 8px;"
44
+ )
45
+
46
+
47
+ def _wrap_with_block_border(text: str) -> str:
48
+ return _wrap_text(text, f"<div style='{_BLOCK_BORDER_STYLE}'>", "</div>")
49
+
50
+
51
+ _QUOTE_BORDER_STYLE = (
52
+ "margin-left: 20px; border-left: 2px solid #ccc; padding-left: 10px;"
53
+ )
54
+
55
+
56
+ def _wrap_with_quote_border(text: str) -> str:
57
+ return _wrap_text(text, f"<div style='{_QUOTE_BORDER_STYLE}'>", "</div>")
58
+
59
+
60
+ def _wrap_strong(text: str) -> str:
61
+ return _wrap_text(text, "<strong>", "</strong>")
62
+
63
+
64
+ def _wrap_hidden_div(text: str) -> str:
65
+ return _wrap_text(text, '<div style="display: none;">', "</div>")
66
+
67
+
68
+ def _add_line_break(text: str, before: bool = True, after: bool = True) -> str:
69
+ start_tag = ""
70
+ if before:
71
+ start_tag = "<br>"
48
72
 
49
- def _get_detect_re(self, assistant_id: str) -> str:
50
- if self._mode == "open":
51
- return (
52
- r"(?s)<details open>\s*"
53
- r"<summary>(.*?)</summary>\s*"
54
- rf"<div style=\"display: none;\">{re.escape(assistant_id)}</div>\s*"
55
- r"(.*?)\s*"
56
- r"</details>\s*"
57
- r"<br>\s*"
73
+ end_tag = ""
74
+ if after:
75
+ end_tag = "<br>"
76
+
77
+ return _wrap_text(text, start_tag, end_tag)
78
+
79
+
80
+ def _get_display_template(
81
+ mode: SubAgentResponseDisplayMode,
82
+ add_quote_border: bool,
83
+ add_block_border: bool,
84
+ answer_placeholder: str = "answer",
85
+ assistant_id_placeholder: str = "assistant_id",
86
+ display_name_placeholder: str = "display_name",
87
+ ) -> str:
88
+ if mode == SubAgentResponseDisplayMode.HIDDEN:
89
+ return ""
90
+
91
+ assistant_id_placeholder = _wrap_hidden_div("{%s}" % assistant_id_placeholder)
92
+ display_name_placeholder = _wrap_strong("{%s}" % display_name_placeholder)
93
+ template = _join_text_blocks(
94
+ assistant_id_placeholder, "{%s}" % answer_placeholder, sep="\n\n"
95
+ ) # Double line break is needed for markdown formatting
96
+
97
+ if add_quote_border:
98
+ template = _wrap_with_quote_border(template)
99
+
100
+ match mode:
101
+ case SubAgentResponseDisplayMode.DETAILS_OPEN:
102
+ template = _wrap_with_details_tag(
103
+ template, "open", display_name_placeholder
104
+ )
105
+ case SubAgentResponseDisplayMode.DETAILS_CLOSED:
106
+ template = _wrap_with_details_tag(
107
+ template, "closed", display_name_placeholder
58
108
  )
59
- else:
60
- return (
61
- r"(?s)<details>\s*"
62
- r"<summary>(.*?)</summary>\s*"
63
- rf"<div style=\"display: none;\">{re.escape(assistant_id)}</div>\s*"
64
- r"(.*?)\s*"
65
- r"</details>\s*"
66
- r"<br>\s*"
109
+ case SubAgentResponseDisplayMode.PLAIN:
110
+ display_name_placeholder = _add_line_break(
111
+ display_name_placeholder, before=False, after=True
67
112
  )
113
+ template = _join_text_blocks(display_name_placeholder, template)
114
+ # Add a hidden block border to seperate sub agent answers from the rest of the text.
115
+ hidden_block_border = _wrap_hidden_div("sub_agent_answer_block")
116
+ template = _join_text_blocks(template, hidden_block_border)
68
117
 
69
- def _get_template(self) -> str:
70
- if self._mode == "open":
71
- return self.DETAILS_OPEN_TEMPLATE
72
- else:
73
- return self.DETAILS_CLOSED_TEMPLATE
118
+ if add_block_border:
119
+ template = _wrap_with_block_border(template)
74
120
 
75
- @override
76
- def build_response_display(
77
- self, display_name: str, assistant_id: str, answer: str
78
- ) -> str:
79
- return self._get_template().format(
80
- assistant_id=assistant_id, display_name=display_name, answer=answer
81
- )
121
+ return template
82
122
 
83
- @override
84
- def remove_response_display(self, assistant_id: str, text: str) -> str:
85
- return re.sub(self._get_detect_re(assistant_id=assistant_id), "", text)
86
123
 
124
+ def _get_display_removal_re(
125
+ assistant_id: str,
126
+ mode: SubAgentResponseDisplayMode,
127
+ add_quote_border: bool,
128
+ add_block_border: bool,
129
+ ) -> re.Pattern[str]:
130
+ template = _get_display_template(
131
+ mode=mode,
132
+ add_quote_border=add_quote_border,
133
+ add_block_border=add_block_border,
134
+ )
135
+
136
+ pattern = template.format(
137
+ assistant_id=re.escape(assistant_id), answer=r"(.*?)", display_name=r"(.*?)"
138
+ )
87
139
 
88
- _DISPLAY_HANDLERS = {
89
- SubAgentResponseDisplayMode.DETAILS_OPEN: _DetailsResponseDisplayHandler(
90
- mode="open"
91
- ),
92
- SubAgentResponseDisplayMode.DETAILS_CLOSED: _DetailsResponseDisplayHandler(
93
- mode="closed"
94
- ),
95
- }
140
+ return re.compile(pattern, flags=re.DOTALL)
96
141
 
97
142
 
98
143
  def _build_sub_agent_answer_display(
99
144
  display_name: str,
100
145
  display_mode: SubAgentResponseDisplayMode,
146
+ add_quote_border: bool,
147
+ add_block_border: bool,
101
148
  answer: str,
102
149
  assistant_id: str,
103
150
  ) -> str:
104
- if display_mode not in _DISPLAY_HANDLERS:
105
- return ""
106
-
107
- display_f = _DISPLAY_HANDLERS[display_mode]
108
-
109
- return display_f.build_response_display(
151
+ template = _get_display_template(
152
+ mode=display_mode,
153
+ add_quote_border=add_quote_border,
154
+ add_block_border=add_block_border,
155
+ )
156
+ return template.format(
110
157
  display_name=display_name, answer=answer, assistant_id=assistant_id
111
158
  )
112
159
 
113
160
 
114
161
  def _remove_sub_agent_answer_from_text(
115
- display_mode: SubAgentResponseDisplayMode, text: str, assistant_id: str
162
+ display_mode: SubAgentResponseDisplayMode,
163
+ add_quote_border: bool,
164
+ add_block_border: bool,
165
+ text: str,
166
+ assistant_id: str,
116
167
  ) -> str:
117
- if display_mode not in _DISPLAY_HANDLERS:
118
- return text
119
-
120
- display_f = _DISPLAY_HANDLERS[display_mode]
121
-
122
- return display_f.remove_response_display(assistant_id=assistant_id, text=text)
168
+ pattern = _get_display_removal_re(
169
+ assistant_id=assistant_id,
170
+ mode=display_mode,
171
+ add_quote_border=add_quote_border,
172
+ add_block_border=add_block_border,
173
+ )
174
+ return re.sub(pattern, "", text)
@@ -9,6 +9,7 @@ class SubAgentResponseDisplayMode(StrEnum):
9
9
  HIDDEN = "hidden"
10
10
  DETAILS_OPEN = "details_open"
11
11
  DETAILS_CLOSED = "details_closed"
12
+ PLAIN = "plain"
12
13
 
13
14
 
14
15
  class SubAgentDisplayConfig(BaseModel):
@@ -22,3 +23,11 @@ class SubAgentDisplayConfig(BaseModel):
22
23
  default=True,
23
24
  description="If set, sub agent responses will be removed from the history on subsequent calls to the assistant.",
24
25
  )
26
+ add_quote_border: bool = Field(
27
+ default=True,
28
+ description="If set, a quote border is added to the left of the sub agent response.",
29
+ )
30
+ add_block_border: bool = Field(
31
+ default=False,
32
+ description="If set, a block border is added around the sub agent response.",
33
+ )
@@ -107,6 +107,8 @@ class SubAgentResponsesPostprocessor(Postprocessor):
107
107
  assistant_id=assistant_id,
108
108
  display_mode=display_mode,
109
109
  answer=message["text"],
110
+ add_quote_border=tool_info["display_config"].add_quote_border,
111
+ add_block_border=tool_info["display_config"].add_block_border,
110
112
  )
111
113
  )
112
114
 
@@ -122,7 +124,9 @@ class SubAgentResponsesPostprocessor(Postprocessor):
122
124
  for ref in message["references"]
123
125
  )
124
126
 
125
- loop_response.message.text = "\n".join(answers) + loop_response.message.text
127
+ loop_response.message.text = (
128
+ "\n\n".join(answers) + "<br>\n\n" + loop_response.message.text.strip()
129
+ )
126
130
 
127
131
  return True
128
132
 
@@ -135,6 +139,8 @@ class SubAgentResponsesPostprocessor(Postprocessor):
135
139
  display_mode=display_config.mode,
136
140
  text=text,
137
141
  assistant_id=assistant_id,
142
+ add_quote_border=display_config.add_quote_border,
143
+ add_block_border=display_config.add_block_border,
138
144
  )
139
145
  return text
140
146