vectara-agentic 0.2.14__tar.gz → 0.2.15__tar.gz

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.

Files changed (41) hide show
  1. {vectara_agentic-0.2.14/vectara_agentic.egg-info → vectara_agentic-0.2.15}/PKG-INFO +1 -1
  2. vectara_agentic-0.2.15/tests/test_return_direct.py +49 -0
  3. {vectara_agentic-0.2.14 → vectara_agentic-0.2.15}/tests/test_tools.py +1 -1
  4. {vectara_agentic-0.2.14 → vectara_agentic-0.2.15}/tests/test_vectara_llms.py +4 -1
  5. {vectara_agentic-0.2.14 → vectara_agentic-0.2.15}/vectara_agentic/_observability.py +43 -21
  6. {vectara_agentic-0.2.14 → vectara_agentic-0.2.15}/vectara_agentic/_prompts.py +1 -1
  7. {vectara_agentic-0.2.14 → vectara_agentic-0.2.15}/vectara_agentic/_version.py +1 -1
  8. {vectara_agentic-0.2.14 → vectara_agentic-0.2.15}/vectara_agentic/agent.py +18 -9
  9. {vectara_agentic-0.2.14 → vectara_agentic-0.2.15}/vectara_agentic/tool_utils.py +117 -94
  10. {vectara_agentic-0.2.14 → vectara_agentic-0.2.15}/vectara_agentic/tools.py +18 -8
  11. {vectara_agentic-0.2.14 → vectara_agentic-0.2.15}/vectara_agentic/tools_catalog.py +1 -1
  12. {vectara_agentic-0.2.14 → vectara_agentic-0.2.15/vectara_agentic.egg-info}/PKG-INFO +1 -1
  13. {vectara_agentic-0.2.14 → vectara_agentic-0.2.15}/vectara_agentic.egg-info/SOURCES.txt +1 -0
  14. {vectara_agentic-0.2.14 → vectara_agentic-0.2.15}/LICENSE +0 -0
  15. {vectara_agentic-0.2.14 → vectara_agentic-0.2.15}/MANIFEST.in +0 -0
  16. {vectara_agentic-0.2.14 → vectara_agentic-0.2.15}/README.md +0 -0
  17. {vectara_agentic-0.2.14 → vectara_agentic-0.2.15}/requirements.txt +0 -0
  18. {vectara_agentic-0.2.14 → vectara_agentic-0.2.15}/setup.cfg +0 -0
  19. {vectara_agentic-0.2.14 → vectara_agentic-0.2.15}/setup.py +0 -0
  20. {vectara_agentic-0.2.14 → vectara_agentic-0.2.15}/tests/__init__.py +0 -0
  21. {vectara_agentic-0.2.14 → vectara_agentic-0.2.15}/tests/endpoint.py +0 -0
  22. {vectara_agentic-0.2.14 → vectara_agentic-0.2.15}/tests/test_agent.py +0 -0
  23. {vectara_agentic-0.2.14 → vectara_agentic-0.2.15}/tests/test_agent_planning.py +0 -0
  24. {vectara_agentic-0.2.14 → vectara_agentic-0.2.15}/tests/test_agent_type.py +0 -0
  25. {vectara_agentic-0.2.14 → vectara_agentic-0.2.15}/tests/test_fallback.py +0 -0
  26. {vectara_agentic-0.2.14 → vectara_agentic-0.2.15}/tests/test_groq.py +0 -0
  27. {vectara_agentic-0.2.14 → vectara_agentic-0.2.15}/tests/test_private_llm.py +0 -0
  28. {vectara_agentic-0.2.14 → vectara_agentic-0.2.15}/tests/test_serialization.py +0 -0
  29. {vectara_agentic-0.2.14 → vectara_agentic-0.2.15}/tests/test_workflow.py +0 -0
  30. {vectara_agentic-0.2.14 → vectara_agentic-0.2.15}/vectara_agentic/__init__.py +0 -0
  31. {vectara_agentic-0.2.14 → vectara_agentic-0.2.15}/vectara_agentic/_callback.py +0 -0
  32. {vectara_agentic-0.2.14 → vectara_agentic-0.2.15}/vectara_agentic/agent_config.py +0 -0
  33. {vectara_agentic-0.2.14 → vectara_agentic-0.2.15}/vectara_agentic/agent_endpoint.py +0 -0
  34. {vectara_agentic-0.2.14 → vectara_agentic-0.2.15}/vectara_agentic/db_tools.py +0 -0
  35. {vectara_agentic-0.2.14 → vectara_agentic-0.2.15}/vectara_agentic/llm_utils.py +0 -0
  36. {vectara_agentic-0.2.14 → vectara_agentic-0.2.15}/vectara_agentic/sub_query_workflow.py +0 -0
  37. {vectara_agentic-0.2.14 → vectara_agentic-0.2.15}/vectara_agentic/types.py +0 -0
  38. {vectara_agentic-0.2.14 → vectara_agentic-0.2.15}/vectara_agentic/utils.py +0 -0
  39. {vectara_agentic-0.2.14 → vectara_agentic-0.2.15}/vectara_agentic.egg-info/dependency_links.txt +0 -0
  40. {vectara_agentic-0.2.14 → vectara_agentic-0.2.15}/vectara_agentic.egg-info/requires.txt +0 -0
  41. {vectara_agentic-0.2.14 → vectara_agentic-0.2.15}/vectara_agentic.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: vectara_agentic
