vectara-agentic 0.2.24__py3-none-any.whl → 0.3.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 vectara-agentic might be problematic. Click here for more details.
- tests/test_agent.py +2 -2
- tests/test_hhem.py +100 -0
- tests/test_return_direct.py +2 -6
- tests/test_tools.py +1 -1
- vectara_agentic/_callback.py +26 -18
- vectara_agentic/_prompts.py +4 -0
- vectara_agentic/_version.py +1 -1
- vectara_agentic/agent.py +69 -12
- vectara_agentic/hhem.py +45 -0
- vectara_agentic/llm_utils.py +34 -10
- vectara_agentic/tool_utils.py +177 -15
- vectara_agentic/tools.py +99 -99
- vectara_agentic/types.py +22 -1
- {vectara_agentic-0.2.24.dist-info → vectara_agentic-0.3.0.dist-info}/METADATA +24 -2
- {vectara_agentic-0.2.24.dist-info → vectara_agentic-0.3.0.dist-info}/RECORD +18 -16
- {vectara_agentic-0.2.24.dist-info → vectara_agentic-0.3.0.dist-info}/WHEEL +0 -0
- {vectara_agentic-0.2.24.dist-info → vectara_agentic-0.3.0.dist-info}/licenses/LICENSE +0 -0
- {vectara_agentic-0.2.24.dist-info → vectara_agentic-0.3.0.dist-info}/top_level.txt +0 -0
vectara_agentic/tool_utils.py
CHANGED
|
@@ -6,8 +6,16 @@ import inspect
|
|
|
6
6
|
import re
|
|
7
7
|
|
|
8
8
|
from typing import (
|
|
9
|
-
Callable,
|
|
10
|
-
|
|
9
|
+
Callable,
|
|
10
|
+
List,
|
|
11
|
+
Dict,
|
|
12
|
+
Any,
|
|
13
|
+
Optional,
|
|
14
|
+
Union,
|
|
15
|
+
Type,
|
|
16
|
+
Tuple,
|
|
17
|
+
get_origin,
|
|
18
|
+
get_args,
|
|
11
19
|
)
|
|
12
20
|
from pydantic import BaseModel, create_model
|
|
13
21
|
from pydantic_core import PydanticUndefined
|
|
@@ -83,7 +91,7 @@ class VectaraTool(FunctionTool):
|
|
|
83
91
|
tool_metadata,
|
|
84
92
|
callback,
|
|
85
93
|
async_callback,
|
|
86
|
-
partial_params
|
|
94
|
+
partial_params,
|
|
87
95
|
)
|
|
88
96
|
vectara_tool = cls(
|
|
89
97
|
tool_type=tool_type,
|
|
@@ -119,7 +127,8 @@ class VectaraTool(FunctionTool):
|
|
|
119
127
|
self, *args: Any, ctx: Optional[Context] = None, **kwargs: Any
|
|
120
128
|
) -> ToolOutput:
|
|
121
129
|
try:
|
|
122
|
-
|
|
130
|
+
result = super().call(*args, ctx=ctx, **kwargs)
|
|
131
|
+
return self._format_tool_output(result)
|
|
123
132
|
except TypeError as e:
|
|
124
133
|
sig = inspect.signature(self.metadata.fn_schema)
|
|
125
134
|
valid_parameters = list(sig.parameters.keys())
|
|
@@ -148,7 +157,8 @@ class VectaraTool(FunctionTool):
|
|
|
148
157
|
self, *args: Any, ctx: Optional[Context] = None, **kwargs: Any
|
|
149
158
|
) -> ToolOutput:
|
|
150
159
|
try:
|
|
151
|
-
|
|
160
|
+
result = await super().acall(*args, ctx=ctx, **kwargs)
|
|
161
|
+
return self._format_tool_output(result)
|
|
152
162
|
except TypeError as e:
|
|
153
163
|
sig = inspect.signature(self.metadata.fn_schema)
|
|
154
164
|
valid_parameters = list(sig.parameters.keys())
|
|
@@ -166,6 +176,7 @@ class VectaraTool(FunctionTool):
|
|
|
166
176
|
return err_output
|
|
167
177
|
except Exception as e:
|
|
168
178
|
import traceback
|
|
179
|
+
|
|
169
180
|
err_output = ToolOutput(
|
|
170
181
|
tool_name=self.metadata.name,
|
|
171
182
|
content=f"Tool {self.metadata.name} Malfunction: {str(e)}, traceback: {traceback.format_exc()}",
|
|
@@ -174,10 +185,39 @@ class VectaraTool(FunctionTool):
|
|
|
174
185
|
)
|
|
175
186
|
return err_output
|
|
176
187
|
|
|
188
|
+
def _format_tool_output(self, result: ToolOutput) -> ToolOutput:
|
|
189
|
+
"""Format tool output to use human-readable representation if available."""
|
|
190
|
+
if hasattr(result, "content") and _is_human_readable_output(result.content):
|
|
191
|
+
try:
|
|
192
|
+
# Use human-readable format for content, keep raw output
|
|
193
|
+
human_readable_content = result.content.to_human_readable()
|
|
194
|
+
raw_output = result.content.get_raw_output()
|
|
195
|
+
return ToolOutput(
|
|
196
|
+
tool_name=result.tool_name,
|
|
197
|
+
content=human_readable_content,
|
|
198
|
+
raw_input=result.raw_input,
|
|
199
|
+
raw_output=raw_output,
|
|
200
|
+
)
|
|
201
|
+
except Exception as e:
|
|
202
|
+
# If formatting fails, fall back to original content with error info
|
|
203
|
+
import logging
|
|
204
|
+
|
|
205
|
+
logging.warning(
|
|
206
|
+
f"Failed to format tool output for {result.tool_name}: {e}"
|
|
207
|
+
)
|
|
208
|
+
return ToolOutput(
|
|
209
|
+
tool_name=result.tool_name,
|
|
210
|
+
content=f"[Formatting Error] {str(result.content)}",
|
|
211
|
+
raw_input=result.raw_input,
|
|
212
|
+
raw_output={"error": str(e), "original_content": result.content},
|
|
213
|
+
)
|
|
214
|
+
return result
|
|
215
|
+
|
|
177
216
|
|
|
178
217
|
class EmptyBaseModel(BaseModel):
|
|
179
218
|
"""empty base model"""
|
|
180
219
|
|
|
220
|
+
|
|
181
221
|
def _clean_type_repr(type_repr: str) -> str:
|
|
182
222
|
"""Cleans the string representation of a type."""
|
|
183
223
|
# Replace <class 'somename'> with somename
|
|
@@ -188,6 +228,7 @@ def _clean_type_repr(type_repr: str) -> str:
|
|
|
188
228
|
type_repr = type_repr.replace("typing.", "")
|
|
189
229
|
return type_repr
|
|
190
230
|
|
|
231
|
+
|
|
191
232
|
def _format_type(annotation) -> str:
|
|
192
233
|
"""
|
|
193
234
|
Turn things like Union[int, str, NoneType] into 'int | str | None',
|
|
@@ -209,6 +250,7 @@ def _format_type(annotation) -> str:
|
|
|
209
250
|
type_repr = _clean_type_repr(type_repr)
|
|
210
251
|
return type_repr.replace("NoneType", "None")
|
|
211
252
|
|
|
253
|
+
|
|
212
254
|
def _make_docstring(
|
|
213
255
|
function: Callable[..., ToolOutput],
|
|
214
256
|
tool_name: str,
|
|
@@ -267,11 +309,15 @@ def _make_docstring(
|
|
|
267
309
|
ty_info = schema_prop["type"]
|
|
268
310
|
if isinstance(ty_info, str):
|
|
269
311
|
ty_str = _clean_type_repr(ty_info)
|
|
270
|
-
elif isinstance(
|
|
312
|
+
elif isinstance(
|
|
313
|
+
ty_info, list
|
|
314
|
+
): # Handle JSON schema array type e.g., ["integer", "string"]
|
|
271
315
|
ty_str = " | ".join([_clean_type_repr(t) for t in ty_info])
|
|
272
316
|
|
|
273
317
|
# inline default if present
|
|
274
|
-
default_txt =
|
|
318
|
+
default_txt = (
|
|
319
|
+
f", default={default!r}" if default is not PydanticUndefined else ""
|
|
320
|
+
)
|
|
275
321
|
|
|
276
322
|
# inline examples if any
|
|
277
323
|
if examples:
|
|
@@ -288,8 +334,8 @@ def _make_docstring(
|
|
|
288
334
|
doc_lines.append(f" dict[str, Any]: {return_desc}")
|
|
289
335
|
|
|
290
336
|
initial_docstring = "\n".join(doc_lines)
|
|
291
|
-
collapsed_spaces = re.sub(r
|
|
292
|
-
final_docstring = re.sub(r
|
|
337
|
+
collapsed_spaces = re.sub(r" {2,}", " ", initial_docstring)
|
|
338
|
+
final_docstring = re.sub(r"\n{2,}", "\n", collapsed_spaces).strip()
|
|
293
339
|
return final_docstring
|
|
294
340
|
|
|
295
341
|
|
|
@@ -317,13 +363,17 @@ def create_tool_from_dynamic_function(
|
|
|
317
363
|
if tool_args_schema is None:
|
|
318
364
|
tool_args_schema = EmptyBaseModel
|
|
319
365
|
|
|
320
|
-
if not isinstance(tool_args_schema, type) or not issubclass(
|
|
366
|
+
if not isinstance(tool_args_schema, type) or not issubclass(
|
|
367
|
+
tool_args_schema, BaseModel
|
|
368
|
+
):
|
|
321
369
|
raise TypeError("tool_args_schema must be a Pydantic BaseModel subclass")
|
|
322
370
|
|
|
323
371
|
fields: Dict[str, Any] = {}
|
|
324
372
|
base_params = []
|
|
325
373
|
for field_name, field_info in base_params_model.model_fields.items():
|
|
326
|
-
default =
|
|
374
|
+
default = (
|
|
375
|
+
Ellipsis if field_info.default is PydanticUndefined else field_info.default
|
|
376
|
+
)
|
|
327
377
|
param = inspect.Parameter(
|
|
328
378
|
field_name,
|
|
329
379
|
inspect.Parameter.POSITIONAL_OR_KEYWORD,
|
|
@@ -338,7 +388,9 @@ def create_tool_from_dynamic_function(
|
|
|
338
388
|
if field_name in fields:
|
|
339
389
|
continue
|
|
340
390
|
|
|
341
|
-
default =
|
|
391
|
+
default = (
|
|
392
|
+
Ellipsis if field_info.default is PydanticUndefined else field_info.default
|
|
393
|
+
)
|
|
342
394
|
param = inspect.Parameter(
|
|
343
395
|
field_name,
|
|
344
396
|
inspect.Parameter.POSITIONAL_OR_KEYWORD,
|
|
@@ -362,9 +414,7 @@ def create_tool_from_dynamic_function(
|
|
|
362
414
|
function.__name__ = re.sub(r"[^A-Za-z0-9_]", "_", tool_name)
|
|
363
415
|
|
|
364
416
|
function.__doc__ = _make_docstring(
|
|
365
|
-
function,
|
|
366
|
-
tool_name, tool_description, fn_schema,
|
|
367
|
-
all_params, compact_docstring
|
|
417
|
+
function, tool_name, tool_description, fn_schema, all_params, compact_docstring
|
|
368
418
|
)
|
|
369
419
|
tool = VectaraTool.from_defaults(
|
|
370
420
|
fn=function,
|
|
@@ -526,3 +576,115 @@ def build_filter_string(
|
|
|
526
576
|
if fixed_filter and joined:
|
|
527
577
|
return f"({fixed_filter}) AND ({joined})"
|
|
528
578
|
return fixed_filter or joined
|
|
579
|
+
|
|
580
|
+
|
|
581
|
+
def _is_human_readable_output(obj: Any) -> bool:
|
|
582
|
+
"""Check if an object implements the HumanReadableOutput protocol."""
|
|
583
|
+
return (
|
|
584
|
+
hasattr(obj, "to_human_readable")
|
|
585
|
+
and hasattr(obj, "get_raw_output")
|
|
586
|
+
and callable(getattr(obj, "to_human_readable", None))
|
|
587
|
+
and callable(getattr(obj, "get_raw_output", None))
|
|
588
|
+
)
|
|
589
|
+
|
|
590
|
+
|
|
591
|
+
def create_human_readable_output(
|
|
592
|
+
raw_output: Any, formatter: Optional[Callable[[Any], str]] = None
|
|
593
|
+
) -> "HumanReadableToolOutput":
|
|
594
|
+
"""Create a HumanReadableToolOutput wrapper for tool outputs."""
|
|
595
|
+
return HumanReadableToolOutput(raw_output, formatter)
|
|
596
|
+
|
|
597
|
+
|
|
598
|
+
def format_as_table(data: List[Dict[str, Any]], max_width: int = 80) -> str:
|
|
599
|
+
"""Format list of dictionaries as a table."""
|
|
600
|
+
if not data:
|
|
601
|
+
return "No data to display"
|
|
602
|
+
|
|
603
|
+
# Get all unique keys
|
|
604
|
+
all_keys = set()
|
|
605
|
+
for item in data:
|
|
606
|
+
all_keys.update(item.keys())
|
|
607
|
+
|
|
608
|
+
headers = list(all_keys)
|
|
609
|
+
|
|
610
|
+
# Calculate column widths
|
|
611
|
+
col_widths = {}
|
|
612
|
+
for header in headers:
|
|
613
|
+
col_widths[header] = max(
|
|
614
|
+
len(header), max(len(str(item.get(header, ""))) for item in data)
|
|
615
|
+
)
|
|
616
|
+
# Limit column width
|
|
617
|
+
col_widths[header] = min(col_widths[header], max_width // len(headers))
|
|
618
|
+
|
|
619
|
+
# Create table
|
|
620
|
+
lines = []
|
|
621
|
+
|
|
622
|
+
# Header row
|
|
623
|
+
header_row = " | ".join(header.ljust(col_widths[header]) for header in headers)
|
|
624
|
+
lines.append(header_row)
|
|
625
|
+
lines.append("-" * len(header_row))
|
|
626
|
+
|
|
627
|
+
# Data rows
|
|
628
|
+
for item in data:
|
|
629
|
+
row = " | ".join(
|
|
630
|
+
str(item.get(header, "")).ljust(col_widths[header])[: col_widths[header]]
|
|
631
|
+
for header in headers
|
|
632
|
+
)
|
|
633
|
+
lines.append(row)
|
|
634
|
+
|
|
635
|
+
return "\n".join(lines)
|
|
636
|
+
|
|
637
|
+
|
|
638
|
+
def format_as_json(data: Any, indent: int = 2) -> str:
|
|
639
|
+
"""Format data as pretty-printed JSON."""
|
|
640
|
+
import json
|
|
641
|
+
|
|
642
|
+
try:
|
|
643
|
+
return json.dumps(data, indent=indent, ensure_ascii=False)
|
|
644
|
+
except (TypeError, ValueError):
|
|
645
|
+
return str(data)
|
|
646
|
+
|
|
647
|
+
|
|
648
|
+
def format_as_markdown_list(items: List[Any], numbered: bool = False) -> str:
|
|
649
|
+
"""Format items as markdown list."""
|
|
650
|
+
if not items:
|
|
651
|
+
return "No items to display"
|
|
652
|
+
|
|
653
|
+
if numbered:
|
|
654
|
+
return "\n".join(f"{i+1}. {item}" for i, item in enumerate(items))
|
|
655
|
+
else:
|
|
656
|
+
return "\n".join(f"- {item}" for item in items)
|
|
657
|
+
|
|
658
|
+
|
|
659
|
+
class HumanReadableToolOutput:
|
|
660
|
+
"""Wrapper class that implements HumanReadableOutput protocol."""
|
|
661
|
+
|
|
662
|
+
def __init__(
|
|
663
|
+
self, raw_output: Any, formatter: Optional[Callable[[Any], str]] = None
|
|
664
|
+
):
|
|
665
|
+
self._raw_output = raw_output
|
|
666
|
+
self._formatter = formatter or str
|
|
667
|
+
|
|
668
|
+
def to_human_readable(self) -> str:
|
|
669
|
+
"""Convert the output to a human-readable format."""
|
|
670
|
+
try:
|
|
671
|
+
return self._formatter(self._raw_output)
|
|
672
|
+
except Exception as e:
|
|
673
|
+
import logging
|
|
674
|
+
|
|
675
|
+
logging.warning(f"Failed to format output with custom formatter: {e}")
|
|
676
|
+
# Fallback to string representation
|
|
677
|
+
try:
|
|
678
|
+
return str(self._raw_output)
|
|
679
|
+
except Exception:
|
|
680
|
+
return f"[Error formatting output: {e}]"
|
|
681
|
+
|
|
682
|
+
def get_raw_output(self) -> Any:
|
|
683
|
+
"""Get the raw output data."""
|
|
684
|
+
return self._raw_output
|
|
685
|
+
|
|
686
|
+
def __str__(self) -> str:
|
|
687
|
+
return self.to_human_readable()
|
|
688
|
+
|
|
689
|
+
def __repr__(self) -> str:
|
|
690
|
+
return f"HumanReadableToolOutput({self._raw_output!r})"
|
vectara_agentic/tools.py
CHANGED
|
@@ -14,7 +14,6 @@ from pydantic import BaseModel, Field
|
|
|
14
14
|
from llama_index.core.tools import FunctionTool
|
|
15
15
|
from llama_index.indices.managed.vectara import VectaraIndex
|
|
16
16
|
from llama_index.core.utilities.sql_wrapper import SQLDatabase
|
|
17
|
-
from llama_index.core.tools.types import ToolOutput
|
|
18
17
|
|
|
19
18
|
from .types import ToolType
|
|
20
19
|
from .tools_catalog import ToolsCatalog, get_bad_topics
|
|
@@ -25,6 +24,7 @@ from .tool_utils import (
|
|
|
25
24
|
create_tool_from_dynamic_function,
|
|
26
25
|
build_filter_string,
|
|
27
26
|
VectaraTool,
|
|
27
|
+
create_human_readable_output,
|
|
28
28
|
)
|
|
29
29
|
|
|
30
30
|
LI_packages = {
|
|
@@ -170,7 +170,7 @@ class VectaraToolFactory:
|
|
|
170
170
|
)
|
|
171
171
|
|
|
172
172
|
# Dynamically generate the search function
|
|
173
|
-
def search_function(*args: Any, **kwargs: Any) ->
|
|
173
|
+
def search_function(*args: Any, **kwargs: Any) -> list[dict]:
|
|
174
174
|
"""
|
|
175
175
|
Dynamically generated function for semantic search Vectara.
|
|
176
176
|
"""
|
|
@@ -192,12 +192,11 @@ class VectaraToolFactory:
|
|
|
192
192
|
kwargs, tool_args_type, fixed_filter
|
|
193
193
|
)
|
|
194
194
|
except ValueError as e:
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
raw_input={"args": args, "kwargs": kwargs},
|
|
199
|
-
raw_output={"response": str(e)},
|
|
195
|
+
msg = (
|
|
196
|
+
f"Building filter string failed in search tool due to invalid input or configuration ({e}). "
|
|
197
|
+
"Please verify the input arguments and ensure they meet the expected format or conditions."
|
|
200
198
|
)
|
|
199
|
+
return [{"text": msg, "metadata": {"args": args, "kwargs": kwargs}}]
|
|
201
200
|
|
|
202
201
|
vectara_retriever = vectara.as_retriever(
|
|
203
202
|
summary_enabled=False,
|
|
@@ -228,12 +227,7 @@ class VectaraToolFactory:
|
|
|
228
227
|
|
|
229
228
|
if len(response) == 0:
|
|
230
229
|
msg = "Vectara Tool failed to retrieve any results for the query."
|
|
231
|
-
return
|
|
232
|
-
tool_name=search_function.__name__,
|
|
233
|
-
content=msg,
|
|
234
|
-
raw_input={"args": args, "kwargs": kwargs},
|
|
235
|
-
raw_output={"response": msg},
|
|
236
|
-
)
|
|
230
|
+
return [{"text": msg, "metadata": {"args": args, "kwargs": kwargs}}]
|
|
237
231
|
unique_ids = set()
|
|
238
232
|
docs = []
|
|
239
233
|
doc_matches = {}
|
|
@@ -244,7 +238,8 @@ class VectaraToolFactory:
|
|
|
244
238
|
unique_ids.add(doc.id_)
|
|
245
239
|
doc_matches[doc.id_] = [doc.node.get_content()]
|
|
246
240
|
docs.append((doc.id_, doc.metadata))
|
|
247
|
-
|
|
241
|
+
|
|
242
|
+
res = []
|
|
248
243
|
if summarize:
|
|
249
244
|
summaries_dict = asyncio.run(
|
|
250
245
|
summarize_documents(
|
|
@@ -254,35 +249,50 @@ class VectaraToolFactory:
|
|
|
254
249
|
doc_ids=list(unique_ids),
|
|
255
250
|
)
|
|
256
251
|
)
|
|
257
|
-
for doc_id, metadata in docs:
|
|
258
|
-
summary = summaries_dict.get(doc_id, "")
|
|
259
|
-
matching_text = "\n".join(
|
|
260
|
-
f"<match {i}>{piece}</match {i}>"
|
|
261
|
-
for i, piece in enumerate(doc_matches[doc_id], start=1)
|
|
262
|
-
)
|
|
263
|
-
tool_output += (
|
|
264
|
-
f"document_id: '{doc_id}'\nmetadata: '{metadata}'\n"
|
|
265
|
-
f"matching texts: '{matching_text}'\n"
|
|
266
|
-
f"summary: '{summary}'\n\n"
|
|
267
|
-
)
|
|
268
252
|
else:
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
253
|
+
summaries_dict = {}
|
|
254
|
+
|
|
255
|
+
for doc_id, metadata in docs:
|
|
256
|
+
res.append(
|
|
257
|
+
{
|
|
258
|
+
"text": summaries_dict.get(doc_id, "") if summarize else "",
|
|
259
|
+
"metadata": {
|
|
260
|
+
"document_id": doc_id,
|
|
261
|
+
"metadata": metadata,
|
|
262
|
+
"matching_text": doc_matches[doc_id],
|
|
263
|
+
},
|
|
264
|
+
}
|
|
265
|
+
)
|
|
266
|
+
|
|
267
|
+
# Create human-readable output using sequential format
|
|
268
|
+
def format_search_results(results):
|
|
269
|
+
if not results:
|
|
270
|
+
return "No search results found"
|
|
271
|
+
|
|
272
|
+
# Create a sequential view for human reading
|
|
273
|
+
formatted_results = []
|
|
274
|
+
for i, result in enumerate(results, 1):
|
|
275
|
+
result_str = f"**Result #{i}**\n"
|
|
276
|
+
result_str += f"Document ID: {result['metadata']['document_id']}\n"
|
|
277
|
+
result_str += (
|
|
278
|
+
f"Matches: {len(result['metadata']['matching_text'])}\n"
|
|
277
279
|
)
|
|
278
280
|
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
281
|
+
if summarize and result["text"]:
|
|
282
|
+
result_str += f"Summary: {result['text']}\n"
|
|
283
|
+
|
|
284
|
+
# Add sample matching text if available
|
|
285
|
+
if result["metadata"]["matching_text"]:
|
|
286
|
+
sample_matches = result["metadata"]["matching_text"][
|
|
287
|
+
:2
|
|
288
|
+
] # Show first 2 matches
|
|
289
|
+
result_str += f"Sample matches: {', '.join(sample_matches)}\n"
|
|
290
|
+
|
|
291
|
+
formatted_results.append(result_str)
|
|
292
|
+
|
|
293
|
+
return "\n".join(formatted_results)
|
|
294
|
+
|
|
295
|
+
return create_human_readable_output(res, format_search_results)
|
|
286
296
|
|
|
287
297
|
class SearchToolBaseParams(BaseModel):
|
|
288
298
|
"""Model for the base parameters of the search tool."""
|
|
@@ -362,6 +372,7 @@ class VectaraToolFactory:
|
|
|
362
372
|
frequency_penalty: Optional[float] = None,
|
|
363
373
|
presence_penalty: Optional[float] = None,
|
|
364
374
|
include_citations: bool = True,
|
|
375
|
+
citation_pattern: str = "{doc.url}",
|
|
365
376
|
save_history: bool = False,
|
|
366
377
|
fcs_threshold: float = 0.0,
|
|
367
378
|
return_direct: bool = False,
|
|
@@ -415,6 +426,9 @@ class VectaraToolFactory:
|
|
|
415
426
|
higher values increasing the diversity of topics.
|
|
416
427
|
include_citations (bool, optional): Whether to include citations in the response.
|
|
417
428
|
If True, uses markdown vectara citations that requires the Vectara scale plan.
|
|
429
|
+
citation_pattern (str, optional): The pattern for the citations in the response.
|
|
430
|
+
Default is "{doc.url}" which uses the document URL.
|
|
431
|
+
If include_citations is False, this parameter is ignored.
|
|
418
432
|
save_history (bool, optional): Whether to save the query in history.
|
|
419
433
|
fcs_threshold (float, optional): A threshold for factual consistency.
|
|
420
434
|
If set above 0, the tool notifies the calling agent that it "cannot respond" if FCS is too low.
|
|
@@ -436,7 +450,7 @@ class VectaraToolFactory:
|
|
|
436
450
|
)
|
|
437
451
|
|
|
438
452
|
# Dynamically generate the RAG function
|
|
439
|
-
def rag_function(*args: Any, **kwargs: Any) ->
|
|
453
|
+
def rag_function(*args: Any, **kwargs: Any) -> dict:
|
|
440
454
|
"""
|
|
441
455
|
Dynamically generated function for RAG query with Vectara.
|
|
442
456
|
"""
|
|
@@ -452,12 +466,12 @@ class VectaraToolFactory:
|
|
|
452
466
|
kwargs, tool_args_type, fixed_filter
|
|
453
467
|
)
|
|
454
468
|
except ValueError as e:
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
raw_output={"response": str(e)},
|
|
469
|
+
msg = (
|
|
470
|
+
f"Building filter string failed in rag tool. "
|
|
471
|
+
f"Reason: {e}. Ensure that the input arguments match the expected "
|
|
472
|
+
f"format and include all required fields. "
|
|
460
473
|
)
|
|
474
|
+
return {"text": msg, "metadata": {"args": args, "kwargs": kwargs}}
|
|
461
475
|
|
|
462
476
|
vectara_query_engine = vectara.as_query_engine(
|
|
463
477
|
summary_enabled=True,
|
|
@@ -491,7 +505,7 @@ class VectaraToolFactory:
|
|
|
491
505
|
frequency_penalty=frequency_penalty,
|
|
492
506
|
presence_penalty=presence_penalty,
|
|
493
507
|
citations_style="markdown" if include_citations else None,
|
|
494
|
-
citations_url_pattern=
|
|
508
|
+
citations_url_pattern=citation_pattern if include_citations else None,
|
|
495
509
|
save_history=save_history,
|
|
496
510
|
x_source_str="vectara-agentic",
|
|
497
511
|
verbose=verbose,
|
|
@@ -503,73 +517,59 @@ class VectaraToolFactory:
|
|
|
503
517
|
"Tool failed to generate a response since no matches were found. "
|
|
504
518
|
"Please check the arguments and try again."
|
|
505
519
|
)
|
|
506
|
-
return
|
|
507
|
-
tool_name=rag_function.__name__,
|
|
508
|
-
content=msg,
|
|
509
|
-
raw_input={"args": args, "kwargs": kwargs},
|
|
510
|
-
raw_output={"response": msg},
|
|
511
|
-
)
|
|
520
|
+
return {"text": msg, "metadata": {"args": args, "kwargs": kwargs}}
|
|
512
521
|
if str(response) == "None":
|
|
513
522
|
msg = "Tool failed to generate a response."
|
|
514
|
-
return
|
|
515
|
-
tool_name=rag_function.__name__,
|
|
516
|
-
content=msg,
|
|
517
|
-
raw_input={"args": args, "kwargs": kwargs},
|
|
518
|
-
raw_output={"response": msg},
|
|
519
|
-
)
|
|
523
|
+
return {"text": msg, "metadata": {"args": args, "kwargs": kwargs}}
|
|
520
524
|
|
|
521
525
|
# Extract citation metadata
|
|
522
526
|
pattern = r"\[(\d+)\]"
|
|
523
527
|
matches = re.findall(pattern, response.response)
|
|
524
528
|
citation_numbers = sorted(set(int(match) for match in matches))
|
|
525
|
-
citation_metadata =
|
|
529
|
+
citation_metadata = {}
|
|
526
530
|
keys_to_ignore = ["lang", "offset", "len"]
|
|
527
531
|
for citation_number in citation_numbers:
|
|
528
|
-
metadata =
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
]
|
|
537
|
-
)
|
|
538
|
-
+ ".\n"
|
|
539
|
-
)
|
|
532
|
+
metadata = {
|
|
533
|
+
k: v
|
|
534
|
+
for k, v in response.source_nodes[
|
|
535
|
+
citation_number - 1
|
|
536
|
+
].metadata.items()
|
|
537
|
+
if k not in keys_to_ignore
|
|
538
|
+
}
|
|
539
|
+
citation_metadata[str(citation_number)] = metadata
|
|
540
540
|
fcs = 0.0
|
|
541
541
|
fcs_str = response.metadata["fcs"] if "fcs" in response.metadata else "0.0"
|
|
542
542
|
if fcs_str and is_float(fcs_str):
|
|
543
543
|
fcs = float(fcs_str)
|
|
544
544
|
if fcs < fcs_threshold:
|
|
545
545
|
msg = f"Could not answer the query due to suspected hallucination (fcs={fcs})."
|
|
546
|
-
return
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
return
|
|
546
|
+
return {
|
|
547
|
+
"text": msg,
|
|
548
|
+
"metadata": {"args": args, "kwargs": kwargs, "fcs": fcs},
|
|
549
|
+
}
|
|
550
|
+
if fcs:
|
|
551
|
+
citation_metadata["fcs"] = fcs
|
|
552
|
+
|
|
553
|
+
res = {"text": response.response, "metadata": citation_metadata}
|
|
554
|
+
|
|
555
|
+
# Create human-readable output with citation formatting
|
|
556
|
+
def format_rag_response(result):
|
|
557
|
+
text = result["text"]
|
|
558
|
+
metadata = result["metadata"]
|
|
559
|
+
|
|
560
|
+
# Format citations if present
|
|
561
|
+
citation_info = []
|
|
562
|
+
for key, value in metadata.items():
|
|
563
|
+
if key.isdigit():
|
|
564
|
+
url = value.get("document", {}).get("url", None)
|
|
565
|
+
if url:
|
|
566
|
+
citation_info.append(f"[{key}]: {url}")
|
|
567
|
+
if citation_info:
|
|
568
|
+
text += "\n\nCitations:\n" + "\n".join(citation_info)
|
|
569
|
+
|
|
570
|
+
return text
|
|
571
|
+
|
|
572
|
+
return create_human_readable_output(res, format_rag_response)
|
|
573
573
|
|
|
574
574
|
class RagToolBaseParams(BaseModel):
|
|
575
575
|
"""Model for the base parameters of the RAG tool."""
|
vectara_agentic/types.py
CHANGED
|
@@ -1,11 +1,17 @@
|
|
|
1
1
|
"""
|
|
2
2
|
This module contains the types used in the Vectara Agentic.
|
|
3
3
|
"""
|
|
4
|
+
|
|
4
5
|
from enum import Enum
|
|
6
|
+
from typing import Protocol, Any
|
|
5
7
|
|
|
8
|
+
from llama_index.core.schema import Document as LI_Document
|
|
6
9
|
from llama_index.core.tools.types import ToolOutput as LI_ToolOutput
|
|
7
10
|
from llama_index.core.chat_engine.types import AgentChatResponse as LI_AgentChatResponse
|
|
8
|
-
from llama_index.core.chat_engine.types import
|
|
11
|
+
from llama_index.core.chat_engine.types import (
|
|
12
|
+
StreamingAgentChatResponse as LI_StreamingAgentChatResponse,
|
|
13
|
+
)
|
|
14
|
+
|
|
9
15
|
|
|
10
16
|
class AgentType(Enum):
|
|
11
17
|
"""Enumeration for different types of agents."""
|
|
@@ -16,6 +22,7 @@ class AgentType(Enum):
|
|
|
16
22
|
LLMCOMPILER = "LLMCOMPILER"
|
|
17
23
|
LATS = "LATS"
|
|
18
24
|
|
|
25
|
+
|
|
19
26
|
class ObserverType(Enum):
|
|
20
27
|
"""Enumeration for different types of observability integrations."""
|
|
21
28
|
|
|
@@ -55,16 +62,30 @@ class LLMRole(Enum):
|
|
|
55
62
|
|
|
56
63
|
class ToolType(Enum):
|
|
57
64
|
"""Enumeration for different types of tools."""
|
|
65
|
+
|
|
58
66
|
QUERY = "query"
|
|
59
67
|
ACTION = "action"
|
|
60
68
|
|
|
69
|
+
|
|
61
70
|
class AgentConfigType(Enum):
|
|
62
71
|
"""Enumeration for different types of agent configurations."""
|
|
72
|
+
|
|
63
73
|
DEFAULT = "default"
|
|
64
74
|
FALLBACK = "fallback"
|
|
65
75
|
|
|
66
76
|
|
|
77
|
+
class HumanReadableOutput(Protocol):
|
|
78
|
+
"""Protocol for tool outputs that can provide human-readable representations."""
|
|
79
|
+
|
|
80
|
+
def to_human_readable(self) -> str:
|
|
81
|
+
"""Convert the output to a human-readable format."""
|
|
82
|
+
|
|
83
|
+
def get_raw_output(self) -> Any:
|
|
84
|
+
"""Get the raw output data."""
|
|
85
|
+
|
|
86
|
+
|
|
67
87
|
# classes for Agent responses
|
|
68
88
|
ToolOutput = LI_ToolOutput
|
|
69
89
|
AgentResponse = LI_AgentChatResponse
|
|
70
90
|
AgentStreamingResponse = LI_StreamingAgentChatResponse
|
|
91
|
+
Document = LI_Document
|