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

Files changed (43) hide show
  1. tests/__init__.py +1 -0
  2. tests/benchmark_models.py +547 -372
  3. tests/conftest.py +14 -12
  4. tests/endpoint.py +9 -5
  5. tests/run_tests.py +1 -0
  6. tests/test_agent.py +22 -9
  7. tests/test_agent_fallback_memory.py +4 -4
  8. tests/test_agent_memory_consistency.py +4 -4
  9. tests/test_agent_type.py +2 -0
  10. tests/test_api_endpoint.py +13 -13
  11. tests/test_bedrock.py +9 -1
  12. tests/test_fallback.py +18 -7
  13. tests/test_gemini.py +14 -40
  14. tests/test_groq.py +43 -1
  15. tests/test_openai.py +160 -0
  16. tests/test_private_llm.py +19 -6
  17. tests/test_react_error_handling.py +293 -0
  18. tests/test_react_memory.py +257 -0
  19. tests/test_react_streaming.py +135 -0
  20. tests/test_react_workflow_events.py +395 -0
  21. tests/test_return_direct.py +1 -0
  22. tests/test_serialization.py +58 -20
  23. tests/test_session_memory.py +11 -11
  24. tests/test_streaming.py +0 -44
  25. tests/test_together.py +75 -1
  26. tests/test_tools.py +3 -1
  27. tests/test_vectara_llms.py +2 -2
  28. tests/test_vhc.py +7 -2
  29. tests/test_workflow.py +17 -11
  30. vectara_agentic/_callback.py +79 -21
  31. vectara_agentic/_version.py +1 -1
  32. vectara_agentic/agent.py +65 -27
  33. vectara_agentic/agent_core/serialization.py +5 -9
  34. vectara_agentic/agent_core/streaming.py +245 -64
  35. vectara_agentic/agent_core/utils/schemas.py +2 -2
  36. vectara_agentic/llm_utils.py +64 -15
  37. vectara_agentic/tools.py +88 -31
  38. {vectara_agentic-0.4.2.dist-info → vectara_agentic-0.4.4.dist-info}/METADATA +133 -36
  39. vectara_agentic-0.4.4.dist-info/RECORD +59 -0
  40. vectara_agentic-0.4.2.dist-info/RECORD +0 -54
  41. {vectara_agentic-0.4.2.dist-info → vectara_agentic-0.4.4.dist-info}/WHEEL +0 -0
  42. {vectara_agentic-0.4.2.dist-info → vectara_agentic-0.4.4.dist-info}/licenses/LICENSE +0 -0
  43. {vectara_agentic-0.4.2.dist-info → vectara_agentic-0.4.4.dist-info}/top_level.txt +0 -0