3
- Version: 0.2.14
3
+ Version: 0.2.15
4
4
  Summary: A Python package for creating AI Assistants and AI Agents with Vectara
5
5
  Home-page: https://github.com/vectara/py-vectara-agentic
6
6
  Author: Ofer Mendelevitch
@@ -0,0 +1,49 @@
1
+ import unittest
2
+
3
+ from vectara_agentic.agent import Agent
4
+ from vectara_agentic.tools import VectaraToolFactory
5
+
6
+ vectara_corpus_key = "vectara-docs_1"
7
+ vectara_api_key = "zqt_UXrBcnI2UXINZkrv4g1tQPhzj02vfdtqYJIDiA"
8
+
9
+
10
+ class TestAgentPackage(unittest.TestCase):
11
+
12
+ def test_return_direct1(self):
13
+ vec_factory = VectaraToolFactory(vectara_corpus_key, vectara_api_key)
14
+
15
+ query_tool = vec_factory.create_rag_tool(
16
+ tool_name="rag_tool",
17
+ tool_description="""
18
+ A dummy tool for testing return_direct.
19
+ """,
20
+ return_direct=True,
21
+ )
22
+
23
+ agent = Agent(
24
+ tools=[query_tool],
25
+ topic="Sample topic",
26
+ custom_instructions="You are a helpful assistant.",
27
+ )
28
+ res = agent.chat("What is Vectara?")
29
+ self.assertIn("Response:", str(res))
30
+ self.assertIn("fcs_score", str(res))
31
+ self.assertIn("References:", str(res))
32
+
33
+ def test_from_corpus(self):
34
+ agent = Agent.from_corpus(
35
+ tool_name="rag_tool",
36
+ vectara_corpus_key=vectara_corpus_key,
37
+ vectara_api_key=vectara_api_key,
38
+ data_description="stuff about Vectara",
39
+ assistant_specialty="question answering",
40
+ return_direct=True,
41
+ )
42
+ res = agent.chat("What is Vectara?")
43
+ self.assertIn("Response:", str(res))
44
+ self.assertIn("fcs_score", str(res))
45
+ self.assertIn("References:", str(res))
46
+
47
+
48
+ if __name__ == "__main__":
49
+ unittest.main()
@@ -196,7 +196,7 @@ class TestToolsPackage(unittest.TestCase):
196
196
  custom_instructions="Call the tool with 15 arguments for OPENAI",
197
197
  agent_config=config,
198
198
  )
199
- res = agent.chat("What is the stock price?")
199
+ res = agent.chat("What is the stock price for Yahoo on 12/31/22?")
200
200
  self.assertIn("maximum length of 1024 characters", str(res))
201
201
 
202
202
  # Same test but with GROQ
@@ -15,7 +15,10 @@ vectara_api_key = "zqt_UXrBcnI2UXINZkrv4g1tQPhzj02vfdtqYJIDiA"
15
15
  class TestLLMPackage(unittest.TestCase):
16
16
 
17
17
  def test_vectara_openai(self):
18
- vec_factory = VectaraToolFactory(vectara_corpus_key, vectara_api_key)
18
+ vec_factory = VectaraToolFactory(
19
+ vectara_corpus_key=vectara_corpus_key,
20
+ vectara_api_key=vectara_api_key
21
+ )
19
22
 
