vectara-agentic 0.2.21__tar.gz → 0.2.23__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 (45) hide show
  1. {vectara_agentic-0.2.21/vectara_agentic.egg-info → vectara_agentic-0.2.23}/PKG-INFO +9 -1
  2. {vectara_agentic-0.2.21 → vectara_agentic-0.2.23}/README.md +8 -0
  3. {vectara_agentic-0.2.21 → vectara_agentic-0.2.23}/tests/test_agent_type.py +5 -2
  4. vectara_agentic-0.2.23/tests/test_groq.py +42 -0
  5. {vectara_agentic-0.2.21 → vectara_agentic-0.2.23}/tests/test_workflow.py +8 -35
  6. {vectara_agentic-0.2.21 → vectara_agentic-0.2.23}/vectara_agentic/_prompts.py +1 -0
  7. {vectara_agentic-0.2.21 → vectara_agentic-0.2.23}/vectara_agentic/_version.py +1 -1
  8. {vectara_agentic-0.2.21 → vectara_agentic-0.2.23}/vectara_agentic/agent.py +33 -10
  9. {vectara_agentic-0.2.21 → vectara_agentic-0.2.23}/vectara_agentic/llm_utils.py +52 -16
  10. {vectara_agentic-0.2.21 → vectara_agentic-0.2.23}/vectara_agentic/sub_query_workflow.py +31 -9
  11. {vectara_agentic-0.2.21 → vectara_agentic-0.2.23}/vectara_agentic/tools.py +2 -0
  12. {vectara_agentic-0.2.21 → vectara_agentic-0.2.23/vectara_agentic.egg-info}/PKG-INFO +9 -1
  13. vectara_agentic-0.2.21/tests/test_groq.py +0 -115
  14. {vectara_agentic-0.2.21 → vectara_agentic-0.2.23}/LICENSE +0 -0
  15. {vectara_agentic-0.2.21 → vectara_agentic-0.2.23}/MANIFEST.in +0 -0
  16. {vectara_agentic-0.2.21 → vectara_agentic-0.2.23}/requirements.txt +0 -0
  17. {vectara_agentic-0.2.21 → vectara_agentic-0.2.23}/setup.cfg +0 -0
  18. {vectara_agentic-0.2.21 → vectara_agentic-0.2.23}/setup.py +0 -0
  19. {vectara_agentic-0.2.21 → vectara_agentic-0.2.23}/tests/__init__.py +0 -0
  20. {vectara_agentic-0.2.21 → vectara_agentic-0.2.23}/tests/endpoint.py +0 -0
  21. {vectara_agentic-0.2.21 → vectara_agentic-0.2.23}/tests/test_agent.py +0 -0
  22. {vectara_agentic-0.2.21 → vectara_agentic-0.2.23}/tests/test_agent_planning.py +0 -0
  23. {vectara_agentic-0.2.21 → vectara_agentic-0.2.23}/tests/test_api_endpoint.py +0 -0
  24. {vectara_agentic-0.2.21 → vectara_agentic-0.2.23}/tests/test_bedrock.py +0 -0
  25. {vectara_agentic-0.2.21 → vectara_agentic-0.2.23}/tests/test_fallback.py +0 -0
  26. {vectara_agentic-0.2.21 → vectara_agentic-0.2.23}/tests/test_gemini.py +0 -0
  27. {vectara_agentic-0.2.21 → vectara_agentic-0.2.23}/tests/test_private_llm.py +0 -0
  28. {vectara_agentic-0.2.21 → vectara_agentic-0.2.23}/tests/test_return_direct.py +0 -0
  29. {vectara_agentic-0.2.21 → vectara_agentic-0.2.23}/tests/test_serialization.py +0 -0
  30. {vectara_agentic-0.2.21 → vectara_agentic-0.2.23}/tests/test_tools.py +0 -0
  31. {vectara_agentic-0.2.21 → vectara_agentic-0.2.23}/tests/test_vectara_llms.py +0 -0
  32. {vectara_agentic-0.2.21 → vectara_agentic-0.2.23}/vectara_agentic/__init__.py +0 -0
  33. {vectara_agentic-0.2.21 → vectara_agentic-0.2.23}/vectara_agentic/_callback.py +0 -0
  34. {vectara_agentic-0.2.21 → vectara_agentic-0.2.23}/vectara_agentic/_observability.py +0 -0
  35. {vectara_agentic-0.2.21 → vectara_agentic-0.2.23}/vectara_agentic/agent_config.py +0 -0
  36. {vectara_agentic-0.2.21 → vectara_agentic-0.2.23}/vectara_agentic/agent_endpoint.py +0 -0
  37. {vectara_agentic-0.2.21 → vectara_agentic-0.2.23}/vectara_agentic/db_tools.py +0 -0
  38. {vectara_agentic-0.2.21 → vectara_agentic-0.2.23}/vectara_agentic/tool_utils.py +0 -0
  39. {vectara_agentic-0.2.21 → vectara_agentic-0.2.23}/vectara_agentic/tools_catalog.py +0 -0
  40. {vectara_agentic-0.2.21 → vectara_agentic-0.2.23}/vectara_agentic/types.py +0 -0
  41. {vectara_agentic-0.2.21 → vectara_agentic-0.2.23}/vectara_agentic/utils.py +0 -0
  42. {vectara_agentic-0.2.21 → vectara_agentic-0.2.23}/vectara_agentic.egg-info/SOURCES.txt +0 -0
  43. {vectara_agentic-0.2.21 → vectara_agentic-0.2.23}/vectara_agentic.egg-info/dependency_links.txt +0 -0
  44. {vectara_agentic-0.2.21 → vectara_agentic-0.2.23}/vectara_agentic.egg-info/requires.txt +0 -0
  45. {vectara_agentic-0.2.21 → vectara_agentic-0.2.23}/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.21
