vectara-agentic 0.1.14__py3-none-any.whl → 0.1.16__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
@@ -18,13 +18,7 @@ from llama_index.core.tools.types import ToolMetadata, ToolOutput
18
18
 
19
19
 
20
20
  from .types import ToolType
21
- from .tools_catalog import (
22
- summarize_text,
23
- rephrase_text,
24
- critique_text,
25
- avoid_topics_tool,
26
- db_load_sample_data
27
- )
21
+ from .tools_catalog import summarize_text, rephrase_text, critique_text, get_bad_topics, DBLoadSampleData
28
22
 
29
23
  LI_packages = {
30
24
  "yahoo_finance": ToolType.QUERY,
@@ -51,24 +45,42 @@ LI_packages = {
51
45
  }
52
46
 
53
47
 
48
+ class VectaraToolMetadata(ToolMetadata):
49
+ """
50
+ A subclass of ToolMetadata adding the tool_type attribute.
51
+ """
52
+ tool_type: ToolType
53
+
54
+ def __init__(self, tool_type: ToolType, **kwargs):
55
+ super().__init__(**kwargs)
56
+ self.tool_type = tool_type
57
+
58
+ def __repr__(self) -> str:
59
+ """
60
+ Returns a string representation of the VectaraToolMetadata object, including the tool_type attribute.
61
+ """
62
+ base_repr = super().__repr__()
63
+ return f"{base_repr}, tool_type={self.tool_type}"
64
+
54
65
  class VectaraTool(FunctionTool):
55
66
  """
56
67
  A subclass of FunctionTool adding the tool_type attribute.
57
68
  """
69
+
58
70
  def __init__(
59
71
  self,
60
72
  tool_type: ToolType,
73
+ metadata: ToolMetadata,
61
74
  fn: Optional[Callable[..., Any]] = None,
62
- metadata: Optional[ToolMetadata] = None,
63
75
  async_fn: Optional[AsyncCallable] = None,
64
76
  ) -> None:
65
- self.tool_type = tool_type
66
- super().__init__(fn, metadata, async_fn)
77
+ metadata_dict = metadata.dict() if hasattr(metadata, 'dict') else metadata.__dict__
78
+ vm = VectaraToolMetadata(tool_type=tool_type, **metadata_dict)
79
+ super().__init__(fn, vm, async_fn)
67
80
 
68
81
  @classmethod
69
82
  def from_defaults(
70
83
  cls,
71
- tool_type: ToolType,
72
84
  fn: Optional[Callable[..., Any]] = None,
73
85
  name: Optional[str] = None,
74
86
  description: Optional[str] = None,
@@ -76,18 +88,14 @@ class VectaraTool(FunctionTool):
76
88
  fn_schema: Optional[Type[BaseModel]] = None,
77
89
  async_fn: Optional[AsyncCallable] = None,
78
90
  tool_metadata: Optional[ToolMetadata] = None,
91
+ tool_type: ToolType = ToolType.QUERY,
79
92
  ) -> "VectaraTool":
80
93
  tool = FunctionTool.from_defaults(fn, name, description, return_direct, fn_schema, async_fn, tool_metadata)
81
- vectara_tool = cls(
82
- tool_type=tool_type,
83
- fn=tool._fn,
84
- metadata=tool._metadata,
85
- async_fn=tool._async_fn
86
- )
94
+ vectara_tool = cls(tool_type=tool_type, fn=tool.fn, metadata=tool.metadata, async_fn=tool.async_fn)
87
95
  return vectara_tool
88
96
 
89
97
  def __eq__(self, other):
90
- if self.tool_type != other.tool_type:
98
+ if self.metadata.tool_type != other.metadata.tool_type:
91
99
  return False
92
100
 
93
101
  # Check if fn_schema is an instance of a BaseModel or a class itself (metaclass)
@@ -98,15 +106,15 @@ class VectaraTool(FunctionTool):
98
106
  if key not in other_schema_dict:
99
107
  is_equal = False
100
108
  break
101
- if (self_schema_dict[key].annotation != other_schema_dict[key].annotation or
102
- self_schema_dict[key].description != other_schema_dict[key].description or
103
- self_schema_dict[key].is_required() != other_schema_dict[key].is_required()
109
+ if (
110
+ self_schema_dict[key].annotation != other_schema_dict[key].annotation
111
+ or self_schema_dict[key].description != other_schema_dict[key].description
112
+ or self_schema_dict[key].is_required() != other_schema_dict[key].is_required()
104
113
  ):
105
114
  is_equal = False
106
115
  break
107
116
  return is_equal
108
117
 
109
-
110
118
  class VectaraToolFactory:
111
119
  """
112
120
  A factory class for creating Vectara RAG tools.
@@ -145,7 +153,7 @@ class VectaraToolFactory:
145
153
  rerank_k: int = 50,
146
154
  mmr_diversity_bias: float = 0.2,
147
155
  include_citations: bool = True,
148
- fcs_threshold: float = 0.0
156
+ fcs_threshold: float = 0.0,
149
157
  ) -> VectaraTool:
150
158
  """
151
159
  Creates a RAG (Retrieve and Generate) tool.
@@ -175,7 +183,7 @@ class VectaraToolFactory:
175
183
  vectara_api_key=self.vectara_api_key,
176
184
  vectara_customer_id=self.vectara_customer_id,
177
185
  vectara_corpus_id=self.vectara_corpus_id,
178
- x_source_str="vectara-agentic"
186
+ x_source_str="vectara-agentic",
179
187
  )
180
188
 
181
189
  def _build_filter_string(kwargs):
@@ -225,7 +233,7 @@ class VectaraToolFactory:
225
233
  tool_name=rag_function.__name__,
226
234
  content=msg,
227
235
  raw_input={"args": args, "kwargs": kwargs},
228
- raw_output={'response': msg}
236
+ raw_output={"response": msg},
229
237
  )