20
23
  self.assertEqual(vectara_corpus_key, vec_factory.vectara_corpus_key)
21
24
  self.assertEqual(vectara_api_key, vec_factory.vectara_api_key)
@@ -12,28 +12,50 @@ def setup_observer(config: AgentConfig, verbose: bool) -> bool:
12
12
  '''
13
13
  Setup the observer.
14
14
  '''
15
- import phoenix as px
16
- from openinference.instrumentation.llama_index import LlamaIndexInstrumentor
17
- from phoenix.otel import register
18
- if config.observer == ObserverType.ARIZE_PHOENIX:
19
- phoenix_endpoint = os.getenv("PHOENIX_ENDPOINT", None)
20
- if not phoenix_endpoint:
21
- px.launch_app()
22
- tracer_provider = register(endpoint='http://localhost:6006/v1/traces', project_name="vectara-agentic")
23
- elif 'app.phoenix.arize.com' in phoenix_endpoint: # hosted on Arize
24
- phoenix_api_key = os.getenv("PHOENIX_API_KEY", None)
25
- if not phoenix_api_key:
26
- raise ValueError("Arize Phoenix API key not set. Please set PHOENIX_API_KEY environment variable.")
27
- os.environ["PHOENIX_CLIENT_HEADERS"] = f"api_key={phoenix_api_key}"
28
- os.environ["PHOENIX_COLLECTOR_ENDPOINT"] = "https://app.phoenix.arize.com"
29
- tracer_provider = register(endpoint=phoenix_endpoint, project_name="vectara-agentic")
30
- else: # Self hosted Phoenix
31
- tracer_provider = register(endpoint=phoenix_endpoint, project_name="vectara-agentic")
32
- LlamaIndexInstrumentor().instrument(tracer_provider=tracer_provider)
33
- return True
15
+ if config.observer != ObserverType.ARIZE_PHOENIX:
16
+ if verbose:
17
+ print("No Phoenix observer set.")
18
+ return False
19
+
20
+ try:
21
+ import phoenix as px
22
+ from openinference.instrumentation.llama_index import LlamaIndexInstrumentor
23
+ from phoenix.otel import register
24
+ except ImportError:
25
+ print(
26
+ (
27
+ "Phoenix libraries not found. Please install with"
28
+ "'pip install arize-phoenix openinference-instrumentation-llama-index'"
29
+ )
30
+ )
31
+ return False
32
+
33
+ phoenix_endpoint = os.getenv("PHOENIX_ENDPOINT", None)
34
+ if not phoenix_endpoint:
35
+ print("Phoenix endpoint not set. Attempting to launch local Phoenix UI...")
36
+ px.launch_app()
37
+ print("Local Phoenix UI launched. You can view traces at the UI address (usually http://localhost:6006).")
38
+
39
+ if phoenix_endpoint and 'app.phoenix.arize.com' in phoenix_endpoint:
40
+ phoenix_api_key = os.getenv("PHOENIX_API_KEY")
41
+ if not phoenix_api_key:
42
+ raise ValueError(
43
+ "Arize Phoenix API key not set. Please set PHOENIX_API_KEY."
44
+ )
45
+ os.environ["PHOENIX_CLIENT_HEADERS"] = f"api_key={phoenix_api_key}"
46
+ os.environ["PHOENIX_COLLECTOR_ENDPOINT"] = "https://app.phoenix.arize.com"
47
+
48
+ reg_kwargs = {
49
+ "endpoint": phoenix_endpoint or 'http://localhost:6006/v1/traces',
50
+ "project_name": "vectara-agentic",
51
+ "batch": True,
52
+ "set_global_tracer_provider": False,
53
+ }
54
+ tracer_provider = register(**reg_kwargs)
55
+ LlamaIndexInstrumentor().instrument(tracer_provider=tracer_provider)
34
56
  if verbose:
35
- print("No observer set.")
36
- return False
57
+ print(f"Phoenix observer configured for project 'vectara-agentic' at endpoint: {reg_kwargs['endpoint']}")
58
+ return True
37
59
 
38
60
 
39
61
  def _extract_fcs_value(output: Union[str, dict]) -> Optional[float]:
@@ -5,7 +5,7 @@ This file contains the prompt templates for the different types of agents.
5
5
  # General (shared) instructions
6
6
  GENERAL_INSTRUCTIONS = """
7
7
  - Use tools as your main source of information, do not respond without using a tool. Do not respond based on pre-trained knowledge.
8
- - Use the 'get_bad_topics' tool to determine the topics you are not allowed to discuss or respond to.
8
+ - Use the 'get_bad_topics' (if it exists) tool to determine the topics you are not allowed to discuss or respond to.
9
9
  - Before responding to a user query that requires knowledge of the current date, call the 'get_current_date' tool to get the current date.
10
10
  Never rely on previous knowledge of the current date.
11
11
  Example queries that require the current date: "What is the revenue of Apple last october?" or "What was the stock price 5 days ago?".
@@ -1,4 +1,4 @@
1
1
  """
2
2
  Define the version of the package.
3
3
  """
4
- __version__ = "0.2.14"
4
+ __version__ = "0.2.15"
@@ -248,19 +248,25 @@ class Agent:
248
248
 
249
249
  if validate_tools:
250
250
  prompt = f"""
251
- Given the following instructions, and a list of tool names,
252
- Please identify tools mentioned in the instructions that do not exist in the list.
253
- Instructions:
251
+ You are provided these tools:
252
+ <tools>{','.join(tool_names)}</tools>
253
+ And these instructions:
254
+ <instructions>
254
255
  {self._custom_instructions}
255
- Tool names: {', '.join(tool_names)}
256
- Your response should include a comma separated list of tool names that do not exist in the list.
257
- Your response should be an empty string if all tools mentioned in the instructions are in the list.
256
+ </instructions>
257
+ Your task is to identify invalid tools.
258
+ A tool is invalid if it is mentioned in the instructions but not in the tools list.
259
+ A tool's name must have at least two characters.
260
+ Your response should be a comma-separated list of the invalid tools.
261
+ If not invalid tools exist, respond with "<OKAY>".
258
262
  """
259
263
  llm = get_llm(LLMRole.MAIN, config=self.agent_config)
260
- bad_tools = llm.complete(prompt).text.split(", ")
261
- if bad_tools:
264
+ bad_tools_str = llm.complete(prompt).text
265
+ if bad_tools_str and bad_tools_str != "<OKAY>":
266
+ bad_tools = [tool.strip() for tool in bad_tools_str.split(",")]
267
+ numbered = ", ".join(f"({i}) {tool}" for i, tool in enumerate(bad_tools, 1))
262
268
  raise ValueError(
263
- f"The Agent custom instructions mention these invalid tools: {', '.join(bad_tools)}"
269
+ f"The Agent custom instructions mention these invalid tools: {numbered}"
264
270
  )
265
271
 
266
272
  # Create token counters for the main and tool LLMs
@@ -689,6 +695,7 @@ class Agent:
689
695
  vectara_frequency_penalty: Optional[float] = None,
690
696
  vectara_presence_penalty: Optional[float] = None,
691
697
  vectara_save_history: bool = True,
698
+ return_direct: bool = False,
692
699
  ) -> "Agent":
693
700
  """
694
701
  Create an agent from a single Vectara corpus
@@ -738,6 +745,7 @@ class Agent:
738
745
  vectara_presence_penalty (float, optional): How much to penalize repeating tokens in the response,
739
746
  higher values increasing the diversity of topics.
740
747
  vectara_save_history (bool, optional): Whether to save the query in history.
748
+ return_direct (bool, optional): Whether the agent should return the tool's response directly.
741
749
 
742
750
  Returns:
743
751
  Agent: An instance of the Agent class.
@@ -791,6 +799,7 @@ class Agent:
791
799
  save_history=vectara_save_history,
792
800
  include_citations=True,
793
801
  verbose=verbose,
802
+ return_direct=return_direct,
794
803
  )
795
804
 