3
+ Version: 0.2.23
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
@@ -530,6 +530,9 @@ class MyWorkflow(Workflow):
530
530
  class OutputsModel(BaseModel):
531
531
  answer: str
532
532
 
533
+ class OutputModelOnFail(BaseModel):
534
+ partial_response: str = ""
535
+
533
536
  @step
534
537
  async def my_step(self, ev: StartEvent) -> StopEvent:
535
538
  # do something here
@@ -587,6 +590,11 @@ workflow_result = asyncio.run(agent.run(inputs))
587
590
  print(workflow_result.answer)
588
591
  ```
589
592
 
593
+ When a workflow reaches its timeout, the timeout handler builds and returns an `OutputModelOnFail`
594
+ by reading each field named in that model from the workflow’s Context; for any field that isn’t set in the context,
595
+ it uses the default value you’ve defined on `OutputModelOnFail`. In other words, every property in `OutputModelOnFail`
596
+ must declare a default so that even if the corresponding context variable is missing, the model can be fully populated and returned without errors.
597
+
590
598
  ### Built-in Workflows
591
599
 
592
600
  `vectara-agentic` includes two workflow implementations that you can use right away:
@@ -454,6 +454,9 @@ class MyWorkflow(Workflow):
454
454
  class OutputsModel(BaseModel):
455
455
  answer: str
456
456
 
457
+ class OutputModelOnFail(BaseModel):
458
+ partial_response: str = ""
459
+
457
460
  @step
458
461
  async def my_step(self, ev: StartEvent) -> StopEvent:
459
462
  # do something here
@@ -511,6 +514,11 @@ workflow_result = asyncio.run(agent.run(inputs))
511
514
  print(workflow_result.answer)
512
515
  ```
513
516
 
517
+ When a workflow reaches its timeout, the timeout handler builds and returns an `OutputModelOnFail`
518
+ by reading each field named in that model from the workflow’s Context; for any field that isn’t set in the context,
519
+ it uses the default value you’ve defined on `OutputModelOnFail`. In other words, every property in `OutputModelOnFail`
520
+ must declare a default so that even if the corresponding context variable is missing, the model can be fully populated and returned without errors.
521
+
514
522
  ### Built-in Workflows
515
523
 
516
524
  `vectara-agentic` includes two workflow implementations that you can use right away:
@@ -19,10 +19,13 @@ react_config_anthropic = AgentConfig(
19
19
  tool_llm_provider=ModelProvider.ANTHROPIC,
20
20
  )
21
21
 
22
+ # React with Google does not work with Gemini 2.5-flash
22
23
  react_config_gemini = AgentConfig(
23
24
  agent_type=AgentType.REACT,
24
25
  main_llm_provider=ModelProvider.GEMINI,
26
+ main_llm_model_name="models/gemini-2.0-flash",
25
27
  tool_llm_provider=ModelProvider.GEMINI,
28
+ tool_llm_model_name="models/gemini-2.0-flash",
26
29
  )
27
30
 
28
31
  react_config_together = AgentConfig(
@@ -97,7 +100,7 @@ class TestAgentType(unittest.TestCase):
97
100
  )
98
101
  agent.chat("What is 5 times 10. Only give the answer, nothing else")
99
102
  agent.chat("what is 3 times 7. Only give the answer, nothing else")
100
- res = agent.chat("multiply the results of the last two multiplications. Only give the answer, nothing else.")
103
+ res = agent.chat("what is the result of multiplying the results of the last two multiplications. Only give the answer, nothing else.")
101
104
  self.assertIn("1050", res.response)
102
105
 
