vectara-agentic 0.2.13__py3-none-any.whl → 0.2.15__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_groq.py +120 -0
- tests/test_return_direct.py +49 -0
- tests/test_tools.py +42 -6
- tests/test_vectara_llms.py +4 -12
- vectara_agentic/_observability.py +43 -21
- vectara_agentic/_prompts.py +1 -1
- vectara_agentic/_version.py +1 -1
- vectara_agentic/agent.py +83 -10
- vectara_agentic/llm_utils.py +174 -0
- vectara_agentic/tool_utils.py +536 -0
- vectara_agentic/tools.py +37 -475
- vectara_agentic/tools_catalog.py +3 -2
- vectara_agentic/utils.py +0 -153
- {vectara_agentic-0.2.13.dist-info → vectara_agentic-0.2.15.dist-info}/METADATA +25 -11
- vectara_agentic-0.2.15.dist-info/RECORD +34 -0
- {vectara_agentic-0.2.13.dist-info → vectara_agentic-0.2.15.dist-info}/WHEEL +1 -1
- vectara_agentic-0.2.13.dist-info/RECORD +0 -30
- {vectara_agentic-0.2.13.dist-info → vectara_agentic-0.2.15.dist-info}/licenses/LICENSE +0 -0
- {vectara_agentic-0.2.13.dist-info → vectara_agentic-0.2.15.dist-info}/top_level.txt +0 -0
vectara_agentic/tools.py
CHANGED
|
@@ -8,22 +8,24 @@ import importlib
|
|
|
8
8
|
import os
|
|
9
9
|
import asyncio
|
|
10
10
|
|
|
11
|
-
from typing import Callable, List, Dict, Any, Optional, Union
|
|
12
|
-
from pydantic import BaseModel, Field
|
|
13
|
-
from pydantic_core import PydanticUndefined
|
|
11
|
+
from typing import Callable, List, Dict, Any, Optional, Union
|
|
12
|
+
from pydantic import BaseModel, Field
|
|
14
13
|
|
|
15
14
|
from llama_index.core.tools import FunctionTool
|
|
16
|
-
from llama_index.core.tools.function_tool import AsyncCallable
|
|
17
15
|
from llama_index.indices.managed.vectara import VectaraIndex
|
|
18
16
|
from llama_index.core.utilities.sql_wrapper import SQLDatabase
|
|
19
|
-
from llama_index.core.tools.types import
|
|
20
|
-
from llama_index.core.workflow.context import Context
|
|
17
|
+
from llama_index.core.tools.types import ToolOutput
|
|
21
18
|
|
|
22
19
|
from .types import ToolType
|
|
23
20
|
from .tools_catalog import ToolsCatalog, get_bad_topics
|
|
24
21
|
from .db_tools import DatabaseTools
|
|
25
22
|
from .utils import summarize_documents, is_float
|
|
26
23
|
from .agent_config import AgentConfig
|
|
24
|
+
from .tool_utils import (
|
|
25
|
+
create_tool_from_dynamic_function,
|
|
26
|
+
build_filter_string,
|
|
27
|
+
VectaraTool,
|
|
28
|
+
)
|
|
27
29
|
|
|
28
30
|
LI_packages = {
|
|
29
31
|
"yahoo_finance": ToolType.QUERY,
|
|
@@ -61,460 +63,6 @@ LI_packages = {
|
|
|
61
63
|
}
|
|
62
64
|
|
|
63
65
|
|
|
64
|
-
class VectaraToolMetadata(ToolMetadata):
|
|
65
|
-
"""
|
|
66
|
-
A subclass of ToolMetadata adding the tool_type attribute.
|
|
67
|
-
"""
|
|
68
|
-
|
|
69
|
-
tool_type: ToolType
|
|
70
|
-
|
|
71
|
-
def __init__(self, tool_type: ToolType, **kwargs):
|
|
72
|
-
super().__init__(**kwargs)
|
|
73
|
-
self.tool_type = tool_type
|
|
74
|
-
|
|
75
|
-
def __repr__(self) -> str:
|
|
76
|
-
"""
|
|
77
|
-
Returns a string representation of the VectaraToolMetadata object, including the tool_type attribute.
|
|
78
|
-
"""
|
|
79
|
-
base_repr = super().__repr__()
|
|
80
|
-
return f"{base_repr}, tool_type={self.tool_type}"
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
class VectaraTool(FunctionTool):
|
|
84
|
-
"""
|
|
85
|
-
A subclass of FunctionTool adding the tool_type attribute.
|
|
86
|
-
"""
|
|
87
|
-
|
|
88
|
-
def __init__(
|
|
89
|
-
self,
|
|
90
|
-
tool_type: ToolType,
|
|
91
|
-
metadata: ToolMetadata,
|
|
92
|
-
fn: Optional[Callable[..., Any]] = None,
|
|
93
|
-
async_fn: Optional[AsyncCallable] = None,
|
|
94
|
-
) -> None:
|
|
95
|
-
metadata_dict = (
|
|
96
|
-
metadata.dict() if hasattr(metadata, "dict") else metadata.__dict__
|
|
97
|
-
)
|
|
98
|
-
vm = VectaraToolMetadata(tool_type=tool_type, **metadata_dict)
|
|
99
|
-
super().__init__(fn, vm, async_fn)
|
|
100
|
-
|
|
101
|
-
@classmethod
|
|
102
|
-
def from_defaults(
|
|
103
|
-
cls,
|
|
104
|
-
fn: Optional[Callable[..., Any]] = None,
|
|
105
|
-
name: Optional[str] = None,
|
|
106
|
-
description: Optional[str] = None,
|
|
107
|
-
return_direct: bool = False,
|
|
108
|
-
fn_schema: Optional[Type[BaseModel]] = None,
|
|
109
|
-
async_fn: Optional[AsyncCallable] = None,
|
|
110
|
-
tool_metadata: Optional[ToolMetadata] = None,
|
|
111
|
-
callback: Optional[Callable[[Any], Any]] = None,
|
|
112
|
-
async_callback: Optional[AsyncCallable] = None,
|
|
113
|
-
tool_type: ToolType = ToolType.QUERY,
|
|
114
|
-
) -> "VectaraTool":
|
|
115
|
-
tool = FunctionTool.from_defaults(
|
|
116
|
-
fn,
|
|
117
|
-
name,
|
|
118
|
-
description,
|
|
119
|
-
return_direct,
|
|
120
|
-
fn_schema,
|
|
121
|
-
async_fn,
|
|
122
|
-
tool_metadata,
|
|
123
|
-
callback,
|
|
124
|
-
async_callback,
|
|
125
|
-
)
|
|
126
|
-
vectara_tool = cls(
|
|
127
|
-
tool_type=tool_type,
|
|
128
|
-
fn=tool.fn,
|
|
129
|
-
metadata=tool.metadata,
|
|
130
|
-
async_fn=tool.async_fn,
|
|
131
|
-
)
|
|
132
|
-
return vectara_tool
|
|
133
|
-
|
|
134
|
-
def __str__(self) -> str:
|
|
135
|
-
return f"Tool(name={self.metadata.name}, " f"Tool metadata={self.metadata})"
|
|
136
|
-
|
|
137
|
-
def __repr__(self) -> str:
|
|
138
|
-
return str(self)
|
|
139
|
-
|
|
140
|
-
def __eq__(self, other):
|
|
141
|
-
if not isinstance(other, VectaraTool):
|
|
142
|
-
return False
|
|
143
|
-
|
|
144
|
-
if self.metadata.tool_type != other.metadata.tool_type:
|
|
145
|
-
return False
|
|
146
|
-
|
|
147
|
-
if self.metadata.name != other.metadata.name:
|
|
148
|
-
return False
|
|
149
|
-
|
|
150
|
-
# If schema is a dict-like object, compare the dict representation
|
|
151
|
-
try:
|
|
152
|
-
# Try to get schema as dict if possible
|
|
153
|
-
if hasattr(self.metadata.fn_schema, "schema"):
|
|
154
|
-
self_schema = self.metadata.fn_schema.schema
|
|
155
|
-
other_schema = other.metadata.fn_schema.schema
|
|
156
|
-
|
|
157
|
-
# Compare only properties and required fields
|
|
158
|
-
self_props = self_schema.get("properties", {})
|
|
159
|
-
other_props = other_schema.get("properties", {})
|
|
160
|
-
|
|
161
|
-
self_required = self_schema.get("required", [])
|
|
162
|
-
other_required = other_schema.get("required", [])
|
|
163
|
-
|
|
164
|
-
return self_props.keys() == other_props.keys() and set(
|
|
165
|
-
self_required
|
|
166
|
-
) == set(other_required)
|
|
167
|
-
except Exception:
|
|
168
|
-
# If any exception occurs during schema comparison, fall back to name comparison
|
|
169
|
-
pass
|
|
170
|
-
|
|
171
|
-
return True
|
|
172
|
-
|
|
173
|
-
def call(
|
|
174
|
-
self, *args: Any, ctx: Optional[Context] = None, **kwargs: Any
|
|
175
|
-
) -> ToolOutput:
|
|
176
|
-
try:
|
|
177
|
-
return super().call(*args, ctx=ctx, **kwargs)
|
|
178
|
-
except TypeError as e:
|
|
179
|
-
sig = inspect.signature(self.metadata.fn_schema)
|
|
180
|
-
valid_parameters = list(sig.parameters.keys())
|
|
181
|
-
params_str = ", ".join(valid_parameters)
|
|
182
|
-
|
|
183
|
-
err_output = ToolOutput(
|
|
184
|
-
tool_name=self.metadata.name,
|
|
185
|
-
content=(
|
|
186
|
-
f"Wrong argument used when calling {self.metadata.name}: {str(e)}. "
|
|
187
|
-
f"Valid arguments: {params_str}. please call the tool again with the correct arguments."
|
|
188
|
-
),
|
|
189
|
-
raw_input={"args": args, "kwargs": kwargs},
|
|
190
|
-
raw_output={"response": str(e)},
|
|
191
|
-
)
|
|
192
|
-
return err_output
|
|
193
|
-
except Exception as e:
|
|
194
|
-
err_output = ToolOutput(
|
|
195
|
-
tool_name=self.metadata.name,
|
|
196
|
-
content=f"Tool {self.metadata.name} Malfunction: {str(e)}",
|
|
197
|
-
raw_input={"args": args, "kwargs": kwargs},
|
|
198
|
-
raw_output={"response": str(e)},
|
|
199
|
-
)
|
|
200
|
-
return err_output
|
|
201
|
-
|
|
202
|
-
async def acall(
|
|
203
|
-
self, *args: Any, ctx: Optional[Context] = None, **kwargs: Any
|
|
204
|
-
) -> ToolOutput:
|
|
205
|
-
try:
|
|
206
|
-
return await super().acall(*args, ctx=ctx, **kwargs)
|
|
207
|
-
except TypeError as e:
|
|
208
|
-
sig = inspect.signature(self.metadata.fn_schema)
|
|
209
|
-
valid_parameters = list(sig.parameters.keys())
|
|
210
|
-
params_str = ", ".join(valid_parameters)
|
|
211
|
-
|
|
212
|
-
err_output = ToolOutput(
|
|
213
|
-
tool_name=self.metadata.name,
|
|
214
|
-
content=(
|
|
215
|
-
f"Wrong argument used when calling {self.metadata.name}: {str(e)}. "
|
|
216
|
-
f"Valid arguments: {params_str}. please call the tool again with the correct arguments."
|
|
217
|
-
),
|
|
218
|
-
raw_input={"args": args, "kwargs": kwargs},
|
|
219
|
-
raw_output={"response": str(e)},
|
|
220
|
-
)
|
|
221
|
-
return err_output
|
|
222
|
-
except Exception as e:
|
|
223
|
-
err_output = ToolOutput(
|
|
224
|
-
tool_name=self.metadata.name,
|
|
225
|
-
content=f"Tool {self.metadata.name} Malfunction: {str(e)}",
|
|
226
|
-
raw_input={"args": args, "kwargs": kwargs},
|
|
227
|
-
raw_output={"response": str(e)},
|
|
228
|
-
)
|
|
229
|
-
return err_output
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
def _create_tool_from_dynamic_function(
|
|
233
|
-
function: Callable[..., ToolOutput],
|
|
234
|
-
tool_name: str,
|
|
235
|
-
tool_description: str,
|
|
236
|
-
base_params_model: Type[BaseModel], # Now a Pydantic BaseModel
|
|
237
|
-
tool_args_schema: Type[BaseModel],
|
|
238
|
-
compact_docstring: bool = False,
|
|
239
|
-
) -> VectaraTool:
|
|
240
|
-
fields = {}
|
|
241
|
-
base_params = []
|
|
242
|
-
|
|
243
|
-
if tool_args_schema is None:
|
|
244
|
-
|
|
245
|
-
class EmptyBaseModel(BaseModel):
|
|
246
|
-
"""empty base model"""
|
|
247
|
-
|
|
248
|
-
tool_args_schema = EmptyBaseModel
|
|
249
|
-
|
|
250
|
-
# Create inspect.Parameter objects for base_params_model fields.
|
|
251
|
-
for param_name, model_field in base_params_model.model_fields.items():
|
|
252
|
-
field_type = base_params_model.__annotations__.get(
|
|
253
|
-
param_name, str
|
|
254
|
-
) # default to str if not found
|
|
255
|
-
default_value = (
|
|
256
|
-
model_field.default
|
|
257
|
-
if model_field.default is not None
|
|
258
|
-
else inspect.Parameter.empty
|
|
259
|
-
)
|
|
260
|
-
base_params.append(
|
|
261
|
-
inspect.Parameter(
|
|
262
|
-
param_name,
|
|
263
|
-
inspect.Parameter.POSITIONAL_OR_KEYWORD,
|
|
264
|
-
default=default_value,
|
|
265
|
-
annotation=field_type,
|
|
266
|
-
)
|
|
267
|
-
)
|
|
268
|
-
fields[param_name] = (
|
|
269
|
-
field_type,
|
|
270
|
-
model_field.default if model_field.default is not None else ...,
|
|
271
|
-
)
|
|
272
|
-
|
|
273
|
-
# Add tool_args_schema fields to the fields dict if not already included.
|
|
274
|
-
# Also add them to the function signature by creating new inspect.Parameter objects.
|
|
275
|
-
for field_name, field_info in tool_args_schema.model_fields.items():
|
|
276
|
-
if field_name not in fields:
|
|
277
|
-
default_value = (
|
|
278
|
-
field_info.default if field_info.default is not None else ...
|
|
279
|
-
)
|
|
280
|
-
field_type = tool_args_schema.__annotations__.get(field_name, None)
|
|
281
|
-
fields[field_name] = (field_type, default_value)
|
|
282
|
-
# Append these fields to the signature.
|
|
283
|
-
base_params.append(
|
|
284
|
-
inspect.Parameter(
|
|
285
|
-
field_name,
|
|
286
|
-
inspect.Parameter.POSITIONAL_OR_KEYWORD,
|
|
287
|
-
default=(
|
|
288
|
-
default_value
|
|
289
|
-
if default_value is not ...
|
|
290
|
-
else inspect.Parameter.empty
|
|
291
|
-
),
|
|
292
|
-
annotation=field_type,
|
|
293
|
-
)
|
|
294
|
-
)
|
|
295
|
-
|
|
296
|
-
# Create the dynamic schema with both base_params_model and tool_args_schema fields.
|
|
297
|
-
fn_schema = create_model(f"{tool_name}_schema", **fields)
|
|
298
|
-
|
|
299
|
-
# Combine parameters into a function signature.
|
|
300
|
-
all_params = base_params[:] # Now all_params contains parameters from both models.
|
|
301
|
-
required_params = [p for p in all_params if p.default is inspect.Parameter.empty]
|
|
302
|
-
optional_params = [
|
|
303
|
-
p for p in all_params if p.default is not inspect.Parameter.empty
|
|
304
|
-
]
|
|
305
|
-
function.__signature__ = inspect.Signature(required_params + optional_params)
|
|
306
|
-
function.__annotations__["return"] = dict[str, Any]
|
|
307
|
-
function.__name__ = re.sub(r"[^A-Za-z0-9_]", "_", tool_name)
|
|
308
|
-
|
|
309
|
-
# Build a docstring using parameter descriptions from the BaseModels.
|
|
310
|
-
params_str = ", ".join(
|
|
311
|
-
f"{p.name}: {p.annotation.__name__ if hasattr(p.annotation, '__name__') else p.annotation}"
|
|
312
|
-
for p in all_params
|
|
313
|
-
)
|
|
314
|
-
signature_line = f"{tool_name}({params_str}) -> dict[str, Any]"
|
|
315
|
-
if compact_docstring:
|
|
316
|
-
doc_lines = [
|
|
317
|
-
tool_description.strip(),
|
|
318
|
-
]
|
|
319
|
-
else:
|
|
320
|
-
doc_lines = [
|
|
321
|
-
signature_line,
|
|
322
|
-
"",
|
|
323
|
-
tool_description.strip(),
|
|
324
|
-
]
|
|
325
|
-
doc_lines += [
|
|
326
|
-
"",
|
|
327
|
-
"Args:",
|
|
328
|
-
]
|
|
329
|
-
for param in all_params:
|
|
330
|
-
description = ""
|
|
331
|
-
if param.name in base_params_model.model_fields:
|
|
332
|
-
description = base_params_model.model_fields[param.name].description
|
|
333
|
-
elif param.name in tool_args_schema.model_fields:
|
|
334
|
-
description = tool_args_schema.model_fields[param.name].description
|
|
335
|
-
if not description:
|
|
336
|
-
description = ""
|
|
337
|
-
type_name = (
|
|
338
|
-
param.annotation.__name__
|
|
339
|
-
if hasattr(param.annotation, "__name__")
|
|
340
|
-
else str(param.annotation)
|
|
341
|
-
)
|
|
342
|
-
if (
|
|
343
|
-
param.default is not inspect.Parameter.empty
|
|
344
|
-
and param.default is not PydanticUndefined
|
|
345
|
-
):
|
|
346
|
-
default_text = f", default={param.default!r}"
|
|
347
|
-
else:
|
|
348
|
-
default_text = ""
|
|
349
|
-
doc_lines.append(f" - {param.name} ({type_name}){default_text}: {description}")
|
|
350
|
-
doc_lines.append("")
|
|
351
|
-
doc_lines.append("Returns:")
|
|
352
|
-
return_desc = getattr(
|
|
353
|
-
function, "__return_description__", "A dictionary containing the result data."
|
|
354
|
-
)
|
|
355
|
-
doc_lines.append(f" dict[str, Any]: {return_desc}")
|
|
356
|
-
|
|
357
|
-
initial_docstring = "\n".join(doc_lines)
|
|
358
|
-
collapsed_spaces = re.sub(r' {2,}', ' ', initial_docstring)
|
|
359
|
-
final_docstring = re.sub(r'\n{2,}', '\n', collapsed_spaces).strip()
|
|
360
|
-
function.__doc__ = final_docstring
|
|
361
|
-
|
|
362
|
-
tool = VectaraTool.from_defaults(
|
|
363
|
-
fn=function,
|
|
364
|
-
name=tool_name,
|
|
365
|
-
description=function.__doc__,
|
|
366
|
-
fn_schema=fn_schema,
|
|
367
|
-
tool_type=ToolType.QUERY,
|
|
368
|
-
)
|
|
369
|
-
return tool
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
Range = Tuple[float, float, bool, bool] # (min, max, min_inclusive, max_inclusive)
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
def _parse_range(val_str: str) -> Range:
|
|
376
|
-
"""
|
|
377
|
-
Parses '[1,10)' or '(0.5, 5]' etc.
|
|
378
|
-
Returns (start, end, start_incl, end_incl) or raises ValueError.
|
|
379
|
-
"""
|
|
380
|
-
m = re.match(
|
|
381
|
-
r"""
|
|
382
|
-
^([\[\(])\s* # opening bracket
|
|
383
|
-
([+-]?\d+(\.\d*)?)\s*, # first number
|
|
384
|
-
\s*([+-]?\d+(\.\d*)?) # second number
|
|
385
|
-
\s*([\]\)])$ # closing bracket
|
|
386
|
-
""",
|
|
387
|
-
val_str,
|
|
388
|
-
re.VERBOSE,
|
|
389
|
-
)
|
|
390
|
-
if not m:
|
|
391
|
-
raise ValueError(f"Invalid range syntax: {val_str!r}")
|
|
392
|
-
start_inc = m.group(1) == "["
|
|
393
|
-
end_inc = m.group(7) == "]"
|
|
394
|
-
start = float(m.group(2))
|
|
395
|
-
end = float(m.group(4))
|
|
396
|
-
if start > end:
|
|
397
|
-
raise ValueError(f"Range lower bound greater than upper bound: {val_str!r}")
|
|
398
|
-
return start, end, start_inc, end_inc
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
def _parse_comparison(val_str: str) -> Tuple[str, Union[float, str, bool]]:
|
|
402
|
-
"""
|
|
403
|
-
Parses '>10', '<=3.14', '!=foo', \"='bar'\" etc.
|
|
404
|
-
Returns (operator, rhs) or raises ValueError.
|
|
405
|
-
"""
|
|
406
|
-
# pick off the operator
|
|
407
|
-
comparison_operators = [">=", "<=", "!=", ">", "<", "="]
|
|
408
|
-
numeric_only_operators = {">", "<", ">=", "<="}
|
|
409
|
-
for op in comparison_operators:
|
|
410
|
-
if val_str.startswith(op):
|
|
411
|
-
rhs = val_str[len(op) :].strip()
|
|
412
|
-
if op in numeric_only_operators:
|
|
413
|
-
try:
|
|
414
|
-
rhs_val = float(rhs)
|
|
415
|
-
except ValueError as e:
|
|
416
|
-
raise ValueError(
|
|
417
|
-
f"Numeric comparison {op!r} must have a number, got {rhs!r}"
|
|
418
|
-
) from e
|
|
419
|
-
return op, rhs_val
|
|
420
|
-
# = and != can be bool, numeric, or string
|
|
421
|
-
low = rhs.lower()
|
|
422
|
-
if low in ("true", "false"):
|
|
423
|
-
return op, (low == "true")
|
|
424
|
-
try:
|
|
425
|
-
return op, float(rhs)
|
|
426
|
-
except ValueError:
|
|
427
|
-
return op, rhs
|
|
428
|
-
raise ValueError(f"No valid comparison operator at start of {val_str!r}")
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
def _build_filter_string(
|
|
432
|
-
kwargs: Dict[str, Any], tool_args_type: Dict[str, dict], fixed_filter: str
|
|
433
|
-
) -> str:
|
|
434
|
-
"""
|
|
435
|
-
Build filter string for Vectara from kwargs
|
|
436
|
-
"""
|
|
437
|
-
filter_parts = []
|
|
438
|
-
for key, raw in kwargs.items():
|
|
439
|
-
if raw is None or raw == "":
|
|
440
|
-
continue
|
|
441
|
-
|
|
442
|
-
if raw is PydanticUndefined:
|
|
443
|
-
raise ValueError(
|
|
444
|
-
f"Value of argument {key!r} is undefined, and this is invalid. "
|
|
445
|
-
"Please form proper arguments and try again."
|
|
446
|
-
)
|
|
447
|
-
|
|
448
|
-
tool_args_dict = tool_args_type.get(key, {"type": "doc", "is_list": False})
|
|
449
|
-
prefix = tool_args_dict.get("type", "doc")
|
|
450
|
-
is_list = tool_args_dict.get("is_list", False)
|
|
451
|
-
|
|
452
|
-
if prefix not in ("doc", "part"):
|
|
453
|
-
raise ValueError(
|
|
454
|
-
f'Unrecognized prefix {prefix!r}. Please make sure to use either "doc" or "part" for the prefix.'
|
|
455
|
-
)
|
|
456
|
-
|
|
457
|
-
# 1) native numeric
|
|
458
|
-
if isinstance(raw, (int, float)) or is_float(str(raw)):
|
|
459
|
-
val = str(raw)
|
|
460
|
-
if is_list:
|
|
461
|
-
filter_parts.append(f"({val} IN {prefix}.{key})")
|
|
462
|
-
else:
|
|
463
|
-
filter_parts.append(f"{prefix}.{key}={val}")
|
|
464
|
-
continue
|
|
465
|
-
|
|
466
|
-
# 2) native boolean
|
|
467
|
-
if isinstance(raw, bool):
|
|
468
|
-
val = "true" if raw else "false"
|
|
469
|
-
if is_list:
|
|
470
|
-
filter_parts.append(f"({val} IN {prefix}.{key})")
|
|
471
|
-
else:
|
|
472
|
-
filter_parts.append(f"{prefix}.{key}={val}")
|
|
473
|
-
continue
|
|
474
|
-
|
|
475
|
-
if not isinstance(raw, str):
|
|
476
|
-
raise ValueError(f"Unsupported type for {key!r}: {type(raw).__name__}")
|
|
477
|
-
|
|
478
|
-
val_str = raw.strip()
|
|
479
|
-
|
|
480
|
-
# 3) Range operator
|
|
481
|
-
if (val_str.startswith("[") or val_str.startswith("(")) and (
|
|
482
|
-
val_str.endswith("]") or val_str.endswith(")")
|
|
483
|
-
):
|
|
484
|
-
start, end, start_incl, end_incl = _parse_range(val_str)
|
|
485
|
-
conds = []
|
|
486
|
-
op1 = ">=" if start_incl else ">"
|
|
487
|
-
op2 = "<=" if end_incl else "<"
|
|
488
|
-
conds.append(f"{prefix}.{key} {op1} {start}")
|
|
489
|
-
conds.append(f"{prefix}.{key} {op2} {end}")
|
|
490
|
-
filter_parts.append("(" + " AND ".join(conds) + ")")
|
|
491
|
-
continue
|
|
492
|
-
|
|
493
|
-
# 4) comparison operator
|
|
494
|
-
try:
|
|
495
|
-
op, rhs = _parse_comparison(val_str)
|
|
496
|
-
except ValueError:
|
|
497
|
-
# no operator → treat as membership or equality-on-string
|
|
498
|
-
if is_list:
|
|
499
|
-
filter_parts.append(f"('{val_str}' IN {prefix}.{key})")
|
|
500
|
-
else:
|
|
501
|
-
filter_parts.append(f"{prefix}.{key}='{val_str}'")
|
|
502
|
-
else:
|
|
503
|
-
# normal comparison always binds to the field
|
|
504
|
-
if isinstance(rhs, bool):
|
|
505
|
-
rhs_sql = "true" if rhs else "false"
|
|
506
|
-
elif isinstance(rhs, (int, float)):
|
|
507
|
-
rhs_sql = str(rhs)
|
|
508
|
-
else:
|
|
509
|
-
rhs_sql = f"'{rhs}'"
|
|
510
|
-
filter_parts.append(f"{prefix}.{key}{op}{rhs_sql}")
|
|
511
|
-
|
|
512
|
-
joined = " AND ".join(filter_parts)
|
|
513
|
-
if fixed_filter and joined:
|
|
514
|
-
return f"({fixed_filter}) AND ({joined})"
|
|
515
|
-
return fixed_filter or joined
|
|
516
|
-
|
|
517
|
-
|
|
518
66
|
class VectaraToolFactory:
|
|
519
67
|
"""
|
|
520
68
|
A factory class for creating Vectara RAG tools.
|
|
@@ -561,6 +109,7 @@ class VectaraToolFactory:
|
|
|
561
109
|
mmr_diversity_bias: float = 0.2,
|
|
562
110
|
udf_expression: str = None,
|
|
563
111
|
rerank_chain: List[Dict] = None,
|
|
112
|
+
return_direct: bool = False,
|
|
564
113
|
save_history: bool = True,
|
|
565
114
|
verbose: bool = False,
|
|
566
115
|
vectara_base_url: str = "https://api.vectara.io",
|
|
@@ -595,6 +144,7 @@ class VectaraToolFactory:
|
|
|
595
144
|
"diversity_bias" for mmr, and "user_function" for udf).
|
|
596
145
|
If using slingshot/multilingual_reranker_v1, it must be first in the list.
|
|
597
146
|
save_history (bool, optional): Whether to save the query in history.
|
|
147
|
+
return_direct (bool, optional): Whether the agent should return the tool's response directly.
|
|
598
148
|
verbose (bool, optional): Whether to print verbose output.
|
|
599
149
|
vectara_base_url (str, optional): The base URL for the Vectara API.
|
|
600
150
|
vectara_verify_ssl (bool, optional): Whether to verify SSL certificates for the Vectara API.
|
|
@@ -630,7 +180,7 @@ class VectaraToolFactory:
|
|
|
630
180
|
else summarize_docs
|
|
631
181
|
)
|
|
632
182
|
try:
|
|
633
|
-
filter_string =
|
|
183
|
+
filter_string = build_filter_string(
|
|
634
184
|
kwargs, tool_args_type, fixed_filter
|
|
635
185
|
)
|
|
636
186
|
except ValueError as e:
|
|
@@ -718,7 +268,7 @@ class VectaraToolFactory:
|
|
|
718
268
|
description="The search query to perform, in the form of a question.",
|
|
719
269
|
)
|
|
720
270
|
top_k: int = Field(
|
|
721
|
-
10, description="The number of top documents to retrieve."
|
|
271
|
+
default=10, description="The number of top documents to retrieve."
|
|
722
272
|
)
|
|
723
273
|
summarize: bool = Field(
|
|
724
274
|
True,
|
|
@@ -742,7 +292,7 @@ class VectaraToolFactory:
|
|
|
742
292
|
+ "Use this tool to search for relevant documents, not to ask questions."
|
|
743
293
|
)
|
|
744
294
|
|
|
745
|
-
tool =
|
|
295
|
+
tool = create_tool_from_dynamic_function(
|
|
746
296
|
search_function,
|
|
747
297
|
tool_name,
|
|
748
298
|
search_tool_extra_desc,
|
|
@@ -753,6 +303,7 @@ class VectaraToolFactory:
|
|
|
753
303
|
),
|
|
754
304
|
tool_args_schema,
|
|
755
305
|
compact_docstring=self.compact_docstring,
|
|
306
|
+
return_direct=return_direct,
|
|
756
307
|
)
|
|
757
308
|
return tool
|
|
758
309
|
|
|
@@ -789,6 +340,7 @@ class VectaraToolFactory:
|
|
|
789
340
|
include_citations: bool = True,
|
|
790
341
|
save_history: bool = False,
|
|
791
342
|
fcs_threshold: float = 0.0,
|
|
343
|
+
return_direct: bool = False,
|
|
792
344
|
verbose: bool = False,
|
|
793
345
|
vectara_base_url: str = "https://api.vectara.io",
|
|
794
346
|
vectara_verify_ssl: bool = True,
|
|
@@ -841,6 +393,7 @@ class VectaraToolFactory:
|
|
|
841
393
|
save_history (bool, optional): Whether to save the query in history.
|
|
842
394
|
fcs_threshold (float, optional): A threshold for factual consistency.
|
|
843
395
|
If set above 0, the tool notifies the calling agent that it "cannot respond" if FCS is too low.
|
|
396
|
+
return_direct (bool, optional): Whether the agent should return the tool's response directly.
|
|
844
397
|
verbose (bool, optional): Whether to print verbose output.
|
|
845
398
|
vectara_base_url (str, optional): The base URL for the Vectara API.
|
|
846
399
|
vectara_verify_ssl (bool, optional): Whether to verify SSL certificates for the Vectara API.
|
|
@@ -870,7 +423,7 @@ class VectaraToolFactory:
|
|
|
870
423
|
|
|
871
424
|
query = kwargs.pop("query")
|
|
872
425
|
try:
|
|
873
|
-
filter_string =
|
|
426
|
+
filter_string = build_filter_string(
|
|
874
427
|
kwargs, tool_args_type, fixed_filter
|
|
875
428
|
)
|
|
876
429
|
except ValueError as e:
|
|
@@ -921,7 +474,10 @@ class VectaraToolFactory:
|
|
|
921
474
|
response = vectara_query_engine.query(query)
|
|
922
475
|
|
|
923
476
|
if len(response.source_nodes) == 0:
|
|
924
|
-
msg =
|
|
477
|
+
msg = (
|
|
478
|
+
"Tool failed to generate a response since no matches were found. "
|
|
479
|
+
"Please check the arguments and try again."
|
|
480
|
+
)
|
|
925
481
|
return ToolOutput(
|
|
926
482
|
tool_name=rag_function.__name__,
|
|
927
483
|
content=msg,
|
|
@@ -956,22 +512,27 @@ class VectaraToolFactory:
|
|
|
956
512
|
)
|
|
957
513
|
+ ".\n"
|
|
958
514
|
)
|
|
959
|
-
fcs =
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
515
|
+
fcs = 0.0
|
|
516
|
+
fcs_str = response.metadata["fcs"] if "fcs" in response.metadata else "0.0"
|
|
517
|
+
if fcs_str and is_float(fcs_str):
|
|
518
|
+
fcs = float(fcs_str)
|
|
519
|
+
if fcs < fcs_threshold:
|
|
520
|
+
msg = f"Could not answer the query due to suspected hallucination (fcs={fcs})."
|
|
521
|
+
return ToolOutput(
|
|
522
|
+
tool_name=rag_function.__name__,
|
|
523
|
+
content=msg,
|
|
524
|
+
raw_input={"args": args, "kwargs": kwargs},
|
|
525
|
+
raw_output={"response": msg},
|
|
526
|
+
)
|
|
968
527
|
res = {
|
|
969
528
|
"response": response.response,
|
|
970
529
|
"references_metadata": citation_metadata,
|
|
530
|
+
"fcs_score": fcs,
|
|
971
531
|
}
|
|
972
532
|
if len(citation_metadata) > 0:
|
|
973
533
|
tool_output = f"""
|
|
974
534
|
Response: '''{res['response']}'''
|
|
535
|
+
fcs_score: {res['fcs_score']:.4f}
|
|
975
536
|
References:
|
|
976
537
|
{res['references_metadata']}
|
|
977
538
|
"""
|
|
@@ -993,13 +554,14 @@ class VectaraToolFactory:
|
|
|
993
554
|
description="The search query to perform, in the form of a question",
|
|
994
555
|
)
|
|
995
556
|
|
|
996
|
-
tool =
|
|
557
|
+
tool = create_tool_from_dynamic_function(
|
|
997
558
|
rag_function,
|
|
998
559
|
tool_name,
|
|
999
560
|
tool_description,
|
|
1000
561
|
RagToolBaseParams,
|
|
1001
562
|
tool_args_schema,
|
|
1002
563
|
compact_docstring=self.compact_docstring,
|
|
564
|
+
return_direct=return_direct,
|
|
1003
565
|
)
|
|
1004
566
|
return tool
|
|
1005
567
|
|
vectara_agentic/tools_catalog.py
CHANGED
|
@@ -10,7 +10,8 @@ from pydantic import Field
|
|
|
10
10
|
|
|
11
11
|
from .types import LLMRole
|
|
12
12
|
from .agent_config import AgentConfig
|
|
13
|
-
from .
|
|
13
|
+
from .llm_utils import get_llm
|
|
14
|
+
from .utils import remove_self_from_signature
|
|
14
15
|
|
|
15
16
|
req_session = requests.Session()
|
|
16
17
|
|
|
@@ -24,7 +25,7 @@ get_headers = {
|
|
|
24
25
|
|
|
25
26
|
def get_current_date() -> str:
|
|
26
27
|
"""
|
|
27
|
-
Returns
|
|
28
|
+
Returns the current date as a string.
|
|
28
29
|
"""
|
|
29
30
|
return date.today().strftime("%A, %B %d, %Y")
|
|
30
31
|
|