796
805
  assistant_instructions = f"""
@@ -231,44 +231,119 @@ class VectaraTool(FunctionTool):
231
231
  return err_output
232
232
 
233
233
 
234
- def _create_tool_from_dynamic_function(
234
+ class EmptyBaseModel(BaseModel):
235
+ """empty base model"""
236
+
237
+ def _unwrap_default(default):
238
+ # PydanticUndefined means “no default — required”
239
+ return default if default is not PydanticUndefined else inspect.Parameter.empty
240
+
241
+ def _schema_default(default):
242
+ # PydanticUndefined ⇒ Ellipsis (required)
243
+ return default if default is not PydanticUndefined else ...
244
+
245
+ def _make_docstring(
246
+ function: Callable[..., ToolOutput],
247
+ tool_name: str,
248
+ tool_description: str,
249
+ fn_schema: Type[BaseModel],
250
+ all_params: List[inspect.Parameter],
251
+ compact_docstring: bool,
252
+ ) -> str:
253
+ params_str = ", ".join(
254
+ f"{p.name}: {p.annotation.__name__ if hasattr(p.annotation, '__name__') else p.annotation}"
255
+ for p in all_params
256
+ )
257
+ signature_line = f"{tool_name}({params_str}) -> dict[str, Any]"
258
+ if compact_docstring:
259
+ doc_lines = [tool_description.strip()]
260
+ else:
261
+ doc_lines = [signature_line, "", tool_description.strip()]
262
+ doc_lines += [
263
+ "",
264
+ "Args:",
265
+ ]
266
+
267
+ full_schema = fn_schema.model_json_schema()
268
+ props = full_schema.get("properties", {})
269
+ for prop_name, schema_prop in props.items():
270
+ desc = schema_prop.get("description", "")
271
+
272
+ # pick up any examples you declared on the Field or via schema_extra
273
+ examples = schema_prop.get("examples", [])
274
+ default = schema_prop.get("default", PydanticUndefined)
275
+
276
+ # format the type, default, description, examples
277
+ # find the matching inspect.Parameter so you get its annotation
278
+ param = next((p for p in all_params if p.name == prop_name), None)
279
+ if param and hasattr(param.annotation, "__name__"):
280
+ ty = param.annotation.__name__
281
+ else:
282
+ ty = schema_prop.get("type", "")
283
+
284
+ # inline default if present
285
+ default_txt = f", default={default!r}" if default is not PydanticUndefined else ""
286
+
287
+ # inline examples if any
288
+ if examples:
289
+ examples_txt = ", ".join(repr(e) for e in examples)
290
+ desc = f"{desc} (e.g., {examples_txt})"
291
+
292
+ doc_lines.append(f" - {prop_name} ({ty}{default_txt}): {desc}")
293
+
294
+ doc_lines.append("")
295
+ doc_lines.append("Returns:")
296
+ return_desc = getattr(
297
+ function, "__return_description__", "A dictionary containing the result data."
298
+ )
299
+ doc_lines.append(f" dict[str, Any]: {return_desc}")
300
+
301
+ initial_docstring = "\n".join(doc_lines)
302
+ collapsed_spaces = re.sub(r' {2,}', ' ', initial_docstring)
303
+ final_docstring = re.sub(r'\n{2,}', '\n', collapsed_spaces).strip()
304
+ return final_docstring
305
+
306
+
307
+ def create_tool_from_dynamic_function(
235
308
  function: Callable[..., ToolOutput],
236
309
  tool_name: str,
237
310
  tool_description: str,
238
311
  base_params_model: Type[BaseModel],
239
312
  tool_args_schema: Type[BaseModel],
240
313
  compact_docstring: bool = False,
314
+ return_direct: bool = False,
241
315
  ) -> VectaraTool:
242
- base_params = []
243
-
316
+ """
317
+ Create a VectaraTool from a dynamic function.
318
+ Args:
319
+ function (Callable[..., ToolOutput]): The function to wrap as a tool.
320
+ tool_name (str): The name of the tool.
321
+ tool_description (str): The description of the tool.
322
+ base_params_model (Type[BaseModel]): The Pydantic model for the base parameters.
323
+ tool_args_schema (Type[BaseModel]): The Pydantic model for the tool arguments.
324
+ compact_docstring (bool): Whether to use a compact docstring format.
325
+ Returns:
326
+ VectaraTool: The created VectaraTool.
327
+ """
244
328
  if tool_args_schema is None:
245
-
246
- class EmptyBaseModel(BaseModel):
247
- """empty base model"""
248
-
249
329
  tool_args_schema = EmptyBaseModel
250
330
 
331
+ if not isinstance(tool_args_schema, type) or not issubclass(tool_args_schema, BaseModel):
332
+ raise TypeError("tool_args_schema must be a Pydantic BaseModel subclass")
333
+
251
334
  fields = {}
252
- for param_name, model_field in base_params_model.model_fields.items():
253
- field_type = base_params_model.__annotations__.get(
254
- param_name, str
255
- )
256
- default_value = (
257
- model_field.default
258
- if model_field.default is not None
259
- else inspect.Parameter.empty
260
- )
335
+ base_params = []
336
+ for field_name, field_info in base_params_model.model_fields.items():
337
+ field_type = field_info.annotation
338
+ default_value = _unwrap_default(field_info.default)
261
339
  param = inspect.Parameter(
262
- param_name,
340
+ field_name,
263
341
  inspect.Parameter.POSITIONAL_OR_KEYWORD,
264
342
  default=default_value,
265
343
  annotation=field_type,
266
344
  )
267
345
  base_params.append(param)
268
- fields[param_name] = (
269
- field_type,
270
- model_field.default if model_field.default is not None else ...,
271
- )
346
+ fields[field_name] = (field_type, _schema_default(field_info.default))
272
347
 
273
348
  # Add tool_args_schema fields to the fields dict if not already included.
274
349
  # Also add them to the function signature by creating new inspect.Parameter objects.
@@ -276,20 +351,16 @@ def _create_tool_from_dynamic_function(
276
351
  if field_name in fields:
277
352
  continue
278
353
 
279
- default_value = field_info.default if field_info.default is not None else ...
280
- field_type = tool_args_schema.__annotations__.get(field_name, None)
281
- fields[field_name] = (field_type, default_value)
354
+ field_type = field_info.annotation
355
+ default_value = _unwrap_default(field_info.default)
282
356
  param = inspect.Parameter(
283
357
  field_name,
284
358
  inspect.Parameter.POSITIONAL_OR_KEYWORD,
285
- default=(
286
- default_value
287
- if default_value is not ...
288
- else inspect.Parameter.empty
289
- ),
359
+ default=default_value,
290
360
  annotation=field_type,
291
361
  )
292
362
  base_params.append(param)
363
+ fields[field_name] = (field_type, _schema_default(field_info.default))
293
364
 
294
365
  # Create the dynamic schema with both base_params_model and tool_args_schema fields.
295
366
  fn_schema = create_model(f"{tool_name}_schema", **fields)
@@ -304,87 +375,39 @@ def _create_tool_from_dynamic_function(
304
375
  function.__annotations__["return"] = dict[str, Any]
305
376
  function.__name__ = re.sub(r"[^A-Za-z0-9_]", "_", tool_name)
306
377
 
307
- # Build a docstring using parameter descriptions from the BaseModels.
308
- params_str = ", ".join(
309
- f"{p.name}: {p.annotation.__name__ if hasattr(p.annotation, '__name__') else p.annotation}"
310
- for p in all_params
378
+ function.__doc__ = _make_docstring(
379
+ function,
380
+ tool_name, tool_description, fn_schema,
381
+ all_params, compact_docstring
311
382
  )
312
- signature_line = f"{tool_name}({params_str}) -> dict[str, Any]"
313
- if compact_docstring:
314
- doc_lines = [
315
- tool_description.strip(),
316
- ]
317
- else:
318
- doc_lines = [
319
- signature_line,
320
- "",
321
- tool_description.strip(),
322
- ]
323
- doc_lines += [
324
- "",
325
- "Args:",
326
- ]
327
- for param in all_params:
328
- description = ""
329
- if param.name in base_params_model.model_fields:
330
- description = base_params_model.model_fields[param.name].description
331
- elif param.name in tool_args_schema.model_fields:
332
- description = tool_args_schema.model_fields[param.name].description
333
- if not description:
334
- description = ""
335
- type_name = (
336
- param.annotation.__name__
337
- if hasattr(param.annotation, "__name__")
338
- else str(param.annotation)
339
- )
340
- if (
341
- param.default is not inspect.Parameter.empty
342
- and param.default is not PydanticUndefined
343
- ):
344
- default_text = f", default={param.default!r}"
345
- else:
346
- default_text = ""
347
- doc_lines.append(f" - {param.name} ({type_name}){default_text}: {description}")
348
- doc_lines.append("")
349
- doc_lines.append("Returns:")
350
- return_desc = getattr(
351
- function, "__return_description__", "A dictionary containing the result data."
352
- )
353
- doc_lines.append(f" dict[str, Any]: {return_desc}")
354
-
355
- initial_docstring = "\n".join(doc_lines)
356
- collapsed_spaces = re.sub(r' {2,}', ' ', initial_docstring)
357
- final_docstring = re.sub(r'\n{2,}', '\n', collapsed_spaces).strip()
358
- function.__doc__ = final_docstring
359
-
360
383
  tool = VectaraTool.from_defaults(
361
384
  fn=function,
362
385
  name=tool_name,
363
386
  description=function.__doc__,
364
387
  fn_schema=fn_schema,
365
388
  tool_type=ToolType.QUERY,
389
+ return_direct=return_direct,
366
390
  )
367
391
  return tool
368
392
 
369
393
 
370
- Range = Tuple[float, float, bool, bool] # (min, max, min_inclusive, max_inclusive)
394
+ _PARSE_RANGE_REGEX = re.compile(
395
+ r"""
396
+ ^([\[\(])\s* # opening bracket
397
+ ([+-]?\d+(\.\d*)?)\s*, # first number
398
+ \s*([+-]?\d+(\.\d*)?) # second number
399
+ \s*([\]\)])$ # closing bracket
400
+ """,
401
+ re.VERBOSE,
402
+ )
371
403
 
372
404
 
373
- def _parse_range(val_str: str) -> Range:
405
+ def _parse_range(val_str: str) -> Tuple[float, float, bool, bool]:
374
406
  """