103
106
  agent = Agent(
@@ -108,7 +111,7 @@ class TestAgentType(unittest.TestCase):
108
111
  )
109
112
  agent.chat("What is 5 times 10. Only give the answer, nothing else")
110
113
  agent.chat("what is 3 times 7. Only give the answer, nothing else")
111
- res = agent.chat("multiply the results of the last two multiplications. Only give the answer, nothing else.")
114
+ res = agent.chat("what is the result of multiplying the results of the last two multiplications. Only give the answer, nothing else.")
112
115
  self.assertIn("1050", res.response)
113
116
 
114
117
  def test_together(self):
@@ -0,0 +1,42 @@
1
+ import unittest
2
+
3
+ from vectara_agentic.agent import Agent, AgentType
4
+ from vectara_agentic.agent_config import AgentConfig
5
+ from vectara_agentic.tools import ToolsFactory
6
+ from vectara_agentic.types import ModelProvider
7
+
8
+ import nest_asyncio
9
+ nest_asyncio.apply()
10
+
11
+ def mult(x: float, y: float) -> float:
12
+ "Multiply two numbers"
13
+ return x * y
14
+
15
+
16
+ fc_config_groq = AgentConfig(
17
+ agent_type=AgentType.FUNCTION_CALLING,
18
+ main_llm_provider=ModelProvider.GROQ,
19
+ tool_llm_provider=ModelProvider.GROQ,
20
+ )
21
+
22
+
23
+ class TestGROQ(unittest.TestCase):
24
+
25
+ def test_multiturn(self):
26
+ tools = [ToolsFactory().create_tool(mult)]
27
+ topic = "AI topic"
28
+ instructions = "Always do as your father tells you, if your mother agrees!"
29
+ agent = Agent(
30
+ tools=tools,
31
+ topic=topic,
32
+ custom_instructions=instructions,
33
+ )
34
+
35
+ agent.chat("What is 5 times 10. Only give the answer, nothing else")
36
+ agent.chat("what is 3 times 7. Only give the answer, nothing else")
37
+ res = agent.chat("multiply the results of the last two questions. Output only the answer.")
38
+ self.assertEqual(res.response, "1050")
39
+
40
+
41
+ if __name__ == "__main__":
42
+ unittest.main()
@@ -1,9 +1,5 @@
1
1
  import unittest
2
2
 
3
- from pydantic import BaseModel
4
-
5
- from llama_index.core.workflow import WorkflowTimeoutError
6
-
7
3
  from vectara_agentic.agent import Agent
8
4
  from vectara_agentic.agent_config import AgentConfig
9
5
  from vectara_agentic.tools import ToolsFactory
@@ -68,7 +64,7 @@ class TestWorkflowPackage(unittest.IsolatedAsyncioTestCase):
68
64
 
69
65
  class TestWorkflowFailure(unittest.IsolatedAsyncioTestCase):
70
66
 
71
- async def test_workflow_failure(self):
67
+ async def test_workflow_failure_sub_question(self):
72
68
  tools = [ToolsFactory().create_tool(mult)] + [ToolsFactory().create_tool(add)]
73
69
  topic = "AI topic"
74
70
  instructions = "You are a helpful AI assistant."
@@ -84,50 +80,27 @@ class TestWorkflowFailure(unittest.IsolatedAsyncioTestCase):
84
80
  inputs = SubQuestionQueryWorkflow.InputsModel(
85
81
  query="Compute 5 times 3, then add 7 to the result."
86
82
  )
83
+ res = await agent.run(inputs=inputs)
84
+ self.assertIsInstance(res, SubQuestionQueryWorkflow.OutputModelOnFail)
87
85
 
88
- res = None
89
-
90
- try:
91
- res = await agent.run(inputs=inputs)
92
- except Exception as e:
93
- self.assertIsInstance(e, WorkflowTimeoutError)
94
-
95
- self.assertIsNone(res)
96
-
97
- async def test_workflow_with_fail_class(self):
86
+ async def test_workflow_failure_seq_sub_question(self):
98
87
  tools = [ToolsFactory().create_tool(mult)] + [ToolsFactory().create_tool(add)]
99
88
  topic = "AI topic"
100
89
  instructions = "You are a helpful AI assistant."
101
-
102
- class SubQuestionQueryWorkflowWithFailClass(SubQuestionQueryWorkflow):
103
- class OutputModelOnFail(BaseModel):
104
- """
105
- In case of failure, returns the user's original query
106
- """
107
- original_query: str
108
-
109
90
  agent = Agent(
110
91
  tools=tools,
111
92
  topic=topic,
112
93
  custom_instructions=instructions,
113
94
  agent_config = AgentConfig(),
114
- workflow_cls = SubQuestionQueryWorkflowWithFailClass,
95
+ workflow_cls = SequentialSubQuestionsWorkflow,
115
96
  workflow_timeout = 1
116
97
  )