tests/test_openai.py ADDED
@@ -0,0 +1,160 @@
1
+ # Suppress external dependency warnings before any other imports
2
+ import warnings
3
+
4
+ warnings.simplefilter("ignore", DeprecationWarning)
5
+
6
+ import unittest
7
+ import threading
8
+
9
+ from vectara_agentic.agent import Agent
10
+ from vectara_agentic.tools import ToolsFactory
11
+ from vectara_agentic.agent_config import AgentConfig
12
+ from vectara_agentic.types import AgentType, ModelProvider
13
+
14
+ import nest_asyncio
15
+
16
+ nest_asyncio.apply()
17
+
18
+ from conftest import (
19
+ fc_config_openai,
20
+ mult,
21
+ STANDARD_TEST_TOPIC,
22
+ STANDARD_TEST_INSTRUCTIONS,
23
+ )
24
+
25
+
26
+ ARIZE_LOCK = threading.Lock()
27
+
28
+
29
+ class TestOpenAI(unittest.IsolatedAsyncioTestCase):
30
+
31
+ async def test_multiturn(self):
32
+ """Test multi-turn conversation with default OpenAI model."""
33
+ with ARIZE_LOCK:
34
+ tools = [ToolsFactory().create_tool(mult)]
35
+ agent = Agent(
36
+ agent_config=fc_config_openai,
37
+ tools=tools,
38
+ topic=STANDARD_TEST_TOPIC,
39
+ custom_instructions=STANDARD_TEST_INSTRUCTIONS,
40
+ )
41
+
42
+ # First calculation: 5 * 10 = 50
43
+ stream1 = await agent.astream_chat(
44
+ "What is 5 times 10. Only give the answer, nothing else"
45
+ )
46
+ # Consume the stream
47
+ async for chunk in stream1.async_response_gen():
48
+ pass
49
+ _ = await stream1.aget_response()
50
+
51
+ # Second calculation: 3 * 7 = 21
52
+ stream2 = await agent.astream_chat(
53
+ "what is 3 times 7. Only give the answer, nothing else"
54
+ )
55
+ # Consume the stream
56
+ async for chunk in stream2.async_response_gen():
57
+ pass
58
+ _ = await stream2.aget_response()
59
+
60
+ # Final calculation: 50 * 21 = 1050
61
+ stream3 = await agent.astream_chat(
62
+ "multiply the results of the last two questions. Output only the answer."
63
+ )
64
+ # Consume the stream
65
+ async for chunk in stream3.async_response_gen():
66
+ pass
67
+ response3 = await stream3.aget_response()
68
+
69
+ self.assertEqual(response3.response, "1050")
70
+
71
+ async def test_gpt_4o(self):
72
+ """Test GPT-4o model with OpenAI provider."""
73
+ with ARIZE_LOCK:
74
+ config = AgentConfig(
75
+ agent_type=AgentType.FUNCTION_CALLING,
76
+ main_llm_provider=ModelProvider.OPENAI,
77
+ main_llm_model_name="gpt-4o",
78
+ tool_llm_provider=ModelProvider.OPENAI,
79
+ tool_llm_model_name="gpt-4o",
80
+ )
81
+
82
+ tools = [ToolsFactory().create_tool(mult)]
83
+ agent = Agent(
84
+ agent_config=config,
85
+ tools=tools,
86
+ topic=STANDARD_TEST_TOPIC,
87
+ custom_instructions=STANDARD_TEST_INSTRUCTIONS,
88
+ )
89
+
90
+ # Test simple multiplication: 4 * 3 = 12
91
+ stream = await agent.astream_chat(
92
+ "What is 4 times 3? Only give the answer, nothing else"
93
+ )
94
+ async for chunk in stream.async_response_gen():
95
+ pass
96
+ response = await stream.aget_response()
97
+
98
+ self.assertIn("12", response.response)
99
+
100
+ async def test_gpt_4_1(self):
101
+ """Test GPT-4.1 model with OpenAI provider."""
102
+ with ARIZE_LOCK:
103
+ config = AgentConfig(
104
+ agent_type=AgentType.FUNCTION_CALLING,
105
+ main_llm_provider=ModelProvider.OPENAI,
106
+ main_llm_model_name="gpt-4.1",
107
+ tool_llm_provider=ModelProvider.OPENAI,
108
+ tool_llm_model_name="gpt-4.1",
109
+ )
110
+
111
+ tools = [ToolsFactory().create_tool(mult)]
112
+ agent = Agent(
113
+ agent_config=config,
114
+ tools=tools,
115
+ topic=STANDARD_TEST_TOPIC,
116
+ custom_instructions=STANDARD_TEST_INSTRUCTIONS,
117
+ )
118
+
119
+ # Test simple multiplication: 6 * 2 = 12
120
+ stream = await agent.astream_chat(
121
+ "What is 6 times 2? Only give the answer, nothing else"
122
+ )
123
+ async for chunk in stream.async_response_gen():
124
+ pass
125
+ response = await stream.aget_response()
126
+
127
+ self.assertIn("12", response.response)
128
+
129
+ async def test_gpt_5_minimal_reasoning(self):
130
+ """Test GPT-5 model with minimal reasoning effort."""
131
+ with ARIZE_LOCK:
132
+ config = AgentConfig(
133
+ agent_type=AgentType.FUNCTION_CALLING,
134
+ main_llm_provider=ModelProvider.OPENAI,
135
+ main_llm_model_name="gpt-5",
136
+ tool_llm_provider=ModelProvider.OPENAI,
137
+ tool_llm_model_name="gpt-5",
138
+ )
139
+
140
+ tools = [ToolsFactory().create_tool(mult)]
141
+ agent = Agent(
142
+ agent_config=config,
143
+ tools=tools,
144
+ topic=STANDARD_TEST_TOPIC,
145
+ custom_instructions=STANDARD_TEST_INSTRUCTIONS,
146
+ )
147
+
148
+ # Test simple multiplication: 5 * 5 = 25
149
+ stream = await agent.astream_chat(
150
+ "What is 5 times 5? Only give the answer, nothing else"
151
+ )
152
+ async for chunk in stream.async_response_gen():
153
+ pass
154
+ response = await stream.aget_response()
155
+
156
+ self.assertIn("25", response.response)
157
+
158
+
159
+ if __name__ == "__main__":
160
+ unittest.main()
tests/test_private_llm.py CHANGED
@@ -1,5 +1,6 @@
1
1
  # Suppress external dependency warnings before any other imports