375
407
  Parses '[1,10)' or '(0.5, 5]' etc.
376
408
  Returns (start, end, start_incl, end_incl) or raises ValueError.
377
409
  """
378
- m = re.match(
379
- r"""
380
- ^([\[\(])\s* # opening bracket
381
- ([+-]?\d+(\.\d*)?)\s*, # first number
382
- \s*([+-]?\d+(\.\d*)?) # second number
383
- \s*([\]\)])$ # closing bracket
384
- """,
385
- val_str,
386
- re.VERBOSE,
387
- )
410
+ m = _PARSE_RANGE_REGEX.match(val_str)
388
411
  if not m:
389
412
  raise ValueError(f"Invalid range syntax: {val_str!r}")
390
413
  start_inc = m.group(1) == "["
@@ -426,7 +449,7 @@ def _parse_comparison(val_str: str) -> Tuple[str, Union[float, str, bool]]:
426
449
  raise ValueError(f"No valid comparison operator at start of {val_str!r}")
427
450
 
428
451
 
429
- def _build_filter_string(
452
+ def build_filter_string(
430
453
  kwargs: Dict[str, Any], tool_args_type: Dict[str, dict], fixed_filter: str
431
454
  ) -> str:
432
455
  """
@@ -22,9 +22,9 @@ from .db_tools import DatabaseTools
22
22
  from .utils import summarize_documents, is_float
