vectara-agentic 0.2.12__py3-none-any.whl → 0.2.14__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.

vectara_agentic/tools.py CHANGED
@@ -8,31 +8,35 @@ import importlib
8
8
  import os
9
9
  import asyncio
10
10
 
11
- from typing import Callable, List, Dict, Any, Optional, Union, Type
12
- from pydantic import BaseModel, Field, create_model
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 ToolMetadata, ToolOutput
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
- from .utils import is_float, summarize_documents
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,
30
32
  "arxiv": ToolType.QUERY,
31
33
  "tavily_research": ToolType.QUERY,
32
34
  "exa": ToolType.QUERY,
33
- "brave": ToolType.QUERY,
35
+ "brave_search": ToolType.QUERY,
36
+ "bing_search": ToolType.QUERY,
34
37
  "neo4j": ToolType.QUERY,
35
38
  "kuzu": ToolType.QUERY,
39
+ "wikipedia": ToolType.QUERY,
36
40
  "google": {
37
41
  "GmailToolSpec": {
38
42
  "load_data": ToolType.QUERY,
@@ -58,427 +62,6 @@ LI_packages = {
58
62
  },
59
63
  }
60
64
 
61
-
62
- class VectaraToolMetadata(ToolMetadata):
63
- """
64
- A subclass of ToolMetadata adding the tool_type attribute.
65
- """
66
-
67
- tool_type: ToolType
68
-
69
- def __init__(self, tool_type: ToolType, **kwargs):
70
- super().__init__(**kwargs)
71
- self.tool_type = tool_type
72
-
73
- def __repr__(self) -> str:
74
- """
75
- Returns a string representation of the VectaraToolMetadata object, including the tool_type attribute.
76
- """
77
- base_repr = super().__repr__()
78
- return f"{base_repr}, tool_type={self.tool_type}"
79
-
80
-
81
- class VectaraTool(FunctionTool):
82
- """
83
- A subclass of FunctionTool adding the tool_type attribute.
84
- """
85
-
86
- def __init__(
87
- self,
88
- tool_type: ToolType,
89
- metadata: ToolMetadata,
90
- fn: Optional[Callable[..., Any]] = None,
91
- async_fn: Optional[AsyncCallable] = None,
92
- ) -> None:
93
- metadata_dict = (
94
- metadata.dict() if hasattr(metadata, "dict") else metadata.__dict__
95
- )
96
- vm = VectaraToolMetadata(tool_type=tool_type, **metadata_dict)
97
- super().__init__(fn, vm, async_fn)
98
-
99
- @classmethod
100
- def from_defaults(
101
- cls,
102
- fn: Optional[Callable[..., Any]] = None,
103
- name: Optional[str] = None,
104
- description: Optional[str] = None,
105
- return_direct: bool = False,
106
- fn_schema: Optional[Type[BaseModel]] = None,
107
- async_fn: Optional[AsyncCallable] = None,
108
- tool_metadata: Optional[ToolMetadata] = None,
109
- callback: Optional[Callable[[Any], Any]] = None,
110
- async_callback: Optional[AsyncCallable] = None,
111
- tool_type: ToolType = ToolType.QUERY,
112
- ) -> "VectaraTool":
113
- tool = FunctionTool.from_defaults(
114
- fn,
115
- name,
116
- description,
117
- return_direct,
118
- fn_schema,
119
- async_fn,
120
- tool_metadata,
121
- callback,
122
- async_callback,
123
- )
124
- vectara_tool = cls(
125
- tool_type=tool_type,
126
- fn=tool.fn,
127
- metadata=tool.metadata,
128
- async_fn=tool.async_fn,
129
- )
130
- return vectara_tool
131
-
132
- def __str__(self) -> str:
133
- return f"Tool(name={self.metadata.name}, " f"Tool metadata={self.metadata})"
134
-
135
- def __repr__(self) -> str:
136
- return str(self)
137
-
138
- def __eq__(self, other):
139
- if not isinstance(other, VectaraTool):
140
- return False
141
-
142
- if self.metadata.tool_type != other.metadata.tool_type:
143
- return False
144
-
145
- if self.metadata.name != other.metadata.name:
146
- return False
147
-
148
- # If schema is a dict-like object, compare the dict representation
149
- try:
150
- # Try to get schema as dict if possible
151
- if hasattr(self.metadata.fn_schema, "schema"):
152
- self_schema = self.metadata.fn_schema.schema
153
- other_schema = other.metadata.fn_schema.schema
154
-
155
- # Compare only properties and required fields
156
- self_props = self_schema.get("properties", {})
157
- other_props = other_schema.get("properties", {})
158
-
159
- self_required = self_schema.get("required", [])
160
- other_required = other_schema.get("required", [])
161
-
162
- return self_props.keys() == other_props.keys() and set(
163
- self_required
164
- ) == set(other_required)
165
- except Exception:
166
- # If any exception occurs during schema comparison, fall back to name comparison
167
- pass
168
-
169
- return True
170
-
171
- def call(
172
- self, *args: Any, ctx: Optional[Context] = None, **kwargs: Any
173
- ) -> ToolOutput:
174
- try:
175
- return super().call(*args, ctx=ctx, **kwargs)
176
- except TypeError as e:
177
- sig = inspect.signature(self.metadata.fn_schema)
178
- valid_parameters = list(sig.parameters.keys())
179
- params_str = ", ".join(valid_parameters)
180
-
181
- err_output = ToolOutput(
182
- tool_name=self.metadata.name,
183
- content=(
184
- f"Wrong argument used when calling {self.metadata.name}: {str(e)}. "
185
- f"Valid arguments: {params_str}. please call the tool again with the correct arguments."
186
- ),
187
- raw_input={"args": args, "kwargs": kwargs},
188
- raw_output={"response": str(e)},
189
- )
190
- return err_output
191
- except Exception as e:
192
- err_output = ToolOutput(
193
- tool_name=self.metadata.name,
194
- content=f"Tool {self.metadata.name} Malfunction: {str(e)}",
195
- raw_input={"args": args, "kwargs": kwargs},
196
- raw_output={"response": str(e)},
197
- )
198
- return err_output
199
-
200
- async def acall(
201
- self, *args: Any, ctx: Optional[Context] = None, **kwargs: Any
202
- ) -> ToolOutput:
203
- try:
204
- return await super().acall(*args, ctx=ctx, **kwargs)
205
- except TypeError as e:
206
- sig = inspect.signature(self.metadata.fn_schema)
207
- valid_parameters = list(sig.parameters.keys())
208
- params_str = ", ".join(valid_parameters)
209
-
210
- err_output = ToolOutput(
211
- tool_name=self.metadata.name,
212
- content=(
213
- f"Wrong argument used when calling {self.metadata.name}: {str(e)}. "
214
- f"Valid arguments: {params_str}. please call the tool again with the correct arguments."
215
- ),
216
- raw_input={"args": args, "kwargs": kwargs},
217
- raw_output={"response": str(e)},
218
- )
219
- return err_output
220
- except Exception as e:
221
- err_output = ToolOutput(
222
- tool_name=self.metadata.name,
223
- content=f"Tool {self.metadata.name} Malfunction: {str(e)}",
224
- raw_input={"args": args, "kwargs": kwargs},
225
- raw_output={"response": str(e)},
226
- )
227
- return err_output
228
-
229
-
230
- def _create_tool_from_dynamic_function(
231
- function: Callable[..., ToolOutput],
232
- tool_name: str,
233
- tool_description: str,
234
- base_params_model: Type[BaseModel], # Now a Pydantic BaseModel
235
- tool_args_schema: Type[BaseModel],
236
- ) -> VectaraTool:
237
- fields = {}
238
- base_params = []
239
-
240
- # Create inspect.Parameter objects for base_params_model fields.
241
- for param_name, model_field in base_params_model.model_fields.items():
242
- field_type = base_params_model.__annotations__.get(
243
- param_name, str
244
- ) # default to str if not found
245
- default_value = (
246
- model_field.default
247
- if model_field.default is not None
248
- else inspect.Parameter.empty
249
- )
250
- base_params.append(
251
- inspect.Parameter(
252
- param_name,
253
- inspect.Parameter.POSITIONAL_OR_KEYWORD,
254
- default=default_value,
255
- annotation=field_type,
256
- )
257
- )
258
- fields[param_name] = (
259
- field_type,
260
- model_field.default if model_field.default is not None else ...,
261
- )
262
-
263
- # Add tool_args_schema fields to the fields dict if not already included.
264
- # Also add them to the function signature by creating new inspect.Parameter objects.
265
- for field_name, field_info in tool_args_schema.model_fields.items():
266
- if field_name not in fields:
267
- default_value = (
268
- field_info.default if field_info.default is not None else ...
269
- )
270
- field_type = tool_args_schema.__annotations__.get(field_name, None)
271
- fields[field_name] = (field_type, default_value)
272
- # Append these fields to the signature.
273
- base_params.append(
274
- inspect.Parameter(
275
- field_name,
276
- inspect.Parameter.POSITIONAL_OR_KEYWORD,
277
- default=(
278
- default_value
279
- if default_value is not ...
280
- else inspect.Parameter.empty
281
- ),
282
- annotation=field_type,
283
- )
284
- )
285
-
286
- # Create the dynamic schema with both base_params_model and tool_args_schema fields.
287
- fn_schema = create_model(f"{tool_name}_schema", **fields)
288
-
289
- # Combine parameters into a function signature.
290
- all_params = base_params[:] # Now all_params contains parameters from both models.
291
- required_params = [p for p in all_params if p.default is inspect.Parameter.empty]
292
- optional_params = [
293
- p for p in all_params if p.default is not inspect.Parameter.empty
294
- ]
295
- function.__signature__ = inspect.Signature(required_params + optional_params)
296
- function.__annotations__["return"] = dict[str, Any]
297
- function.__name__ = re.sub(r"[^A-Za-z0-9_]", "_", tool_name)
298
-
299
- # Build a docstring using parameter descriptions from the BaseModels.
300
- params_str = ",\n ".join(
301
- f"{p.name}: {p.annotation.__name__ if hasattr(p.annotation, '__name__') else p.annotation}"
302
- for p in all_params
303
- )
304
- signature_line = f"{tool_name}(\n {params_str}\n) -> dict[str, Any]"
305
- doc_lines = [
306
- signature_line,
307
- "",
308
- tool_description.strip(),
309
- "",
310
- "Args:",
311
- ]
312
- for param in all_params:
313
- description = ""
314
- if param.name in base_params_model.model_fields:
315
- description = base_params_model.model_fields[param.name].description
316
- elif param.name in tool_args_schema.model_fields:
317
- description = tool_args_schema.model_fields[param.name].description
318
- if not description:
319
- description = "No description provided."
320
- type_name = (
321
- param.annotation.__name__
322
- if hasattr(param.annotation, "__name__")
323
- else str(param.annotation)
324
- )
325
- default_text = (
326
- f", default={param.default!r}"
327
- if param.default is not inspect.Parameter.empty
328
- else ""
329
- )
330
- doc_lines.append(f" {param.name} ({type_name}){default_text}: {description}")
331
- doc_lines.append("")
332
- doc_lines.append("Returns:")
333
- return_desc = getattr(
334
- function, "__return_description__", "A dictionary containing the result data."
335
- )
336
- doc_lines.append(f" dict[str, Any]: {return_desc}")
337
- function.__doc__ = "\n".join(doc_lines)
338
-
339
- tool = VectaraTool.from_defaults(
340
- fn=function,
341
- name=tool_name,
342
- description=function.__doc__,
343
- fn_schema=fn_schema,
344
- tool_type=ToolType.QUERY,
345
- )
346
- return tool
347
-
348
-
349
- def _build_filter_string(
350
- kwargs: Dict[str, Any], tool_args_type: Dict[str, dict], fixed_filter: str
351
- ) -> str:
352
- """
353
- Build filter string for Vectara from kwargs
354
- """
355
- filter_parts = []
356
- comparison_operators = [">=", "<=", "!=", ">", "<", "="]
357
- numeric_only_ops = {">", "<", ">=", "<="}
358
-
359
- for key, value in kwargs.items():
360
- if value is None or value == "":
361
- continue
362
-
363
- # Determine the prefix for the key. Valid values are "doc" or "part"
364
- # default to 'doc' if not specified
365
- tool_args_dict = tool_args_type.get(key, {"type": "doc", "is_list": False})
366
- prefix = tool_args_dict.get(key, "doc")
367
- is_list = tool_args_dict.get("is_list", False)
368
-
369
- if prefix not in ["doc", "part"]:
370
- raise ValueError(
371
- f'Unrecognized prefix {prefix}. Please make sure to use either "doc" or "part" for the prefix.'
372
- )
373
-
374
- if value is PydanticUndefined:
375
- raise ValueError(
376
- f"Value of argument {key} is undefined, and this is invalid."
377
- "Please form proper arguments and try again."
378
- )
379
-
380
- # value of the argument
381
- val_str = str(value).strip()
382
-
383
- # Special handling for range operator
384
- if val_str.startswith(("[", "(")) and val_str.endswith(("]", ")")):
385
- # Extract the boundary types
386
- start_inclusive = val_str.startswith("[")
387
- end_inclusive = val_str.endswith("]")
388
-
389
- # Remove the boundaries and strip whitespace
390
- val_str = val_str[1:-1].strip()
391
-
392
- if "," in val_str:
393
- val_str = val_str.split(",")
394
- if len(val_str) != 2:
395
- raise ValueError(
396
- f"Range operator requires two values for {key}: {value}"
397
- )
398
-
399
- # Validate both bounds as numeric or empty (for unbounded ranges)
400
- start_val, end_val = val_str[0].strip(), val_str[1].strip()
401
- if start_val and not (start_val.isdigit() or is_float(start_val)):
402
- raise ValueError(
403
- f"Range operator requires numeric operands for {key}: {value}"
404
- )
405
- if end_val and not (end_val.isdigit() or is_float(end_val)):
406
- raise ValueError(
407
- f"Range operator requires numeric operands for {key}: {value}"
408
- )
409
-
410
- # Build the SQL condition
411
- range_conditions = []
412
- if start_val:
413
- operator = ">=" if start_inclusive else ">"
414
- range_conditions.append(f"{prefix}.{key} {operator} {start_val}")
415
- if end_val:
416
- operator = "<=" if end_inclusive else "<"
417
- range_conditions.append(f"{prefix}.{key} {operator} {end_val}")
418
-
419
- # Join the range conditions with AND
420
- filter_parts.append("( " + " AND ".join(range_conditions) + " )")
421
- continue
422
-
423
- raise ValueError(f"Range operator requires two values for {key}: {value}")
424
-
425
- # Check if value contains a known comparison operator at the start
426
- matched_operator = None
427
- for op in comparison_operators:
428
- if val_str.startswith(op):
429
- matched_operator = op
430
- break
431
-
432
- # Break down operator from value
433
- # e.g. val_str = ">2022" --> operator = ">", rhs = "2022"
434
- if matched_operator:
435
- rhs = val_str[len(matched_operator) :].strip()
436
-
437
- if matched_operator in numeric_only_ops:
438
- # Must be numeric
439
- if not (rhs.isdigit() or is_float(rhs)):
440
- raise ValueError(
441
- f"Operator {matched_operator} requires a numeric operand for {key}: {val_str}"
442
- )
443
- filter_parts.append(f"{prefix}.{key}{matched_operator}{rhs}")
444
- else:
445
- # = and != operators can be numeric or string
446
- if rhs.isdigit() or is_float(rhs):
447
- filter_parts.append(f"{prefix}.{key}{matched_operator}{rhs}")
448
- elif rhs.lower() in ["true", "false"]:
449
- filter_parts.append(
450
- f"{prefix}.{key}{matched_operator}{rhs.lower()}"
451
- )
452
- else:
453
- # For string operands, wrap them in quotes
454
- filter_parts.append(f"{prefix}.{key}{matched_operator}'{rhs}'")
455
- else:
456
- if val_str.isdigit() or is_float(val_str):
457
- if is_list:
458
- filter_parts.append(f"({val_str} IN {prefix}.{key})")
459
- else:
460
- filter_parts.append(f"{prefix}.{key}={val_str}")
461
- elif val_str.lower() in ["true", "false"]:
462
- # This is to handle boolean values.
463
- # This is not complete solution - the best solution would be to test if the field is boolean
464
- # That can be done after we move to APIv2
465
- if is_list:
466
- filter_parts.append(f"({val_str.lower()} IN {prefix}.{key})")
467
- else:
468
- filter_parts.append(f"{prefix}.{key}={val_str.lower()}")
469
- else:
470
- if is_list:
471
- filter_parts.append(f"('{val_str}' IN {prefix}.{key})")
472
- else:
473
- filter_parts.append(f"{prefix}.{key}='{val_str}'")
474
-
475
- filter_str = " AND ".join(filter_parts)
476
- if fixed_filter and filter_str:
477
- return f"({fixed_filter}) AND ({filter_str})"
478
- else:
479
- return fixed_filter or filter_str
480
-
481
-
482
65
  class VectaraToolFactory:
483
66
  """
484
67
  A factory class for creating Vectara RAG tools.
@@ -488,25 +71,29 @@ class VectaraToolFactory:
488
71
  self,
489
72
  vectara_corpus_key: str = str(os.environ.get("VECTARA_CORPUS_KEY", "")),
490
73
  vectara_api_key: str = str(os.environ.get("VECTARA_API_KEY", "")),
74
+ compact_docstring: bool = False,
491
75
  ) -> None:
492
76
  """
493
77
  Initialize the VectaraToolFactory
494
78
  Args:
495
79
  vectara_corpus_key (str): The Vectara corpus key (or comma separated list of keys).
496
80
  vectara_api_key (str): The Vectara API key.
81
+ compact_docstring (bool): Whether to use a compact docstring format for tools
82
+ This is useful if OpenAI complains on the 1024 token limit.
497
83
  """
498
84
  self.vectara_corpus_key = vectara_corpus_key
499
85
  self.vectara_api_key = vectara_api_key
500
86
  self.num_corpora = len(vectara_corpus_key.split(","))
501
- self.cache_expiry = 60 * 60 # 1 hour
502
- self.max_cache_size = 128
87
+ self.compact_docstring = compact_docstring
503
88
 
504
89
  def create_search_tool(
505
90
  self,
506
91
  tool_name: str,
507
92
  tool_description: str,
508
- tool_args_schema: type[BaseModel],
93
+ tool_args_schema: type[BaseModel] = None,
509
94
  tool_args_type: Dict[str, str] = {},
95
+ summarize_docs: Optional[bool] = None,
96
+ summarize_llm_name: Optional[str] = None,
510
97
  fixed_filter: str = "",
511
98
  lambda_val: Union[List[float], float] = 0.005,
512
99
  semantics: Union[List[str] | str] = "default",
@@ -532,7 +119,7 @@ class VectaraToolFactory:
532
119
  Args:
533
120
  tool_name (str): The name of the tool.
534
121
  tool_description (str): The description of the tool.
535
- tool_args_schema (BaseModel): The schema for the tool arguments.
122
+ tool_args_schema (BaseModel, optional): The schema for the tool arguments.
536
123
  tool_args_type (Dict[str, str], optional): The type of each argument (doc or part).
537
124
  fixed_filter (str, optional): A fixed Vectara filter condition to apply to all queries.
538
125
  lambda_val (Union[List[float] | float], optional): Lambda value (or list of values for each corpora)
@@ -584,7 +171,11 @@ class VectaraToolFactory:
584
171
 
585
172
  query = kwargs.pop("query")
586
173
  top_k = kwargs.pop("top_k", 10)
587
- summarize = kwargs.pop("summarize", True)
174
+ summarize = (
175
+ kwargs.pop("summarize", True)
176
+ if summarize_docs is None
177
+ else summarize_docs
178
+ )
588
179
  try:
589
180
  filter_string = _build_filter_string(
590
181
  kwargs, tool_args_type, fixed_filter
@@ -643,7 +234,10 @@ class VectaraToolFactory:
643
234
  if summarize:
644
235
  summaries_dict = asyncio.run(
645
236
  summarize_documents(
646
- self.vectara_corpus_key, self.vectara_api_key, list(unique_ids)
237
+ corpus_key=self.vectara_corpus_key,
238
+ api_key=self.vectara_api_key,
239
+ llm_name=summarize_llm_name,
240
+ doc_ids=list(unique_ids),
647
241
  )
648
242
  )
649
243
  for doc_id, metadata in docs:
@@ -665,30 +259,47 @@ class VectaraToolFactory:
665
259
 
666
260
  class SearchToolBaseParams(BaseModel):
667
261
  """Model for the base parameters of the search tool."""
262
+
668
263
  query: str = Field(
669
264
  ...,
670
- description="The search query to perform, always in the form of a question.",
265
+ description="The search query to perform, in the form of a question.",
671
266
  )
672
267
  top_k: int = Field(
673
- 10, description="The number of top documents to retrieve."
268
+ default=10, description="The number of top documents to retrieve."
674
269
  )
675
270
  summarize: bool = Field(
676
271
  True,
677
- description="Flag that indicates whether to summarize the retrieved documents.",
272
+ description="Whether to summarize the retrieved documents.",
273
+ )
274
+
275
+ class SearchToolBaseParamsWithoutSummarize(BaseModel):
276
+ """Model for the base parameters of the search tool."""
277
+
278
+ query: str = Field(
279
+ ...,
280
+ description="The search query to perform, in the form of a question.",
281
+ )
282
+ top_k: int = Field(
283
+ 10, description="The number of top documents to retrieve."
678
284
  )
679
285
 
680
286
  search_tool_extra_desc = (
681
287
  tool_description
682
288
  + "\n"
683
- + "This tool is meant to perform a search for relevant documents, it is not meant for asking questions."
289
+ + "Use this tool to search for relevant documents, not to ask questions."
684
290
  )
685
291
 
686
292
  tool = _create_tool_from_dynamic_function(
687
293
  search_function,
688
294
  tool_name,
689
295
  search_tool_extra_desc,
690
- SearchToolBaseParams,
296
+ (
297
+ SearchToolBaseParams
298
+ if summarize_docs is None
299
+ else SearchToolBaseParamsWithoutSummarize
300
+ ),
691
301
  tool_args_schema,
302
+ compact_docstring=self.compact_docstring,
692
303
  )
693
304
  return tool
694
305
 
@@ -696,7 +307,7 @@ class VectaraToolFactory:
696
307
  self,
697
308
  tool_name: str,
698
309
  tool_description: str,
699
- tool_args_schema: type[BaseModel],
310
+ tool_args_schema: type[BaseModel] = None,
700
311
  tool_args_type: Dict[str, dict] = {},
701
312
  fixed_filter: str = "",
702
313
  vectara_summarizer: str = "vectara-summary-ext-24-05-med-omni",
@@ -718,6 +329,7 @@ class VectaraToolFactory:
718
329
  rerank_chain: List[Dict] = None,
719
330
  max_response_chars: Optional[int] = None,
720
331
  max_tokens: Optional[int] = None,
332
+ llm_name: Optional[str] = None,
721
333
  temperature: Optional[float] = None,
722
334
  frequency_penalty: Optional[float] = None,
723
335
  presence_penalty: Optional[float] = None,
@@ -734,7 +346,7 @@ class VectaraToolFactory:
734
346
  Args:
735
347
  tool_name (str): The name of the tool.
736
348
  tool_description (str): The description of the tool.
737
- tool_args_schema (BaseModel): The schema for the tool arguments.
349
+ tool_args_schema (BaseModel, optional): The schema for any tool arguments for filtering.
738
350
  tool_args_type (Dict[str, dict], optional): attributes for each argument where they key is the field name
739
351
  and the value is a dictionary with the following keys:
740
352
  - 'type': the type of each filter attribute in Vectara (doc or part).
@@ -765,6 +377,7 @@ class VectaraToolFactory:
765
377
  If using slingshot/multilingual_reranker_v1, it must be first in the list.
766
378
  max_response_chars (int, optional): The desired maximum number of characters for the generated summary.
767
379
  max_tokens (int, optional): The maximum number of tokens to be returned by the LLM.
380
+ llm_name (str, optional): The name of the LLM to use for generation.
768
381
  temperature (float, optional): The sampling temperature; higher values lead to more randomness.
769
382
  frequency_penalty (float, optional): How much to penalize repeating tokens in the response,
770
383
  higher values reducing likelihood of repeating the same line.
@@ -842,6 +455,7 @@ class VectaraToolFactory:
842
455
  filter=filter_string,
843
456
  max_response_chars=max_response_chars,
844
457
  max_tokens=max_tokens,
458
+ llm_name=llm_name,
845
459
  temperature=temperature,
846
460
  frequency_penalty=frequency_penalty,
847
461
  presence_penalty=presence_penalty,
@@ -889,22 +503,27 @@ class VectaraToolFactory:
889
503
  )
890
504
  + ".\n"
891
505
  )
892
- fcs = response.metadata["fcs"] if "fcs" in response.metadata else 0.0
893
- if fcs and fcs < fcs_threshold:
894
- msg = f"Could not answer the query due to suspected hallucination (fcs={fcs})."
895
- return ToolOutput(
896
- tool_name=rag_function.__name__,
897
- content=msg,
898
- raw_input={"args": args, "kwargs": kwargs},
899
- raw_output={"response": msg},
900
- )
506
+ fcs = 0.0
507
+ fcs_str = response.metadata["fcs"] if "fcs" in response.metadata else "0.0"
508
+ if fcs_str and is_float(fcs_str):
509
+ fcs = float(fcs_str)
510
+ if fcs < fcs_threshold:
511
+ msg = f"Could not answer the query due to suspected hallucination (fcs={fcs})."
512
+ return ToolOutput(
513
+ tool_name=rag_function.__name__,
514
+ content=msg,
515
+ raw_input={"args": args, "kwargs": kwargs},
516
+ raw_output={"response": msg},
517
+ )
901
518
  res = {
902
519
  "response": response.response,
903
520
  "references_metadata": citation_metadata,
521
+ "fcs_score": fcs,
904
522
  }
905
523
  if len(citation_metadata) > 0:
906
524
  tool_output = f"""
907
525
  Response: '''{res['response']}'''
526
+ fcs_score: {res['fcs_score']:.4f}
908
527
  References:
909
528
  {res['references_metadata']}
910
529
  """
@@ -920,9 +539,10 @@ class VectaraToolFactory:
920
539
 
921
540
  class RagToolBaseParams(BaseModel):
922
541
  """Model for the base parameters of the RAG tool."""
542
+
923
543
  query: str = Field(
924
544
  ...,
925
- description="The search query to perform, always in the form of a question",
545
+ description="The search query to perform, in the form of a question",
926
546
  )
927
547
 
928
548
  tool = _create_tool_from_dynamic_function(
@@ -931,6 +551,7 @@ class VectaraToolFactory:
931
551
  tool_description,
932
552
  RagToolBaseParams,
933
553
  tool_args_schema,
554
+ compact_docstring=self.compact_docstring,
934
555
  )
935
556
  return tool
936
557