2
2
  import warnings
3
+
3
4
  warnings.simplefilter("ignore", DeprecationWarning)
4
5
 
5
6
  import os
@@ -16,19 +17,26 @@ from vectara_agentic.tools import ToolsFactory
16
17
 
17
18
 
18
19
  FLASK_PORT = 5001
20
+
21
+
19
22
  class TestPrivateLLM(unittest.TestCase):
20
23
 
21
24
  @classmethod
22
25
  def setUp(cls):
23
26
  # Start the Flask server as a subprocess
24
27
  cls.flask_process = subprocess.Popen(
25
- ['flask', 'run', f'--port={FLASK_PORT}'],
26
- env={**os.environ, 'FLASK_APP': 'tests.endpoint:app', 'FLASK_ENV': 'development'},
27
- stdout=None, stderr=None,
28
+ ["flask", "run", f"--port={FLASK_PORT}"],
29
+ env={
30
+ **os.environ,
31
+ "FLASK_APP": "tests.endpoint:app",
32
+ "FLASK_ENV": "development",
33
+ },
34
+ stdout=None,
35
+ stderr=None,
28
36
  )
29
37
  # Wait for the server to start
30
38
  timeout = 10
31
- url = f'http://127.0.0.1:{FLASK_PORT}/'
39
+ url = f"http://127.0.0.1:{FLASK_PORT}/"
32
40
  for _ in range(timeout):
33
41
  try:
34
42
  requests.get(url)
@@ -58,8 +66,13 @@ class TestPrivateLLM(unittest.TestCase):
58
66
  private_llm_api_base=f"http://127.0.0.1:{FLASK_PORT}/v1",
59
67
  private_llm_api_key="TEST_API_KEY",
60
68
  )
61
- agent = Agent(agent_config=config, tools=tools, topic=topic,
62
- custom_instructions=custom_instructions, verbose=False)
69
+ agent = Agent(
70
+ agent_config=config,
71
+ tools=tools,
72
+ topic=topic,
73
+ custom_instructions=custom_instructions,
74
+ verbose=False,
75
+ )
63
76
 
64
77
  # To run this test, you must have OPENAI_API_KEY in your environment
