vectara-agentic 0.2.23__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/db_tools.py +1 -1
- vectara_agentic/hhem.py +45 -0
- vectara_agentic/llm_utils.py +34 -10
- vectara_agentic/tool_utils.py +177 -15
- vectara_agentic/tools.py +102 -86
- vectara_agentic/types.py +22 -1
- {vectara_agentic-0.2.23.dist-info → vectara_agentic-0.3.0.dist-info}/METADATA +24 -2
- {vectara_agentic-0.2.23.dist-info → vectara_agentic-0.3.0.dist-info}/RECORD +19 -17
- {vectara_agentic-0.2.23.dist-info → vectara_agentic-0.3.0.dist-info}/WHEEL +0 -0
- {vectara_agentic-0.2.23.dist-info → vectara_agentic-0.3.0.dist-info}/licenses/LICENSE +0 -0
- {vectara_agentic-0.2.23.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,20 +227,19 @@ 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 = []
|
|
233
|
+
doc_matches = {}
|
|
239
234
|
for doc in response:
|
|
240
235
|
if doc.id_ in unique_ids:
|
|
236
|
+
doc_matches[doc.id_].append(doc.node.get_content())
|
|
241
237
|
continue
|
|
242
238
|
unique_ids.add(doc.id_)
|
|
239
|
+
doc_matches[doc.id_] = [doc.node.get_content()]
|
|
243
240
|
docs.append((doc.id_, doc.metadata))
|
|
244
|
-
|
|
241
|
+
|
|
242
|
+
res = []
|
|
245
243
|
if summarize:
|
|
246
244
|
summaries_dict = asyncio.run(
|
|
247
245
|
summarize_documents(
|
|
@@ -251,22 +249,50 @@ class VectaraToolFactory:
|
|
|
251
249
|
doc_ids=list(unique_ids),
|
|
252
250
|
)
|
|
253
251
|
)
|
|
254
|
-
for doc_id, metadata in docs:
|
|
255
|
-
summary = summaries_dict.get(doc_id, "")
|
|
256
|
-
tool_output += f"document_id: '{doc_id}'\nmetadata: '{metadata}'\nsummary: '{summary}'\n\n"
|
|
257
252
|
else:
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
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"
|
|
261
279
|
)
|
|
262
280
|
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
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)
|
|
270
296
|
|
|
271
297
|
class SearchToolBaseParams(BaseModel):
|
|
272
298
|
"""Model for the base parameters of the search tool."""
|
|
@@ -346,6 +372,7 @@ class VectaraToolFactory:
|
|
|
346
372
|
frequency_penalty: Optional[float] = None,
|
|
347
373
|
presence_penalty: Optional[float] = None,
|
|
348
374
|
include_citations: bool = True,
|
|
375
|
+
citation_pattern: str = "{doc.url}",
|
|
349
376
|
save_history: bool = False,
|
|
350
377
|
fcs_threshold: float = 0.0,
|
|
351
378
|
return_direct: bool = False,
|
|
@@ -399,6 +426,9 @@ class VectaraToolFactory:
|
|
|
399
426
|
higher values increasing the diversity of topics.
|
|
400
427
|
include_citations (bool, optional): Whether to include citations in the response.
|
|
401
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.
|
|
402
432
|
save_history (bool, optional): Whether to save the query in history.
|
|
403
433
|
fcs_threshold (float, optional): A threshold for factual consistency.
|
|
404
434
|
If set above 0, the tool notifies the calling agent that it "cannot respond" if FCS is too low.
|
|
@@ -420,7 +450,7 @@ class VectaraToolFactory:
|
|
|
420
450
|
)
|
|
421
451
|
|
|
422
452
|
# Dynamically generate the RAG function
|
|
423
|
-
def rag_function(*args: Any, **kwargs: Any) ->
|
|
453
|
+
def rag_function(*args: Any, **kwargs: Any) -> dict:
|
|
424
454
|
"""
|
|
425
455
|
Dynamically generated function for RAG query with Vectara.
|
|
426
456
|
"""
|
|
@@ -436,12 +466,12 @@ class VectaraToolFactory:
|
|
|
436
466
|
kwargs, tool_args_type, fixed_filter
|
|
437
467
|
)
|
|
438
468
|
except ValueError as e:
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
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. "
|
|
444
473
|
)
|
|
474
|
+
return {"text": msg, "metadata": {"args": args, "kwargs": kwargs}}
|
|
445
475
|
|
|
446
476
|
vectara_query_engine = vectara.as_query_engine(
|
|
447
477
|
summary_enabled=True,
|
|
@@ -475,7 +505,7 @@ class VectaraToolFactory:
|
|
|
475
505
|
frequency_penalty=frequency_penalty,
|
|
476
506
|
presence_penalty=presence_penalty,
|
|
477
507
|
citations_style="markdown" if include_citations else None,
|
|
478
|
-
citations_url_pattern=
|
|
508
|
+
citations_url_pattern=citation_pattern if include_citations else None,
|
|
479
509
|
save_history=save_history,
|
|
480
510
|
x_source_str="vectara-agentic",
|
|
481
511
|
verbose=verbose,
|
|
@@ -487,73 +517,59 @@ class VectaraToolFactory:
|
|
|
487
517
|
"Tool failed to generate a response since no matches were found. "
|
|
488
518
|
"Please check the arguments and try again."
|
|
489
519
|
)
|
|
490
|
-
return
|
|
491
|
-
tool_name=rag_function.__name__,
|
|
492
|
-
content=msg,
|
|
493
|
-
raw_input={"args": args, "kwargs": kwargs},
|
|
494
|
-
raw_output={"response": msg},
|
|
495
|
-
)
|
|
520
|
+
return {"text": msg, "metadata": {"args": args, "kwargs": kwargs}}
|
|
496
521
|
if str(response) == "None":
|
|
497
522
|
msg = "Tool failed to generate a response."
|
|
498
|
-
return
|
|
499
|
-
tool_name=rag_function.__name__,
|
|
500
|
-
content=msg,
|
|
501
|
-
raw_input={"args": args, "kwargs": kwargs},
|
|
502
|
-
raw_output={"response": msg},
|
|
503
|
-
)
|
|
523
|
+
return {"text": msg, "metadata": {"args": args, "kwargs": kwargs}}
|
|
504
524
|
|
|
505
525
|
# Extract citation metadata
|
|
506
526
|
pattern = r"\[(\d+)\]"
|
|
507
527
|
matches = re.findall(pattern, response.response)
|
|
508
528
|
citation_numbers = sorted(set(int(match) for match in matches))
|
|
509
|
-
citation_metadata =
|
|
529
|
+
citation_metadata = {}
|
|
510
530
|
keys_to_ignore = ["lang", "offset", "len"]
|
|
511
531
|
for citation_number in citation_numbers:
|
|
512
|
-
metadata =
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
]
|
|
521
|
-
)
|
|
522
|
-
+ ".\n"
|
|
523
|
-
)
|
|
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
|
|
524
540
|
fcs = 0.0
|
|
525
541
|
fcs_str = response.metadata["fcs"] if "fcs" in response.metadata else "0.0"
|
|
526
542
|
if fcs_str and is_float(fcs_str):
|
|
527
543
|
fcs = float(fcs_str)
|
|
528
544
|
if fcs < fcs_threshold:
|
|
529
545
|
msg = f"Could not answer the query due to suspected hallucination (fcs={fcs})."
|
|
530
|
-
return
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
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)
|
|
557
573
|
|
|
558
574
|
class RagToolBaseParams(BaseModel):
|
|
559
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
|