23
23
  from .agent_config import AgentConfig
24
24
  from .tool_utils import (
25
- _create_tool_from_dynamic_function,
26
- _build_filter_string,
27
- VectaraTool
25
+ create_tool_from_dynamic_function,
26
+ build_filter_string,
27
+ VectaraTool,
28
28
  )
29
29
 
30
30
  LI_packages = {
@@ -62,6 +62,7 @@ LI_packages = {
62
62
  },
63
63
  }
64
64
 
65
+
65
66
  class VectaraToolFactory:
66
67
  """
67
68
  A factory class for creating Vectara RAG tools.
@@ -108,6 +109,7 @@ class VectaraToolFactory:
108
109
  mmr_diversity_bias: float = 0.2,
109
110
  udf_expression: str = None,
110
111
  rerank_chain: List[Dict] = None,
112
+ return_direct: bool = False,
111
113
  save_history: bool = True,
112
114
  verbose: bool = False,
113
115
  vectara_base_url: str = "https://api.vectara.io",
@@ -142,6 +144,7 @@ class VectaraToolFactory:
142
144
  "diversity_bias" for mmr, and "user_function" for udf).
143
145
  If using slingshot/multilingual_reranker_v1, it must be first in the list.
144
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.
145
148
  verbose (bool, optional): Whether to print verbose output.
146
149
  vectara_base_url (str, optional): The base URL for the Vectara API.
147
150
  vectara_verify_ssl (bool, optional): Whether to verify SSL certificates for the Vectara API.
@@ -177,7 +180,7 @@ class VectaraToolFactory:
177
180
  else summarize_docs
178
181
  )
179
182
  try:
180
- filter_string = _build_filter_string(
183
+ filter_string = build_filter_string(
181
184
  kwargs, tool_args_type, fixed_filter
182
185
  )
183
186
  except ValueError as e:
@@ -289,7 +292,7 @@ class VectaraToolFactory:
289
292
  + "Use this tool to search for relevant documents, not to ask questions."
290
293
  )
291
294
 
292
- tool = _create_tool_from_dynamic_function(
295
+ tool = create_tool_from_dynamic_function(
293
296
  search_function,
294
297
  tool_name,
295
298
  search_tool_extra_desc,
@@ -300,6 +303,7 @@ class VectaraToolFactory:
300
303
  ),
301
304
  tool_args_schema,
302
305
  compact_docstring=self.compact_docstring,
306
+ return_direct=return_direct,
303
307
  )
304
308
  return tool
305
309
 
@@ -336,6 +340,7 @@ class VectaraToolFactory:
336
340
  include_citations: bool = True,
337
341
  save_history: bool = False,
338
342
  fcs_threshold: float = 0.0,
343
+ return_direct: bool = False,
339
344
  verbose: bool = False,
340
345
  vectara_base_url: str = "https://api.vectara.io",
341
346
  vectara_verify_ssl: bool = True,
@@ -388,6 +393,7 @@ class VectaraToolFactory:
388
393
  save_history (bool, optional): Whether to save the query in history.
389
394
  fcs_threshold (float, optional): A threshold for factual consistency.
390
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.
391
397
  verbose (bool, optional): Whether to print verbose output.
392
398
  vectara_base_url (str, optional): The base URL for the Vectara API.
393
399
  vectara_verify_ssl (bool, optional): Whether to verify SSL certificates for the Vectara API.
@@ -417,7 +423,7 @@ class VectaraToolFactory:
417
423
 
418
424
  query = kwargs.pop("query")
419
425
  try:
420
- filter_string = _build_filter_string(
426
+ filter_string = build_filter_string(
421
427
  kwargs, tool_args_type, fixed_filter
422
428
  )
423
429
  except ValueError as e:
@@ -468,7 +474,10 @@ class VectaraToolFactory:
468
474
  response = vectara_query_engine.query(query)
469
475
 
470
476
  if len(response.source_nodes) == 0:
471
- msg = "Tool failed to generate a response since no matches were found."
477
+ msg = (
478
+ "Tool failed to generate a response since no matches were found. "
479
+ "Please check the arguments and try again."
480
+ )
472
481
  return ToolOutput(
473
482
  tool_name=rag_function.__name__,
474
483
  content=msg,
@@ -545,13 +554,14 @@ class VectaraToolFactory:
545
554
  description="The search query to perform, in the form of a question",
546
555
  )
547
556
 
548
- tool = _create_tool_from_dynamic_function(
557
+ tool = create_tool_from_dynamic_function(
549
558
  rag_function,
550
559
  tool_name,
551
560
  tool_description,
552
561
  RagToolBaseParams,
553
562
  tool_args_schema,
554
563
  compact_docstring=self.compact_docstring,
564
+ return_direct=return_direct,
555
565
  )
556
566
  return tool
557
567
 
@@ -25,7 +25,7 @@ get_headers = {
25
25
 
26
26
  def get_current_date() -> str:
27
27
  """
28
- Returns: the current date (when called) as a string.
28
+ Returns the current date as a string.
29
29
  """
30
30
  return date.today().strftime("%A, %B %d, %Y")
31
31
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: vectara_agentic
3
- Version: 0.2.14
3
+ Version: 0.2.15
4
4
  Summary: A Python package for creating AI Assistants and AI Agents with Vectara
5
5
  Home-page: https://github.com/vectara/py-vectara-agentic
6
6
  Author: Ofer Mendelevitch
@@ -11,6 +11,7 @@ tests/test_agent_type.py
11
11
  tests/test_fallback.py
12
12
  tests/test_groq.py
13
13
  tests/test_private_llm.py
14
+ tests/test_return_direct.py
14
15
  tests/test_serialization.py
15
16
  tests/test_tools.py
16
17
  tests/test_vectara_llms.py