230
238
  if len(response.source_nodes) == 0:
231
239
  msg = "Tool failed to generate a response since no matches were found."
@@ -233,18 +241,28 @@ class VectaraToolFactory:
233
241
  tool_name=rag_function.__name__,
234
242
  content=msg,
235
243
  raw_input={"args": args, "kwargs": kwargs},
236
- raw_output={'response': msg}
244
+ raw_output={"response": msg},
237
245
  )
238
246
 
239
247
  # Extract citation metadata
240
248
  pattern = r"\[(\d+)\]"
241
249
  matches = re.findall(pattern, response.response)
242
- citation_numbers = sorted(set([int(match) for match in matches]))
250
+ citation_numbers = sorted(set(int(match) for match in matches))
243
251
  citation_metadata = ""
244
252
  keys_to_ignore = ["lang", "offset", "len"]
245
253
  for citation_number in citation_numbers:
246
254
  metadata = response.source_nodes[citation_number - 1].metadata
247
- citation_metadata += f"""[{citation_number}]: {"; ".join([f"{k}='{v}'" for k,v in metadata.items() if k not in keys_to_ignore])}.\n"""
255
+ citation_metadata += (
256
+ f"[{citation_number}]: "
257
+ + "; ".join(
258
+ [
259
+ f"{k}='{v}'"
260
+ for k, v in metadata.items()
261
+ if k not in keys_to_ignore
262
+ ]
263
+ )
264
+ + ".\n"
265
+ )
248
266
  fcs = response.metadata["fcs"] if "fcs" in response.metadata else 0.0
249
267
  if fcs < fcs_threshold:
250
268
  msg = f"Could not answer the query due to suspected hallucination (fcs={fcs})."
@@ -252,7 +270,7 @@ class VectaraToolFactory:
252
270
  tool_name=rag_function.__name__,
253
271
  content=msg,
254
272
  raw_input={"args": args, "kwargs": kwargs},
255
- raw_output={'response': msg}
273
+ raw_output={"response": msg},
256
274
  )