117
98
 
118
- inputs = SubQuestionQueryWorkflow.InputsModel(
99
+ inputs = SequentialSubQuestionsWorkflow.InputsModel(
119
100
  query="Compute 5 times 3, then add 7 to the result."
120
101
  )
121
-
122
- res = None
123
-
124
- try:
125
- res = await agent.run(inputs=inputs)
126
- except Exception as e:
127
- assert isinstance(e, WorkflowTimeoutError)
128
-
129
- self.assertIsInstance(res, SubQuestionQueryWorkflowWithFailClass.OutputModelOnFail)
130
- self.assertEqual(res.original_query, "Compute 5 times 3, then add 7 to the result.")
102
+ res = await agent.run(inputs=inputs)
103
+ self.assertIsInstance(res, SequentialSubQuestionsWorkflow.OutputModelOnFail)
131
104
 
132
105
 
133
106
  if __name__ == "__main__":
@@ -44,6 +44,7 @@ GENERAL_INSTRUCTIONS = """
44
44
  - If you are provided with database tools use them for analytical queries (such as counting, calculating max, min, average, sum, or other statistics).
45
45
  For each database, the database tools include: x_list_tables, x_load_data, x_describe_tables, x_load_unique_values, and x_load_sample_data, where 'x' in the database name.
46
46
  for example, if the database name is "ev", the tools are: ev_list_tables, ev_load_data, ev_describe_tables, ev_load_unique_values, and ev_load_sample_data.
47
+ Use ANSI SQL-92 syntax for the SQL queries, and do not use any other SQL dialect.
47
48
  Before using the x_load_data with a SQL query, always follow these discovery steps:
48
49
  - call the x_list_tables tool to list of available tables in the x database.
49
50
  - Call the x_describe_tables tool to understand the schema of each table you want to query data from.
@@ -1,4 +1,4 @@
1
1
  """
2
2
  Define the version of the package.
3
3
  """
4
- __version__ = "0.2.21"
4
+ __version__ = "0.2.23"
@@ -15,6 +15,8 @@ from collections import Counter
15
15
  import inspect
16
16
  from inspect import Signature, Parameter, ismethod
17
17
  from pydantic import Field, create_model, ValidationError, BaseModel
18
+ from pydantic_core import PydanticUndefined
19
+
18
20
  import cloudpickle as pickle
19
21
 
20
22
  from dotenv import load_dotenv
@@ -1083,6 +1085,15 @@ class Agent:
1083
1085
  if not isinstance(inputs, self.workflow_cls.InputsModel):
1084
1086
  raise ValueError(f"Inputs must be an instance of {workflow.InputsModel}.")
1085
1087
 
1088
+ outputs_model_on_fail_cls = getattr(workflow.__class__, "OutputModelOnFail", None)
1089
+ if outputs_model_on_fail_cls:
1090
+ fields_without_default = []
1091
+ for name, field_info in outputs_model_on_fail_cls.model_fields.items():
1092
+ if field_info.default_factory is PydanticUndefined:
1093
+ fields_without_default.append(name)
1094
+ if fields_without_default:
1095
+ raise ValueError(f"Fields without default values: {fields_without_default}")
1096
+
1086
1097
  workflow_context = Context(workflow=workflow)
1087
1098
  try:
1088
1099
  # run workflow
@@ -1102,15 +1113,14 @@ class Agent:
1102
1113
  raise ValueError(f"Failed to map workflow output to model: {e}") from e
1103
1114
 
1104
1115
  except Exception as e:
1105
- outputs_model_on_fail_cls = getattr(workflow.__class__, "OutputModelOnFail", None)
1116
+ _missing = object()
1106
1117
  if outputs_model_on_fail_cls:
1107
1118
  model_fields = outputs_model_on_fail_cls.model_fields
1108
- input_dict = {
1109
- key: await workflow_context.get(key, None)
1110
- for key in model_fields
1111
- }
1112
-
1113
- # return output in the form of workflow.OutputModelOnFail(BaseModel)
1119
+ input_dict = {}
1120
+ for key in model_fields:
1121
+ value = await workflow_context.get(key, default=_missing)
1122
+ if value is not _missing:
1123
+ input_dict[key] = value
1114
1124
  output = outputs_model_on_fail_cls.model_validate(input_dict)
1115
1125
  else:
1116
1126
  print(f"Vectara Agentic: Workflow failed with unexpected error: {e}")
@@ -1126,9 +1136,14 @@ class Agent:
1126
1136
  return json.dumps(self.to_dict())
1127
1137
 
1128
1138
  @classmethod
1129
- def loads(cls, data: str) -> "Agent":
1139
+ def loads(
1140
+ cls,
1141
+ data: str,
1142
+ agent_progress_callback: Optional[Callable[[AgentStatusType, str], None]] = None,
1143
+ query_logging_callback: Optional[Callable[[str, str], None]] = None
1144
+ ) -> "Agent":
1130
1145
  """Create an Agent instance from a JSON string."""
1131
- return cls.from_dict(json.loads(data))
1146
+ return cls.from_dict(json.loads(data), agent_progress_callback, query_logging_callback)
1132
1147
 
1133
1148
  def to_dict(self) -> Dict[str, Any]:
1134
1149
  """Serialize the Agent instance to a dictionary."""
@@ -1185,7 +1200,12 @@ class Agent:
1185
1200
  }
1186
1201
 
1187
1202
  @classmethod
1188
- def from_dict(cls, data: Dict[str, Any]) -> "Agent":
1203
+ def from_dict(
1204
+ cls,
1205
+ data: Dict[str, Any],
1206
+ agent_progress_callback: Optional[Callable] = None,
1207
+ query_logging_callback: Optional[Callable] = None
1208
+ ) -> "Agent":
1189
1209
  """Create an Agent instance from a dictionary."""
1190
1210
  agent_config = AgentConfig.from_dict(data["agent_config"])
1191
1211
  fallback_agent_config = (
@@ -1286,6 +1306,8 @@ class Agent:
1286
1306
  verbose=data["verbose"],
1287
1307
  fallback_agent_config=fallback_agent_config,
1288
1308
  workflow_cls=data["workflow_cls"],
1309
+ agent_progress_callback=agent_progress_callback,
1310
+ query_logging_callback=query_logging_callback,
1289
1311
  )
1290
1312
  memory = (
1291
1313
  pickle.loads(data["memory"].encode("latin-1"))
@@ -1294,4 +1316,5 @@ class Agent:
1294
1316
  )
1295
1317
  if memory:
1296
1318
  agent.agent.memory = memory
1319
+ agent.memory = memory
1297
1320
  return agent
@@ -11,18 +11,54 @@ from llama_index.core.llms import LLM
11
11
  from llama_index.llms.openai import OpenAI
12
12
  from llama_index.llms.anthropic import Anthropic
13
13
 
14
+ # Optional provider imports with graceful fallback
15
+ try:
16
+ from llama_index.llms.google_genai import GoogleGenAI
17
+ except ImportError:
18
+ GoogleGenAI = None
19
+
20
+ try:
21
+ from llama_index.llms.together import TogetherLLM
22
+ except ImportError:
23
+ TogetherLLM = None
24
+
25
+ try:
26
+ from llama_index.llms.groq import Groq
27
+ except ImportError:
28
+ Groq = None
29
+
30
+ try:
31
+ from llama_index.llms.fireworks import Fireworks
32
+ except ImportError:
33
+ Fireworks = None
34
+
35
+ try:
36
+ from llama_index.llms.bedrock_converse import BedrockConverse
37
+ except ImportError:
38
+ BedrockConverse = None
39
+
40
+ try:
41
+ from llama_index.llms.cohere import Cohere
42
+ except ImportError:
43
+ Cohere = None
44
+
45
+ try:
46
+ from llama_index.llms.openai_like import OpenAILike
47
+ except ImportError:
48
+ OpenAILike = None
49
+
14
50
  from .types import LLMRole, AgentType, ModelProvider
15
51
  from .agent_config import AgentConfig
16
52
 
17
53
  provider_to_default_model_name = {
18
54
  ModelProvider.OPENAI: "gpt-4.1",
19
55
  ModelProvider.ANTHROPIC: "claude-sonnet-4-20250514",
20
- ModelProvider.TOGETHER: "Qwen/Qwen2.5-72B-Instruct-Turbo",
56
+ ModelProvider.TOGETHER: "meta-llama/Llama-4-Scout-17B-16E-Instruct",
21
57
  ModelProvider.GROQ: "deepseek-r1-distill-llama-70b",
22
58
  ModelProvider.FIREWORKS: "accounts/fireworks/models/firefunction-v2",
23
59
  ModelProvider.BEDROCK: "us.anthropic.claude-sonnet-4-20250514-v1:0",
24
60
  ModelProvider.COHERE: "command-a-03-2025",
25
- ModelProvider.GEMINI: "models/gemini-2.0-flash",
61
+ ModelProvider.GEMINI: "models/gemini-2.5-flash",
26
62
  }
27
63
 
28
64
  DEFAULT_MODEL_PROVIDER = ModelProvider.OPENAI
@@ -106,8 +142,8 @@ def get_llm(role: LLMRole, config: Optional[AgentConfig] = None) -> LLM:
106
142
  max_tokens=max_tokens,
107
143
  )
108
144
  elif model_provider == ModelProvider.GEMINI:
109
- from llama_index.llms.google_genai import GoogleGenAI
110
-
145
+ if GoogleGenAI is None:
146
+ raise ImportError("google_genai not available. Install with: pip install llama-index-llms-google-genai")
111
147
  llm = GoogleGenAI(
112
148
  model=model_name,
113
149
  temperature=0,
@@ -116,8 +152,8 @@ def get_llm(role: LLMRole, config: Optional[AgentConfig] = None) -> LLM:
116
152
  max_tokens=max_tokens,
117
153
  )
118
154
  elif model_provider == ModelProvider.TOGETHER:
119
- from llama_index.llms.together import TogetherLLM
120
-
155
+ if TogetherLLM is None:
156
+ raise ImportError("together not available. Install with: pip install llama-index-llms-together")
121
157
  llm = TogetherLLM(
122
158
  model=model_name,
123
159
  temperature=0,
@@ -125,8 +161,8 @@ def get_llm(role: LLMRole, config: Optional[AgentConfig] = None) -> LLM:
125
161
  max_tokens=max_tokens,
126
162
  )
127
163
  elif model_provider == ModelProvider.GROQ:
128
- from llama_index.llms.groq import Groq
129
-
164
+ if Groq is None:
165
+ raise ImportError("groq not available. Install with: pip install llama-index-llms-groq")
130
166
  llm = Groq(
131
167
  model=model_name,
132
168
  temperature=0,
@@ -134,12 +170,12 @@ def get_llm(role: LLMRole, config: Optional[AgentConfig] = None) -> LLM:
134
170
  max_tokens=max_tokens,
135
171
  )
136
172
  elif model_provider == ModelProvider.FIREWORKS:
137
- from llama_index.llms.fireworks import Fireworks
138
-
173
+ if Fireworks is None:
174
+ raise ImportError("fireworks not available. Install with: pip install llama-index-llms-fireworks")
139
175
  llm = Fireworks(model=model_name, temperature=0, max_tokens=max_tokens)
140
176
  elif model_provider == ModelProvider.BEDROCK:
141
- from llama_index.llms.bedrock_converse import BedrockConverse
142
-
177
+ if BedrockConverse is None:
178
+ raise ImportError("bedrock_converse not available. Install with: pip install llama-index-llms-bedrock")
143
179
  aws_profile_name = os.getenv("AWS_PROFILE", None)
144
180
  aws_region = os.getenv("AWS_REGION", "us-east-2")
145
181
 
@@ -151,12 +187,12 @@ def get_llm(role: LLMRole, config: Optional[AgentConfig] = None) -> LLM:
151
187
  region_name=aws_region,
152
188
  )
153
189
  elif model_provider == ModelProvider.COHERE:
154
- from llama_index.llms.cohere import Cohere
155
-
190
+ if Cohere is None:
191
+ raise ImportError("cohere not available. Install with: pip install llama-index-llms-cohere")
156
192
  llm = Cohere(model=model_name, temperature=0, max_tokens=max_tokens)
157
193
  elif model_provider == ModelProvider.PRIVATE:
158
- from llama_index.llms.openai_like import OpenAILike
159
-
194
+ if OpenAILike is None:
195
+ raise ImportError("openai_like not available. Install with: pip install llama-index-llms-openai-like")
160
196
  llm = OpenAILike(
161
197
  model=model_name,
162
198
  temperature=0,
@@ -5,7 +5,7 @@ that takes a user question and a list of tools, and outputs a list of sub-questi
5
5
 
6
6
  import re
7
7
  import json
8
- from pydantic import BaseModel
8
+ from pydantic import BaseModel, Field
9
9
 
10
10
  from llama_index.core.workflow import (
11
11
  step,
@@ -37,6 +37,13 @@ class SubQuestionQueryWorkflow(Workflow):
37
37
 
38
38
  response: str
39
39
 
40
+ class OutputModelOnFail(BaseModel):
41
+ """
42
+ Outputs for the workflow when it fails.
43
+ """
44
+
45
+ qna: list[tuple[str,str]] = Field(default_factory=list, description="List of question-answer pairs")
46
+
40
47
  # Workflow Event types
41
48
  class QueryEvent(Event):
42
49
  """Event for a query."""
@@ -141,7 +148,7 @@ class SubQuestionQueryWorkflow(Workflow):
141
148
 
142
149
  return None
143
150
 
144
- @step(num_workers=4)
151
+ @step(num_workers=8)
145
152
  async def sub_question(self, ctx: Context, ev: QueryEvent) -> AnswerEvent:
146
153
  """
147
154
  Given a sub-question, return the answer to the sub-question, using the agent.
@@ -149,8 +156,11 @@ class SubQuestionQueryWorkflow(Workflow):
149
156
  if await ctx.get("verbose"):
150
157
  print(f"Sub-question is {ev.question}")
151
158
  agent = await ctx.get("agent")
152
- response = await agent.achat(ev.question)
153
- return self.AnswerEvent(question=ev.question, answer=str(response))
159
+ question = ev.question
160
+ response = await agent.achat(question)
161
+ answer = str(response)
162
+ await ctx.set("qna", await ctx.get("qna", []) + [(question, answer)])
163
+ return self.AnswerEvent(question=question, answer=answer)
154
164
 
155
165
  @step
156
166
  async def combine_answers(self, ctx: Context, ev: AnswerEvent) -> StopEvent | None:
@@ -209,6 +219,15 @@ class SequentialSubQuestionsWorkflow(Workflow):
209
219
 
210
220
  response: str
211
221
 
222
+ class OutputModelOnFail(BaseModel):
223
+ """
224
+ Outputs for the workflow when it fails.
225
+ """
226
+
227
+ qna: list[tuple[str,str]] = Field(
228
+ default_factory=list, description="List of question-answer pairs"
229
+ )
230
+
212
231
  # Workflow Event types
213
232
  class QueryEvent(Event):
214
233
  """Event for a query."""
@@ -322,24 +341,27 @@ class SequentialSubQuestionsWorkflow(Workflow):
322
341
  print(f"Sub-question is {ev.question}")
323
342
  agent = await ctx.get("agent")
324
343
  sub_questions = await ctx.get("sub_questions")
344
+ question = ev.question
325
345
  if ev.prev_answer:
326
346
  prev_question = sub_questions[ev.num - 1]
327
347
  prompt = f"""
328
348
  The answer to the question '{prev_question}' is: '{ev.prev_answer}'
329
- Now answer the following question: '{ev.question}'
349
+ Now answer the following question: '{question}'
330
350
  """
331
351
  response = await agent.achat(prompt)
332
352
  else:
333
- response = await agent.achat(ev.question)
353
+ response = await agent.achat(question)
354
+ answer = response.response
334
355
  if await ctx.get("verbose"):
335
- print(f"Answer is {response}")
356
+ print(f"Answer is {answer}")
336
357
 
337
358
  if ev.num + 1 < len(sub_questions):
359
+ await ctx.set("qna", await ctx.get("qna", []) + [(question, answer)])
338
360
  return self.QueryEvent(
339
361
  question=sub_questions[ev.num + 1],
340
- prev_answer=response.response,
362
+ prev_answer=answer,
341
363
  num=ev.num + 1,
342
364
  )
343
365
 
344
- output = self.OutputsModel(response=response.response)
366
+ output = self.OutputsModel(response=answer)
345
367
  return StopEvent(result=output)
@@ -129,6 +129,8 @@ class VectaraToolFactory:
129
129
  - 'type': the type of each filter attribute in Vectara (doc or part).
130
130
  - 'is_list': whether the filterable attribute is a list.
131
131
  - 'filter_name': the name of the filterable attribute in Vectara.
132
+ summarize_docs (bool, optional): Whether to summarize the retrieved documents.
133
+ summarize_llm_name (str, optional): The name of the LLM to use for summarization.
132
134
  fixed_filter (str, optional): A fixed Vectara filter condition to apply to all queries.
133
135
  lambda_val (Union[List[float] | float], optional): Lambda value (or list of values for each corpora)
134
136
  for the Vectara query, when using hybrid search.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: vectara_agentic
3
- Version: 0.2.21
3
+ Version: 0.2.23
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
@@ -530,6 +530,9 @@ class MyWorkflow(Workflow):
530
530
  class OutputsModel(BaseModel):
531
531
  answer: str
532
532
 
533
+ class OutputModelOnFail(BaseModel):
534
+ partial_response: str = ""
535
+
533
536
  @step
534
537
  async def my_step(self, ev: StartEvent) -> StopEvent:
535
538
  # do something here
@@ -587,6 +590,11 @@ workflow_result = asyncio.run(agent.run(inputs))
587
590
  print(workflow_result.answer)
588
591
  ```
589
592
 
593
+ When a workflow reaches its timeout, the timeout handler builds and returns an `OutputModelOnFail`
594
+ by reading each field named in that model from the workflow’s Context; for any field that isn’t set in the context,
595
+ it uses the default value you’ve defined on `OutputModelOnFail`. In other words, every property in `OutputModelOnFail`
596
+ must declare a default so that even if the corresponding context variable is missing, the model can be fully populated and returned without errors.
597
+
590
598
  ### Built-in Workflows
591
599
 
592
600
  `vectara-agentic` includes two workflow implementations that you can use right away:
@@ -1,115 +0,0 @@
1
- import unittest
2
-
3
- from pydantic import Field, BaseModel
4
-
5
- from vectara_agentic.agent import Agent, AgentType
6
- from vectara_agentic.agent_config import AgentConfig
7
- from vectara_agentic.tools import VectaraToolFactory
8
- from vectara_agentic.types import ModelProvider
9
-
10
-
11
- import nest_asyncio
12
- nest_asyncio.apply()
13
-
14
- tickers = {
15
- "C": "Citigroup",
16
- "COF": "Capital One",
17
- "JPM": "JPMorgan Chase",
18
- "AAPL": "Apple Computer",
19
- "GOOG": "Google",
20
- "AMZN": "Amazon",
21
- "SNOW": "Snowflake",
22
- "TEAM": "Atlassian",
23
- "TSLA": "Tesla",
24
- "NVDA": "Nvidia",
25
- "MSFT": "Microsoft",
26
- "AMD": "Advanced Micro Devices",
27
- "INTC": "Intel",
28
- "NFLX": "Netflix",
29
- "STT": "State Street",
30
- "BK": "Bank of New York Mellon",
31
- }
32
- years = list(range(2015, 2025))
33
-
34
-
35
- def mult(x: float, y: float) -> float:
36
- "Multiply two numbers"
37
- return x * y
38
-
39
-
40
- def get_company_info() -> list[str]:
41
- """
42
- Returns a dictionary of companies you can query about. Always check this before using any other tool.
43
- The output is a dictionary of valid ticker symbols mapped to company names.
44
- You can use this to identify the companies you can query about, and their ticker information.
45
- """
46
- return tickers
47
-
48
-
49
- def get_valid_years() -> list[str]:
50
- """
51
- Returns a list of the years for which financial reports are available.
52
- Always check this before using any other tool.
53
- """
54
- return years
55
-
56
-
57
- fc_config_groq = AgentConfig(
58
- agent_type=AgentType.FUNCTION_CALLING,
59
- main_llm_provider=ModelProvider.GROQ,
60
- tool_llm_provider=ModelProvider.GROQ,
61
- )
62
-
63
-
64
- class TestGROQ(unittest.TestCase):
65
-
66
- def test_tool_with_many_arguments(self):
67
-
68
- vectara_corpus_key = "vectara-docs_1"
69
- vectara_api_key = "zqt_UXrBcnI2UXINZkrv4g1tQPhzj02vfdtqYJIDiA"
70
- vec_factory = VectaraToolFactory(vectara_corpus_key, vectara_api_key)
71
-
72
- class QueryToolArgs(BaseModel):
73
- arg1: str = Field(description="the first argument", examples=["val1"])
74
- arg2: str = Field(description="the second argument", examples=["val2"])
75
- arg3: str = Field(description="the third argument", examples=["val3"])
76
- arg4: str = Field(description="the fourth argument", examples=["val4"])
77
- arg5: str = Field(description="the fifth argument", examples=["val5"])
78
- arg6: str = Field(description="the sixth argument", examples=["val6"])
79
- arg7: str = Field(description="the seventh argument", examples=["val7"])
80
- arg8: str = Field(description="the eighth argument", examples=["val8"])
81
- arg9: str = Field(description="the ninth argument", examples=["val9"])
82
- arg10: str = Field(description="the tenth argument", examples=["val10"])
83
- arg11: str = Field(description="the eleventh argument", examples=["val11"])
84
- arg12: str = Field(description="the twelfth argument", examples=["val12"])
85
- arg13: str = Field(
86
- description="the thirteenth argument", examples=["val13"]
87
- )
88
- arg14: str = Field(
89
- description="the fourteenth argument", examples=["val14"]
90
- )
91
- arg15: str = Field(description="the fifteenth argument", examples=["val15"])
92
-
93
- query_tool_1 = vec_factory.create_rag_tool(
94
- tool_name="rag_tool",
95
- tool_description="""
96
- A dummy tool that takes 15 arguments and returns a response (str) to the user query based on the data in this corpus.
97
- We are using this tool to test the tool factory works and does not crash with OpenAI.
98
- """,
99
- tool_args_schema=QueryToolArgs,
100
- )
101
-
102
- agent = Agent(
103
- tools=[query_tool_1],
104
- topic="Sample topic",
105
- custom_instructions="Call the tool with 15 arguments",
106
- agent_config=fc_config_groq,
107
- )
108
- res = agent.chat("What is the stock price?")
109
- self.assertTrue(
110
- any(sub in str(res) for sub in ["I don't know", "I do not have", "please specify which company"])
111
- )
112
-
113
-
114
- if __name__ == "__main__":
115
- unittest.main()