65
78
  res = agent.chat(
@@ -0,0 +1,293 @@
1
+ # Suppress external dependency warnings before any other imports
2
+ import warnings
3
+
4
+ warnings.simplefilter("ignore", DeprecationWarning)
5
+
6
+ import unittest
7
+ import asyncio
8
+
9
+ from vectara_agentic.agent import Agent
10
+ from vectara_agentic.tools import ToolsFactory
11
+ from vectara_agentic.agent_config import AgentConfig
12
+ from vectara_agentic.types import AgentType, ModelProvider
13
+
14
+ import nest_asyncio
15
+
16
+ nest_asyncio.apply()
17
+
18
+ from conftest import (
19
+ AgentTestMixin,
20
+ react_config_anthropic,
21
+ react_config_gemini,
22
+ react_config_together,
23
+ mult,
24
+ STANDARD_TEST_TOPIC,
25
+ STANDARD_TEST_INSTRUCTIONS,
26
+ is_rate_limited,
27
+ is_api_key_error,
28
+ )
29
+
30
+
31
+ class TestReActErrorHandling(unittest.TestCase, AgentTestMixin):
32
+ """Test error handling and recovery for ReAct agents."""
33
+
34
+ def setUp(self):
35
+ self.tools = [ToolsFactory().create_tool(mult)]
36
+ self.topic = STANDARD_TEST_TOPIC
37
+ self.instructions = STANDARD_TEST_INSTRUCTIONS
38
+
39
+ def test_react_anthropic_rate_limit_handling(self):
40
+ """Test ReAct agent handling of Anthropic rate limits."""
41
+ agent = Agent(
42
+ agent_config=react_config_anthropic,
43
+ tools=self.tools,
44
+ topic=self.topic,
45
+ custom_instructions=self.instructions,
46
+ )
47
+
48
+ with self.with_provider_fallback("Anthropic"):
49
+ response = agent.chat("What is 5 times 10?")
50
+ self.check_response_and_skip(response, "Anthropic")
51
+
52
+ # If we get a response, check it's valid
53
+ if response.response and not is_rate_limited(response.response):
54
+ self.assertIn("50", response.response)
55
+
56
+ def test_react_openai_error_handling(self):
57
+ """Test ReAct agent handling of OpenAI-specific errors."""
58
+ openai_react_config = AgentConfig(
59
+ agent_type=AgentType.REACT,
60
+ main_llm_provider=ModelProvider.OPENAI,
61
+ tool_llm_provider=ModelProvider.OPENAI,
62
+ )
63
+
64
+ agent = Agent(
65
+ agent_config=openai_react_config,
66
+ tools=self.tools,
67
+ topic=self.topic,
68
+ custom_instructions=self.instructions,
69
+ )
70
+
71
+ with self.with_provider_fallback("OpenAI"):
72
+ response = agent.chat("Calculate 7 times 8.")
73
+ self.check_response_and_skip(response, "OpenAI")
74
+
75
+ # If we get a response, check it's valid
76
+ if response.response and not is_rate_limited(response.response):
77
+ self.assertIn("56", response.response)
78
+
79
+ def test_react_together_error_handling(self):
80
+ """Test ReAct agent handling of Together.AI errors."""
81
+ agent = Agent(
82
+ agent_config=react_config_together,
83
+ tools=self.tools,
84
+ topic=self.topic,
85
+ custom_instructions=self.instructions,
86
+ )
87
+
88
+ with self.with_provider_fallback("Together AI"):
89
+ response = agent.chat("Calculate 4 times 15.")
90
+ self.check_response_and_skip(response, "Together AI")
91
+
92
+ # If we get a response, check it's valid
93
+ if response.response and not is_rate_limited(response.response):
94
+ self.assertIn("60", response.response)
95
+
96
+ def test_react_gemini_error_handling(self):
97
+ """Test ReAct agent handling of Gemini-specific errors."""
98
+ agent = Agent(
99
+ agent_config=react_config_gemini,
100
+ tools=self.tools,
101
+ topic=self.topic,
102
+ custom_instructions=self.instructions,
103
+ )
104
+
105
+ with self.with_provider_fallback("Gemini"):
106
+ response = agent.chat("What is 12 times 4?")
107
+ self.check_response_and_skip(response, "Gemini")
108
+
109
+ # If we get a response, check it's valid
110
+ if response.response and not is_rate_limited(response.response):
111
+ self.assertIn("48", response.response)
112
+
113
+ def test_react_async_error_handling(self):
114
+ """Test ReAct agent error handling during async operations."""
115
+ async def _async_test():
116
+ agent = Agent(
117
+ agent_config=react_config_anthropic,
118
+ tools=self.tools,
119
+ topic=self.topic,
120
+ custom_instructions=self.instructions,
121
+ )
122
+
123
+ with self.with_provider_fallback("Anthropic"):
124
+ # Test async chat error handling
125
+ response = await agent.achat("Calculate 11 times 3.")
126
+ self.check_response_and_skip(response, "Anthropic")
127
+
128
+ if response.response and not is_rate_limited(response.response):
129
+ self.assertIn("33", response.response)
130
+
131
+ asyncio.run(_async_test())
132
+
133
+ def test_react_streaming_error_handling(self):
134
+ """Test ReAct agent error handling during streaming operations."""
135
+ async def _async_test():
136
+ agent = Agent(
137
+ agent_config=react_config_anthropic,
138
+ tools=self.tools,
139
+ topic=self.topic,
140
+ custom_instructions=self.instructions,
141
+ )
142
+
143
+ with self.with_provider_fallback("Anthropic"):
144
+ try:
145
+ stream = await agent.astream_chat("Calculate 13 times 2.")
146
+
147
+ # Consume the stream
148
+ chunks = []
149
+ async for chunk in stream.async_response_gen():
150
+ chunks.append(str(chunk))
151
+
152
+ response = await stream.aget_response()
153
+ self.check_response_and_skip(response, "Anthropic")
154
+
155
+ if response.response and not is_rate_limited(response.response):
156
+ self.assertIn("26", response.response)
157
+
158
+ except Exception as e:
159
+ error_msg = str(e)
160
+ if is_rate_limited(error_msg) or is_api_key_error(error_msg):
161
+ self.skipTest(f"Anthropic streaming error: {error_msg}")
162
+ raise
163
+
164
+ asyncio.run(_async_test())
165
+
166
+ def test_react_tool_execution_error_handling(self):
167
+ """Test ReAct agent handling of tool execution errors."""
168
+
169
+ def error_tool(x: float) -> float:
170
+ """A tool that intentionally raises an error."""
171
+ if x < 0:
172
+ raise ValueError("Cannot process negative numbers")
173
+ return x * 2
174
+
175
+ error_tools = [ToolsFactory().create_tool(error_tool)]
176
+
177
+ agent = Agent(
178
+ agent_config=react_config_anthropic,
179
+ tools=error_tools,
180
+ topic=self.topic,
181
+ custom_instructions=self.instructions,
182
+ )
183
+
184
+ with self.with_provider_fallback("Anthropic"):
185
+ # Test with valid input first
186
+ response1 = agent.chat("Use the error_tool with input 5.")
187
+ self.check_response_and_skip(response1, "Anthropic")
188
+
189
+ if not is_rate_limited(response1.response):
190
+ self.assertIn("10", response1.response) # 5 * 2 = 10
191
+
192
+ # Test with invalid input that should cause tool error
193
+ response2 = agent.chat("Use the error_tool with input -1.")
194
+ self.check_response_and_skip(response2, "Anthropic")
195
+
196
+ if not is_rate_limited(response2.response):
197
+ # ReAct agent should handle the tool error gracefully
198
+ # and provide some kind of error message or explanation
199
+ self.assertTrue(len(response2.response) > 0)
200
+ # Should mention error or inability to process
201
+ error_indicators = ["error", "cannot", "unable", "negative", "problem"]
202
+ has_error_indication = any(
203
+ indicator in response2.response.lower()
204
+ for indicator in error_indicators
205
+ )
206
+ self.assertTrue(
207
+ has_error_indication,
208
+ f"Response should indicate error handling: {response2.response}",
209
+ )
210
+
211
+ def test_react_workflow_interruption_handling(self):
212
+ """Test ReAct agent handling of workflow interruptions."""
213
+ agent = Agent(
214
+ agent_config=react_config_anthropic,
215
+ tools=self.tools,
216
+ topic=self.topic,
217
+ custom_instructions=self.instructions,
218
+ )
219
+
220
+ with self.with_provider_fallback("Anthropic"):
221
+ # Test a complex multi-step task that might be interrupted
222
+ response = agent.chat(
223
+ "Calculate 3 times 7, then multiply that by 4, then add 10 to the result."
224
+ )
225
+ self.check_response_and_skip(response, "Anthropic")
226
+
227
+ if response.response and not is_rate_limited(response.response):
228
+ # Final result should be: (3*7)*4+10 = 21*4+10 = 84+10 = 94
229
+ self.assertIn("94", response.response)
230
+
231
+ def test_react_memory_corruption_recovery(self):
232
+ """Test ReAct agent recovery from memory-related errors."""
233
+ agent = Agent(
234
+ agent_config=react_config_anthropic,
235
+ tools=self.tools,
236
+ topic=self.topic,
237
+ custom_instructions=self.instructions,
238
+ )
239
+
240
+ with self.with_provider_fallback("Anthropic"):
241
+ # Normal operation
242
+ response1 = agent.chat("Calculate 6 times 7.")
243
+ self.check_response_and_skip(response1, "Anthropic")
244
+
245
+ if not is_rate_limited(response1.response):
246
+ self.assertIn("42", response1.response)
247
+
248
+ # Continue conversation to test memory consistency
249
+ response2 = agent.chat(
250
+ "What was the result of the previous calculation?"
251
+ )
252
+ self.check_response_and_skip(response2, "Anthropic")
253
+
254
+ if not is_rate_limited(response2.response):
255
+ # Should remember the previous result
256
+ self.assertIn("42", response2.response)
257
+
258
+ def test_react_fallback_behavior_on_provider_failure(self):
259
+ """Test ReAct agent behavior when provider fails completely."""
260
+
261
+ # Create a config with an invalid API configuration to simulate failure
262
+ invalid_config = AgentConfig(
263
+ agent_type=AgentType.REACT,
264
+ main_llm_provider=ModelProvider.ANTHROPIC,
265
+ tool_llm_provider=ModelProvider.ANTHROPIC,
266
+ )
267
+
268
+ agent = Agent(
269
+ agent_config=invalid_config,
270
+ tools=self.tools,
271
+ topic=self.topic,
272
+ custom_instructions=self.instructions,
273
+ )
274
+
275
+ with self.with_provider_fallback("Anthropic"):
276
+ try:
277
+ response = agent.chat("What is 2 times 3?")
278
+ self.check_response_and_skip(response, "Anthropic")
279
+
280
+ # If we get here without error, the test passed
281
+ if response.response and not is_rate_limited(response.response):
282
+ self.assertIn("6", response.response)
283
+
284
+ except Exception as e:
285
+ error_msg = str(e)
286
+ if is_rate_limited(error_msg) or is_api_key_error(error_msg):
287
+ self.skipTest(f"Anthropic provider failure: {error_msg}")
288
+ # For other exceptions, let them bubble up as actual test failures
289
+ raise
290
+
291
+
292
+ if __name__ == "__main__":
293
+ unittest.main()