257
275
  res = {
258
276
  "response": response.response,
@@ -288,16 +306,16 @@ class VectaraToolFactory:
288
306
  # Create a new signature using the extracted parameters
289
307
  sig = inspect.Signature(params)
290
308
  rag_function.__signature__ = sig
291
- rag_function.__annotations__['return'] = dict[str, Any]
309
+ rag_function.__annotations__["return"] = dict[str, Any]
292
310
  rag_function.__name__ = "_" + re.sub(r"[^A-Za-z0-9_]", "_", tool_name)
293
311
 
294
312
  # Create the tool
295
313
  tool = VectaraTool.from_defaults(
296
- tool_type=ToolType.QUERY,
297
314
  fn=rag_function,
298
315
  name=tool_name,
299
316
  description=tool_description,
300
317
  fn_schema=tool_args_schema,
318
+ tool_type=ToolType.QUERY,
301
319
  )
302
320
  return tool
303
321
 
@@ -307,9 +325,7 @@ class ToolsFactory:
307
325
  A factory class for creating agent tools.
308
326
  """
309
327
 
310
- def create_tool(
311
- self, function: Callable, tool_type: ToolType = ToolType.QUERY
312
- ) -> VectaraTool:
328
+ def create_tool(self, function: Callable, tool_type: ToolType = ToolType.QUERY) -> VectaraTool:
313
329
  """
314
330
  Create a tool from a function.
315
331
 
@@ -320,7 +336,7 @@ class ToolsFactory:
320
336
  Returns:
321
337
  VectaraTool: A VectaraTool object.
322
338
  """
323
- return VectaraTool.from_defaults(tool_type, function)
339
+ return VectaraTool.from_defaults(tool_type=tool_type, fn=function)
324
340
 
325
341
  def get_llama_index_tools(
326
342
  self,
@@ -342,10 +358,8 @@ class ToolsFactory:
342
358
  List[VectaraTool]: A list of VectaraTool objects.
343
359
  """
344
360
  # Dynamically install and import the module
345
- if tool_package_name not in LI_packages.keys():
346
- raise ValueError(
347
- f"Tool package {tool_package_name} from LlamaIndex not supported by Vectara-agentic."
348
- )
361
+ if tool_package_name not in LI_packages:
362
+ raise ValueError(f"Tool package {tool_package_name} from LlamaIndex not supported by Vectara-agentic.")
349
363
 
350
364
  module_name = f"llama_index.tools.{tool_package_name}"
351
365
  module = importlib.import_module(module_name)
@@ -358,21 +372,14 @@ class ToolsFactory:
358
372
  vtools = []
359
373
  for tool in tools:
360
374
  if len(tool_name_prefix) > 0:
361
- tool._metadata.name = tool_name_prefix + "_" + tool._metadata.name
375
+ tool.metadata.name = tool_name_prefix + "_" + tool.metadata.name
362
376
  if isinstance(func_type, dict):
363
377
  if tool_spec_name not in func_type.keys():
364
- raise ValueError(
365
- f"Tool spec {tool_spec_name} not found in package {tool_package_name}."
366
- )
378
+ raise ValueError(f"Tool spec {tool_spec_name} not found in package {tool_package_name}.")
367
379
  tool_type = func_type[tool_spec_name]
368
380
  else:
369
381
  tool_type = func_type
370
- vtool = VectaraTool(
371
- tool_type=tool_type,
372
- fn=tool.fn,
373
- metadata=tool.metadata,
374
- async_fn=tool.async_fn
375
- )
382
+ vtool = VectaraTool(tool_type=tool_type, fn=tool.fn, metadata=tool.metadata, async_fn=tool.async_fn)
376
383
  vtools.append(vtool)
377
384
  return vtools
378
385
 
@@ -386,25 +393,19 @@ class ToolsFactory:
386
393
  """
387
394
  Create a list of guardrail tools to avoid controversial topics.
388
395
  """
389
- return [
390
- self.create_tool(
391
- avoid_topics_tool(["politics", "religion", "voilence", "hate speech", "adult content", "illegal activities"])
392
- )
393
- ]
396
+ return [self.create_tool(get_bad_topics)]
394
397
 
395
398
  def financial_tools(self):
396
399
  """
397
400
  Create a list of financial tools.
398
401
  """
399
- return self.get_llama_index_tools(
400
- tool_package_name="yahoo_finance",
401
- tool_spec_name="YahooFinanceToolSpec"
402
- )
402
+ return self.get_llama_index_tools(tool_package_name="yahoo_finance", tool_spec_name="YahooFinanceToolSpec")
403
403
 
404
404
  def legal_tools(self) -> List[FunctionTool]:
405
405
  """
406
406
  Create a list of legal tools.
407
407
  """
408
+
408
409
  def summarize_legal_text(
409
410
  text: str = Field(description="the original text."),
410
411
  ) -> str:
@@ -428,9 +429,7 @@ class ToolsFactory:
428
429
  """,
429
430
  )
430
431
 
431
- return [
432
- self.create_tool(tool) for tool in [summarize_legal_text, critique_as_judge]
433
- ]
432
+ return [self.create_tool(tool) for tool in [summarize_legal_text, critique_as_judge]]
434
433
 
435
434
  def database_tools(
436
435
  self,
@@ -484,18 +483,21 @@ class ToolsFactory:
484
483
  dbname=dbname,
485
484
  )
486
485
  else:
487
- raise Exception("Please provide a SqlDatabase option or a valid DB scheme type (postgresql, mysql, sqlite, mssql, oracle).")
486
+ raise ValueError(
487
+ "Please provide a SqlDatabase option or a valid DB scheme type "
488
+ " (postgresql, mysql, sqlite, mssql, oracle)."
489
+ )
488
490
 
489
491
  # Update tools with description
490
492
  for tool in tools:
491
493
  if content_description:
492
- tool._metadata.description = (
493
- tool._metadata.description
494
- + f"The database tables include data about {content_description}."
494
+ tool.metadata.description = (
495
+ tool.metadata.description + f"The database tables include data about {content_description}."
495
496
  )
496
497
 
497
- load_data_tool = [t for t in tools if t._metadata.name.endswith("load_data")][0]
498
- sample_data_fn = db_load_sample_data(load_data_tool)
498
+ # Update load_data_tool to return only text instead of "Document" objects (to save on space)
499
+ load_data_tool_index = next(i for i, t in enumerate(tools) if t.metadata.name.endswith("load_data"))
500
+ sample_data_fn = DBLoadSampleData(tools[load_data_tool_index])
499
501
  sample_data_fn.__name__ = f"{tool_name_prefix}_load_sample_data"
500
502
  sample_data_tool = self.create_tool(sample_data_fn, ToolType.QUERY)
501
503
  tools.append(sample_data_tool)
@@ -2,10 +2,10 @@
2
2
  This module contains the tools catalog for the Vectara Agentic.
3
3
  """
4
4
 
5
- from typing import Optional, Callable, Any, List
5
+ from typing import Callable, Any, List
6
+ from functools import lru_cache
6
7
  from pydantic import Field
7
8
  import requests
8
- from functools import lru_cache
9
9
 
10
10
  from .types import LLMRole
11
11
  from .utils import get_llm
@@ -24,7 +24,7 @@ get_headers = {
24
24
  #
25
25
  # Standard Tools
26
26
  #
27
- @lru_cache(maxsize=5)
27
+ @lru_cache(maxsize=None)
28
28
  def summarize_text(
29
29
  text: str = Field(description="the original text."),
30
30
  expertise: str = Field(
@@ -55,12 +55,11 @@ def summarize_text(
55
55
  response = llm.complete(prompt)
56
56
  return response.text
57
57
 
58
- @lru_cache(maxsize=5)
58
+
59
+ @lru_cache(maxsize=None)
59
60
  def rephrase_text(
60
61
  text: str = Field(description="the original text."),
61
- instructions: str = Field(
62
- description="the specific instructions for how to rephrase the text."
63
- ),
62
+ instructions: str = Field(description="the specific instructions for how to rephrase the text."),
64
63
  ) -> str:
65
64
  """
66
65
  This is a helper tool.
@@ -84,15 +83,12 @@ def rephrase_text(
84
83
  response = llm.complete(prompt)
85
84
  return response.text
86
85
 
87
- @lru_cache(maxsize=5)
86
+
87
+ @lru_cache(maxsize=None)
88
88
  def critique_text(
89
89
  text: str = Field(description="the original text."),
90
- role: Optional[str] = Field(
91
- None, description="the role of the person providing critique."
92
- ),
93
- point_of_view: Optional[str] = Field(
94
- None, description="the point of view with which to provide critique."
95
- ),
90
+ role: str = Field(default=None, description="the role of the person providing critique."),
91
+ point_of_view: str = Field(default=None, description="the point of view with which to provide critique."),
96
92
  ) -> str:
97
93
  """
98
94
  This is a helper tool.
@@ -109,9 +105,7 @@ def critique_text(
109
105
  if role:
110
106
  prompt = f"As a {role}, critique the provided text from the point of view of {point_of_view}."
111
107
  else:
112
- prompt = (
113
- f"Critique the provided text from the point of view of {point_of_view}."
114
- )
108
+ prompt = f"Critique the provided text from the point of view of {point_of_view}."
115
109
  prompt += "Structure the critique as bullet points.\n"
116
110
  prompt += f"Original text: {text}\nCritique:"
117
111
  llm = get_llm(LLMRole.TOOL)
@@ -122,37 +116,34 @@ def critique_text(
122
116
  #
123
117
  # Guardrails tools
124
118
  #
125
- def avoid_topics_tool(
126
- text: str = Field(description="the original text."),
127
- topics_to_avoid: List[str] = Field(default=["politics", "religion", "violence", "hate speech", "adult content", "illegal activities"],
128
- description="List of topics to avoid.")
129
- ) -> str:
119
+ def get_bad_topics() -> List[str]:
130
120
  """
131
- A tool to help avoid certain topics in the response.
132
- Given the input text, rephrases the text to ensure that the response avoids of the topics listed in 'topics_to_avoid'.
133
-
134
- Args:
135
- text (str): The original text.
136
- topics_to_avoid (List[str]): A list of topics to avoid.
137
-
138
- Returns:
139
- str: The rephrased text.
121
+ Get the list of topics to avoid in the response.
140
122
  """
141
- return rephrase_text(text, f"Avoid the following topics: {', '.join(topics_to_avoid)}")
123
+ return [
124
+ "politics",
125
+ "religion",
126
+ "violence",
127
+ "hate speech",
128
+ "adult content",
129
+ "illegal activities",
130
+ ]
131
+
142
132
 
143
133
  #
144
134
  # Additional database tool
145
135
  #
146
- class db_load_sample_data:
136
+ class DBLoadSampleData:
147
137
  """
148
138
  A tool to load a sample of data from the specified database table.
149
139
 
150
- This tool fetches the first num_rows (default 25) rows from the given table using a provided database query function.
140
+ This tool fetches the first num_rows (default 25) rows from the given table
141
+ using a provided database query function.
151
142
  """
152
143
 
153
144
  def __init__(self, load_data_tool: Callable):
154
145
  """
155
- Initializes the db_load_sample_data with the provided load_data_tool function.
146
+ Initializes the DBLoadSampleData object with the provided load_data_tool function.
156
147
 
157
148
  Args:
158
149
  load_data_tool (Callable): A function to execute the SQL query.
vectara_agentic/types.py CHANGED
@@ -11,7 +11,6 @@ class AgentType(Enum):
11
11
  OPENAI = "OPENAI"
12
12
  LLMCOMPILER = "LLMCOMPILER"
13
13
 
14
-
15
14
  class ObserverType(Enum):
16
15
  """Enumeration for different types of observability integrations."""
17
16
 
vectara_agentic/utils.py CHANGED
@@ -3,14 +3,15 @@ Utilities for the Vectara agentic.
3
3
  """
4
4
 
5
5
  import os
6
+ from typing import Tuple, Callable, Optional
7
+ from functools import lru_cache
8
+
9
+ import tiktoken
6
10
 
7
11
  from llama_index.core.llms import LLM
8
12
  from llama_index.llms.openai import OpenAI
9
13
  from llama_index.llms.anthropic import Anthropic
10
14
 
11
- import tiktoken
12
- from typing import Tuple, Callable, Optional
13
-
14
15
  from .types import LLMRole, AgentType, ModelProvider
15
16
 
16
17
  provider_to_default_model_name = {
@@ -25,12 +26,12 @@ provider_to_default_model_name = {
25
26
 
26
27
  DEFAULT_MODEL_PROVIDER = ModelProvider.OPENAI
27
28
 
28
-
29
+ @lru_cache(maxsize=None)
29
30
  def _get_llm_params_for_role(role: LLMRole) -> Tuple[ModelProvider, str]:
30
31
  """Get the model provider and model name for the specified role."""
31
32
  if role == LLMRole.TOOL:
32
33
  model_provider = ModelProvider(
33
- os.getenv("VECTARA_AGENTIC_TOOL_LLM_PROVIDER", DEFAULT_MODEL_PROVIDER)
34
+ os.getenv("VECTARA_AGENTIC_TOOL_LLM_PROVIDER", str(DEFAULT_MODEL_PROVIDER))
34
35
  )
35
36
  model_name = os.getenv(
36
37
  "VECTARA_AGENTIC_TOOL_MODEL_NAME",
@@ -38,14 +39,16 @@ def _get_llm_params_for_role(role: LLMRole) -> Tuple[ModelProvider, str]:
38
39
  )
39
40
  else:
40
41
  model_provider = ModelProvider(
41
- os.getenv("VECTARA_AGENTIC_MAIN_LLM_PROVIDER", DEFAULT_MODEL_PROVIDER)
42
+ os.getenv("VECTARA_AGENTIC_MAIN_LLM_PROVIDER", str(DEFAULT_MODEL_PROVIDER))
42
43
  )
43
44
  model_name = os.getenv(
44
45
  "VECTARA_AGENTIC_MAIN_MODEL_NAME",
45
46
  provider_to_default_model_name.get(model_provider),
46
47
  )
47
48
 
48
- agent_type = AgentType(os.getenv("VECTARA_AGENTIC_AGENT_TYPE", AgentType.OPENAI))
49
+ agent_type = AgentType(
50
+ os.getenv("VECTARA_AGENTIC_AGENT_TYPE", str(AgentType.OPENAI))
51
+ )
49
52
  if (
50
53
  role == LLMRole.MAIN
51
54
  and agent_type == AgentType.OPENAI
@@ -57,18 +60,18 @@ def _get_llm_params_for_role(role: LLMRole) -> Tuple[ModelProvider, str]:
57
60
 
58
61
  return model_provider, model_name
59
62
 
60
-
63
+ @lru_cache(maxsize=None)
61
64
  def get_tokenizer_for_model(role: LLMRole) -> Optional[Callable]:
62
65
  """Get the tokenizer for the specified model."""
63
66
  model_provider, model_name = _get_llm_params_for_role(role)
64
67
  if model_provider == ModelProvider.OPENAI:
65
68
  return tiktoken.encoding_for_model(model_name).encode
66
- elif model_provider == ModelProvider.ANTHROPIC:
69
+ if model_provider == ModelProvider.ANTHROPIC:
67
70
  return Anthropic().tokenizer
68
- else:
69
- return None
71
+ return None
70
72
 
71
73
 
74
+ @lru_cache(maxsize=None)
72
75
  def get_llm(role: LLMRole) -> LLM:
73
76
  """Get the LLM for the specified role."""
74
77
  model_provider, model_name = _get_llm_params_for_role(role)
@@ -76,7 +79,7 @@ def get_llm(role: LLMRole) -> LLM:
76
79
  if model_provider == ModelProvider.OPENAI:
77
80
  llm = OpenAI(model=model_name, temperature=0, is_function_calling_model=True)
78
81
  elif model_provider == ModelProvider.ANTHROPIC:
79
- llm = Anthropic(model=model_name, temperature=0, is_function_calling_model=True)
82
+ llm = Anthropic(model=model_name, temperature=0)
80
83
  elif model_provider == ModelProvider.GEMINI:
81
84
  from llama_index.llms.gemini import Gemini
82
85
  llm = Gemini(model=model_name, temperature=0, is_function_calling_model=True)
@@ -88,10 +91,10 @@ def get_llm(role: LLMRole) -> LLM:
88
91
  llm = Groq(model=model_name, temperature=0, is_function_calling_model=True)
89
92
  elif model_provider == ModelProvider.FIREWORKS:
90
93
  from llama_index.llms.fireworks import Fireworks
91
- llm = Fireworks(model=model_name, temperature=0, is_function_calling_model=True)
94
+ llm = Fireworks(model=model_name, temperature=0)
92
95
  elif model_provider == ModelProvider.COHERE:
93
96
  from llama_index.llms.cohere import Cohere
94
- llm = Cohere(model=model_name, temperature=0, is_function_calling_model=True)
97
+ llm = Cohere(model=model_name, temperature=0)
95
98
  else:
96
99
  raise ValueError(f"Unknown LLM provider: {model_provider}")
97
100