vectara-agentic 0.2.4__py3-none-any.whl → 0.2.6__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.
- tests/test_agent.py +18 -0
- tests/test_agent_planning.py +46 -0
- tests/test_agent_type.py +83 -0
- tests/test_fallback.py +83 -0
- tests/test_private_llm.py +10 -9
- tests/test_workflow.py +67 -0
- vectara_agentic/__init__.py +12 -2
- vectara_agentic/_callback.py +12 -4
- vectara_agentic/_observability.py +1 -1
- vectara_agentic/_prompts.py +48 -7
- vectara_agentic/_version.py +1 -1
- vectara_agentic/agent.py +331 -134
- vectara_agentic/db_tools.py +2 -2
- vectara_agentic/sub_query_workflow.py +292 -0
- vectara_agentic/tools.py +34 -21
- vectara_agentic/types.py +5 -0
- vectara_agentic/utils.py +5 -3
- {vectara_agentic-0.2.4.dist-info → vectara_agentic-0.2.6.dist-info}/METADATA +101 -23
- vectara_agentic-0.2.6.dist-info/RECORD +28 -0
- {vectara_agentic-0.2.4.dist-info → vectara_agentic-0.2.6.dist-info}/WHEEL +1 -1
- vectara_agentic-0.2.4.dist-info/RECORD +0 -23
- {vectara_agentic-0.2.4.dist-info → vectara_agentic-0.2.6.dist-info/licenses}/LICENSE +0 -0
- {vectara_agentic-0.2.4.dist-info → vectara_agentic-0.2.6.dist-info}/top_level.txt +0 -0
vectara_agentic/agent.py
CHANGED
|
@@ -8,33 +8,39 @@ from datetime import date
|
|
|
8
8
|
import time
|
|
9
9
|
import json
|
|
10
10
|
import logging
|
|
11
|
-
import traceback
|
|
12
11
|
import asyncio
|
|
13
|
-
|
|
12
|
+
import importlib
|
|
14
13
|
from collections import Counter
|
|
15
14
|
|
|
16
15
|
import cloudpickle as pickle
|
|
17
16
|
|
|
18
17
|
from dotenv import load_dotenv
|
|
19
18
|
|
|
20
|
-
from
|
|
21
|
-
from pydantic import Field, create_model
|
|
19
|
+
from pydantic import Field, create_model, ValidationError
|
|
22
20
|
|
|
23
21
|
from llama_index.core.memory import ChatMemoryBuffer
|
|
24
22
|
from llama_index.core.llms import ChatMessage, MessageRole
|
|
25
23
|
from llama_index.core.tools import FunctionTool
|
|
26
|
-
from llama_index.core.agent import ReActAgent
|
|
24
|
+
from llama_index.core.agent import ReActAgent, StructuredPlannerAgent
|
|
27
25
|
from llama_index.core.agent.react.formatter import ReActChatFormatter
|
|
28
26
|
from llama_index.agent.llm_compiler import LLMCompilerAgentWorker
|
|
29
27
|
from llama_index.agent.lats import LATSAgentWorker
|
|
30
28
|
from llama_index.core.callbacks import CallbackManager, TokenCountingHandler
|
|
31
29
|
from llama_index.core.callbacks.base_handler import BaseCallbackHandler
|
|
32
30
|
from llama_index.agent.openai import OpenAIAgent
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
from .
|
|
31
|
+
from llama_index.core.agent.runner.base import AgentRunner
|
|
32
|
+
from llama_index.core.agent.types import BaseAgent
|
|
33
|
+
from llama_index.core.workflow import Workflow
|
|
34
|
+
|
|
35
|
+
from .types import (
|
|
36
|
+
AgentType, AgentStatusType, LLMRole, ToolType,
|
|
37
|
+
AgentResponse, AgentStreamingResponse, AgentConfigType
|
|
38
|
+
)
|
|
36
39
|
from .utils import get_llm, get_tokenizer_for_model
|
|
37
|
-
from ._prompts import
|
|
40
|
+
from ._prompts import (
|
|
41
|
+
REACT_PROMPT_TEMPLATE, GENERAL_PROMPT_TEMPLATE, GENERAL_INSTRUCTIONS,
|
|
42
|
+
STRUCTURED_PLANNER_PLAN_REFINE_PROMPT, STRUCTURED_PLANNER_INITIAL_PLAN_PROMPT
|
|
43
|
+
)
|
|
38
44
|
from ._callback import AgentCallbackHandler
|
|
39
45
|
from ._observability import setup_observer, eval_fcs
|
|
40
46
|
from .tools import VectaraToolFactory, VectaraTool, ToolsFactory
|
|
@@ -97,10 +103,6 @@ def _get_llm_compiler_prompt(prompt: str, topic: str, custom_instructions: str)
|
|
|
97
103
|
prompt += f"Today is {date.today().strftime('%A, %B %d, %Y')}"
|
|
98
104
|
return prompt
|
|
99
105
|
|
|
100
|
-
def _retry_if_exception(exception):
|
|
101
|
-
# Define the condition to retry on certain exceptions
|
|
102
|
-
return isinstance(exception, (TimeoutError))
|
|
103
|
-
|
|
104
106
|
|
|
105
107
|
def get_field_type(field_schema: dict) -> Any:
|
|
106
108
|
"""
|
|
@@ -141,12 +143,16 @@ class Agent:
|
|
|
141
143
|
topic: str = "general",
|
|
142
144
|
custom_instructions: str = "",
|
|
143
145
|
verbose: bool = True,
|
|
146
|
+
use_structured_planning: bool = False,
|
|
144
147
|
update_func: Optional[Callable[[AgentStatusType, str], None]] = None,
|
|
145
148
|
agent_progress_callback: Optional[Callable[[AgentStatusType, str], None]] = None,
|
|
146
149
|
query_logging_callback: Optional[Callable[[str, str], None]] = None,
|
|
147
150
|
agent_config: Optional[AgentConfig] = None,
|
|
151
|
+
fallback_agent_config: Optional[AgentConfig] = None,
|
|
148
152
|
chat_history: Optional[list[Tuple[str, str]]] = None,
|
|
149
153
|
validate_tools: bool = False,
|
|
154
|
+
workflow_cls: Workflow = None,
|
|
155
|
+
workflow_timeout: int = 120,
|
|
150
156
|
) -> None:
|
|
151
157
|
"""
|
|
152
158
|
Initialize the agent with the specified type, tools, topic, and system message.
|
|
@@ -157,26 +163,37 @@ class Agent:
|
|
|
157
163
|
topic (str, optional): The topic for the agent. Defaults to 'general'.
|
|
158
164
|
custom_instructions (str, optional): Custom instructions for the agent. Defaults to ''.
|
|
159
165
|
verbose (bool, optional): Whether the agent should print its steps. Defaults to True.
|
|
166
|
+
use_structured_planning (bool, optional)
|
|
167
|
+
Whether or not we want to wrap the agent with LlamaIndex StructuredPlannerAgent.
|
|
160
168
|
agent_progress_callback (Callable): A callback function the code calls on any agent updates.
|
|
161
169
|
update_func (Callable): old name for agent_progress_callback. Will be deprecated in future.
|
|
162
170
|
query_logging_callback (Callable): A callback function the code calls upon completion of a query
|
|
163
171
|
agent_config (AgentConfig, optional): The configuration of the agent.
|
|
164
172
|
Defaults to AgentConfig(), which reads from environment variables.
|
|
173
|
+
fallback_agent_config (AgentConfig, optional): The fallback configuration of the agent.
|
|
174
|
+
This config is used when the main agent config fails multiple times.
|
|
165
175
|
chat_history (Tuple[str, str], optional): A list of user/agent chat pairs to initialize the agent memory.
|
|
166
176
|
validate_tools (bool, optional): Whether to validate tool inconsistency with instructions.
|
|
167
177
|
Defaults to False.
|
|
178
|
+
workflow_cls (Workflow, optional): The workflow class to be used with run(). Defaults to None.
|
|
179
|
+
workflow_timeout (int, optional): The timeout for the workflow in seconds. Defaults to 120.
|
|
168
180
|
"""
|
|
169
181
|
self.agent_config = agent_config or AgentConfig()
|
|
170
|
-
self.
|
|
182
|
+
self.agent_config_type = AgentConfigType.DEFAULT
|
|
171
183
|
self.tools = tools
|
|
172
184
|
if not any(tool.metadata.name == 'get_current_date' for tool in self.tools):
|
|
173
185
|
self.tools += [ToolsFactory().create_tool(get_current_date)]
|
|
186
|
+
self.agent_type = self.agent_config.agent_type
|
|
187
|
+
self.use_structured_planning = use_structured_planning
|
|
174
188
|
self.llm = get_llm(LLMRole.MAIN, config=self.agent_config)
|
|
175
189
|
self._custom_instructions = custom_instructions
|
|
176
190
|
self._topic = topic
|
|
177
191
|
self.agent_progress_callback = agent_progress_callback if agent_progress_callback else update_func
|
|
178
192
|
self.query_logging_callback = query_logging_callback
|
|
179
193
|
|
|
194
|
+
self.workflow_cls = workflow_cls
|
|
195
|
+
self.workflow_timeout = workflow_timeout
|
|
196
|
+
|
|
180
197
|
# Validate tools
|
|
181
198
|
# Check for:
|
|
182
199
|
# 1. multiple copies of the same tool
|
|
@@ -214,7 +231,6 @@ class Agent:
|
|
|
214
231
|
if self.tool_token_counter:
|
|
215
232
|
callbacks.append(self.tool_token_counter)
|
|
216
233
|
callback_manager = CallbackManager(callbacks) # type: ignore
|
|
217
|
-
self.llm.callback_manager = callback_manager
|
|
218
234
|
self.verbose = verbose
|
|
219
235
|
|
|
220
236
|
if chat_history:
|
|
@@ -225,70 +241,119 @@ class Agent:
|
|
|
225
241
|
self.memory = ChatMemoryBuffer.from_defaults(token_limit=128000, chat_history=msg_history)
|
|
226
242
|
else:
|
|
227
243
|
self.memory = ChatMemoryBuffer.from_defaults(token_limit=128000)
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
244
|
+
|
|
245
|
+
# Set up main agent and fallback agent
|
|
246
|
+
self.agent = self._create_agent(self.agent_config, callback_manager)
|
|
247
|
+
self.fallback_agent_config = fallback_agent_config
|
|
248
|
+
if self.fallback_agent_config:
|
|
249
|
+
self.fallback_agent = self._create_agent(self.fallback_agent_config, callback_manager)
|
|
250
|
+
else:
|
|
251
|
+
self.fallback_agent_config = None
|
|
252
|
+
|
|
253
|
+
# Setup observability
|
|
254
|
+
try:
|
|
255
|
+
self.observability_enabled = setup_observer(self.agent_config)
|
|
256
|
+
except Exception as e:
|
|
257
|
+
print(f"Failed to set up observer ({e}), ignoring")
|
|
258
|
+
self.observability_enabled = False
|
|
259
|
+
|
|
260
|
+
def _create_agent(
|
|
261
|
+
self,
|
|
262
|
+
config: AgentConfig,
|
|
263
|
+
llm_callback_manager: CallbackManager
|
|
264
|
+
) -> Union[BaseAgent, AgentRunner]:
|
|
265
|
+
"""
|
|
266
|
+
Creates the agent based on the configuration object.
|
|
267
|
+
|
|
268
|
+
Args:
|
|
269
|
+
|
|
270
|
+
config: The configuration of the agent.
|
|
271
|
+
llm_callback_manager: The callback manager for the agent's llm.
|
|
272
|
+
|
|
273
|
+
Returns:
|
|
274
|
+
Union[BaseAgent, AgentRunner]: The configured agent object.
|
|
275
|
+
"""
|
|
276
|
+
agent_type = config.agent_type
|
|
277
|
+
llm = get_llm(LLMRole.MAIN, config=config)
|
|
278
|
+
llm.callback_manager = llm_callback_manager
|
|
279
|
+
|
|
280
|
+
if agent_type == AgentType.REACT:
|
|
281
|
+
prompt = _get_prompt(REACT_PROMPT_TEMPLATE, self._topic, self._custom_instructions)
|
|
282
|
+
agent = ReActAgent.from_tools(
|
|
231
283
|
tools=self.tools,
|
|
232
|
-
llm=
|
|
284
|
+
llm=llm,
|
|
233
285
|
memory=self.memory,
|
|
234
|
-
verbose=verbose,
|
|
286
|
+
verbose=self.verbose,
|
|
235
287
|
react_chat_formatter=ReActChatFormatter(system_header=prompt),
|
|
236
|
-
max_iterations=
|
|
237
|
-
callable_manager=
|
|
288
|
+
max_iterations=config.max_reasoning_steps,
|
|
289
|
+
callable_manager=llm_callback_manager,
|
|
238
290
|
)
|
|
239
|
-
elif
|
|
240
|
-
prompt = _get_prompt(GENERAL_PROMPT_TEMPLATE,
|
|
241
|
-
|
|
291
|
+
elif agent_type == AgentType.OPENAI:
|
|
292
|
+
prompt = _get_prompt(GENERAL_PROMPT_TEMPLATE, self._topic, self._custom_instructions)
|
|
293
|
+
agent = OpenAIAgent.from_tools(
|
|
242
294
|
tools=self.tools,
|
|
243
|
-
llm=
|
|
295
|
+
llm=llm,
|
|
244
296
|
memory=self.memory,
|
|
245
|
-
verbose=verbose,
|
|
246
|
-
callable_manager=
|
|
247
|
-
max_function_calls=
|
|
297
|
+
verbose=self.verbose,
|
|
298
|
+
callable_manager=llm_callback_manager,
|
|
299
|
+
max_function_calls=config.max_reasoning_steps,
|
|
248
300
|
system_prompt=prompt,
|
|
249
301
|
)
|
|
250
|
-
elif
|
|
302
|
+
elif agent_type == AgentType.LLMCOMPILER:
|
|
251
303
|
agent_worker = LLMCompilerAgentWorker.from_tools(
|
|
252
304
|
tools=self.tools,
|
|
253
|
-
llm=
|
|
254
|
-
verbose=verbose,
|
|
255
|
-
callable_manager=
|
|
305
|
+
llm=llm,
|
|
306
|
+
verbose=self.verbose,
|
|
307
|
+
callable_manager=llm_callback_manager,
|
|
256
308
|
)
|
|
257
309
|
agent_worker.system_prompt = _get_prompt(
|
|
258
|
-
_get_llm_compiler_prompt(agent_worker.system_prompt,
|
|
259
|
-
|
|
310
|
+
_get_llm_compiler_prompt(agent_worker.system_prompt, self._topic, self._custom_instructions),
|
|
311
|
+
self._topic, self._custom_instructions
|
|
260
312
|
)
|
|
261
313
|
agent_worker.system_prompt_replan = _get_prompt(
|
|
262
|
-
_get_llm_compiler_prompt(agent_worker.system_prompt_replan,
|
|
263
|
-
|
|
314
|
+
_get_llm_compiler_prompt(agent_worker.system_prompt_replan, self._topic, self._custom_instructions),
|
|
315
|
+
self._topic, self._custom_instructions
|
|
264
316
|
)
|
|
265
|
-
|
|
266
|
-
elif
|
|
317
|
+
agent = agent_worker.as_agent()
|
|
318
|
+
elif agent_type == AgentType.LATS:
|
|
267
319
|
agent_worker = LATSAgentWorker.from_tools(
|
|
268
320
|
tools=self.tools,
|
|
269
|
-
llm=
|
|
321
|
+
llm=llm,
|
|
270
322
|
num_expansions=3,
|
|
271
323
|
max_rollouts=-1,
|
|
272
|
-
verbose=verbose,
|
|
273
|
-
callable_manager=
|
|
324
|
+
verbose=self.verbose,
|
|
325
|
+
callable_manager=llm_callback_manager,
|
|
274
326
|
)
|
|
275
|
-
prompt = _get_prompt(REACT_PROMPT_TEMPLATE,
|
|
327
|
+
prompt = _get_prompt(REACT_PROMPT_TEMPLATE, self._topic, self._custom_instructions)
|
|
276
328
|
agent_worker.chat_formatter = ReActChatFormatter(system_header=prompt)
|
|
277
|
-
|
|
329
|
+
agent = agent_worker.as_agent()
|
|
278
330
|
else:
|
|
279
|
-
raise ValueError(f"Unknown agent type: {
|
|
331
|
+
raise ValueError(f"Unknown agent type: {agent_type}")
|
|
280
332
|
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
333
|
+
# Set up structured planner if needed
|
|
334
|
+
if (self.use_structured_planning
|
|
335
|
+
or self.agent_type in [AgentType.LLMCOMPILER, AgentType.LATS]):
|
|
336
|
+
agent = StructuredPlannerAgent(
|
|
337
|
+
agent_worker=agent.agent_worker,
|
|
338
|
+
tools=self.tools,
|
|
339
|
+
memory=self.memory,
|
|
340
|
+
verbose=self.verbose,
|
|
341
|
+
initial_plan_prompt=STRUCTURED_PLANNER_INITIAL_PLAN_PROMPT,
|
|
342
|
+
plan_refine_prompt=STRUCTURED_PLANNER_PLAN_REFINE_PROMPT,
|
|
343
|
+
)
|
|
344
|
+
|
|
345
|
+
return agent
|
|
286
346
|
|
|
287
347
|
def clear_memory(self) -> None:
|
|
288
348
|
"""
|
|
289
349
|
Clear the agent's memory.
|
|
290
350
|
"""
|
|
291
|
-
self.
|
|
351
|
+
if self.agent_config_type == AgentConfigType.DEFAULT:
|
|
352
|
+
self.agent.memory.reset()
|
|
353
|
+
elif self.agent_config_type == AgentConfigType.FALLBACK and self.fallback_agent_config:
|
|
354
|
+
self.fallback_agent.memory.reset()
|
|
355
|
+
else:
|
|
356
|
+
raise ValueError(f"Invalid agent config type {self.agent_config_type}")
|
|
292
357
|
|
|
293
358
|
def __eq__(self, other):
|
|
294
359
|
if not isinstance(other, Agent):
|
|
@@ -296,10 +361,10 @@ class Agent:
|
|
|
296
361
|
return False
|
|
297
362
|
|
|
298
363
|
# Compare agent_type
|
|
299
|
-
if self.agent_type != other.agent_type:
|
|
364
|
+
if self.agent_config.agent_type != other.agent_config.agent_type:
|
|
300
365
|
print(
|
|
301
|
-
f"Comparison failed: agent_type differs. (self.agent_type: {self.agent_type},
|
|
302
|
-
f"other.agent_type: {other.agent_type})"
|
|
366
|
+
f"Comparison failed: agent_type differs. (self.agent_config.agent_type: {self.agent_config.agent_type},"
|
|
367
|
+
f" other.agent_config.agent_type: {other.agent_config.agent_type})"
|
|
303
368
|
)
|
|
304
369
|
return False
|
|
305
370
|
|
|
@@ -329,7 +394,7 @@ class Agent:
|
|
|
329
394
|
print(f"Comparison failed: verbose differs. (self.verbose: {self.verbose}, other.verbose: {other.verbose})")
|
|
330
395
|
return False
|
|
331
396
|
|
|
332
|
-
# Compare agent
|
|
397
|
+
# Compare agent memory
|
|
333
398
|
if self.agent.memory.chat_store != other.agent.memory.chat_store:
|
|
334
399
|
print(
|
|
335
400
|
f"Comparison failed: agent memory differs. (self.agent: {repr(self.agent.memory.chat_store)}, "
|
|
@@ -352,7 +417,11 @@ class Agent:
|
|
|
352
417
|
agent_progress_callback: Optional[Callable[[AgentStatusType, str], None]] = None,
|
|
353
418
|
query_logging_callback: Optional[Callable[[str, str], None]] = None,
|
|
354
419
|
agent_config: AgentConfig = AgentConfig(),
|
|
420
|
+
validate_tools: bool = False,
|
|
421
|
+
fallback_agent_config: Optional[AgentConfig] = None,
|
|
355
422
|
chat_history: Optional[list[Tuple[str, str]]] = None,
|
|
423
|
+
workflow_cls: Workflow = None,
|
|
424
|
+
workflow_timeout: int = 120,
|
|
356
425
|
) -> "Agent":
|
|
357
426
|
"""
|
|
358
427
|
Create an agent from tools, agent type, and language model.
|
|
@@ -367,7 +436,12 @@ class Agent:
|
|
|
367
436
|
update_func (Callable): old name for agent_progress_callback. Will be deprecated in future.
|
|
368
437
|
query_logging_callback (Callable): A callback function the code calls upon completion of a query
|
|
369
438
|
agent_config (AgentConfig, optional): The configuration of the agent.
|
|
439
|
+
fallback_agent_config (AgentConfig, optional): The fallback configuration of the agent.
|
|
370
440
|
chat_history (Tuple[str, str], optional): A list of user/agent chat pairs to initialize the agent memory.
|
|
441
|
+
validate_tools (bool, optional): Whether to validate tool inconsistency with instructions.
|
|
442
|
+
Defaults to False.
|
|
443
|
+
workflow_cls (Workflow, optional): The workflow class to be used with run(). Defaults to None.
|
|
444
|
+
workflow_timeout (int, optional): The timeout for the workflow in seconds. Defaults to 120.
|
|
371
445
|
|
|
372
446
|
Returns:
|
|
373
447
|
Agent: An instance of the Agent class.
|
|
@@ -378,6 +452,9 @@ class Agent:
|
|
|
378
452
|
query_logging_callback=query_logging_callback,
|
|
379
453
|
update_func=update_func, agent_config=agent_config,
|
|
380
454
|
chat_history=chat_history,
|
|
455
|
+
validate_tools=validate_tools,
|
|
456
|
+
fallback_agent_config=fallback_agent_config,
|
|
457
|
+
workflow_cls = workflow_cls, workflow_timeout = workflow_timeout,
|
|
381
458
|
)
|
|
382
459
|
|
|
383
460
|
@classmethod
|
|
@@ -390,6 +467,9 @@ class Agent:
|
|
|
390
467
|
vectara_api_key: str = str(os.environ.get("VECTARA_API_KEY", "")),
|
|
391
468
|
agent_progress_callback: Optional[Callable[[AgentStatusType, str], None]] = None,
|
|
392
469
|
query_logging_callback: Optional[Callable[[str, str], None]] = None,
|
|
470
|
+
agent_config: AgentConfig = AgentConfig(),
|
|
471
|
+
fallback_agent_config: Optional[AgentConfig] = None,
|
|
472
|
+
chat_history: Optional[list[Tuple[str, str]]] = None,
|
|
393
473
|
verbose: bool = False,
|
|
394
474
|
vectara_filter_fields: list[dict] = [],
|
|
395
475
|
vectara_offset: int = 0,
|
|
@@ -425,6 +505,9 @@ class Agent:
|
|
|
425
505
|
vectara_api_key (str): The Vectara API key.
|
|
426
506
|
agent_progress_callback (Callable): A callback function the code calls on any agent updates.
|
|
427
507
|
query_logging_callback (Callable): A callback function the code calls upon completion of a query
|
|
508
|
+
agent_config (AgentConfig, optional): The configuration of the agent.
|
|
509
|
+
fallback_agent_config (AgentConfig, optional): The fallback configuration of the agent.
|
|
510
|
+
chat_history (Tuple[str, str], optional): A list of user/agent chat pairs to initialize the agent memory.
|
|
428
511
|
data_description (str): The description of the data.
|
|
429
512
|
assistant_specialty (str): The specialty of the assistant.
|
|
430
513
|
verbose (bool, optional): Whether to print verbose output.
|
|
@@ -526,22 +609,41 @@ class Agent:
|
|
|
526
609
|
verbose=verbose,
|
|
527
610
|
agent_progress_callback=agent_progress_callback,
|
|
528
611
|
query_logging_callback=query_logging_callback,
|
|
612
|
+
agent_config=agent_config,
|
|
613
|
+
fallback_agent_config=fallback_agent_config,
|
|
614
|
+
chat_history=chat_history,
|
|
529
615
|
)
|
|
530
616
|
|
|
531
|
-
def
|
|
617
|
+
def _switch_agent_config(self) -> None:
|
|
618
|
+
""""
|
|
619
|
+
Switch the configuration type of the agent.
|
|
620
|
+
This function is called automatically to switch the agent configuration if the current configuration fails.
|
|
621
|
+
"""
|
|
622
|
+
if self.agent_config_type == AgentConfigType.DEFAULT:
|
|
623
|
+
self.agent_config_type = AgentConfigType.FALLBACK
|
|
624
|
+
else:
|
|
625
|
+
self.agent_config_type = AgentConfigType.DEFAULT
|
|
626
|
+
|
|
627
|
+
def report(self, detailed: bool = False) -> None:
|
|
532
628
|
"""
|
|
533
629
|
Get a report from the agent.
|
|
534
630
|
|
|
631
|
+
Args:
|
|
632
|
+
detailed (bool, optional): Whether to include detailed information. Defaults to False.
|
|
633
|
+
|
|
535
634
|
Returns:
|
|
536
635
|
str: The report from the agent.
|
|
537
636
|
"""
|
|
538
637
|
print("Vectara agentic Report:")
|
|
539
|
-
print(f"Agent Type = {self.agent_type}")
|
|
638
|
+
print(f"Agent Type = {self.agent_config.agent_type}")
|
|
540
639
|
print(f"Topic = {self._topic}")
|
|
541
640
|
print("Tools:")
|
|
542
641
|
for tool in self.tools:
|
|
543
642
|
if hasattr(tool, 'metadata'):
|
|
544
|
-
|
|
643
|
+
if detailed:
|
|
644
|
+
print(f"- {tool.metadata.name} - {tool.metadata.description}")
|
|
645
|
+
else:
|
|
646
|
+
print(f"- {tool.metadata.name}")
|
|
545
647
|
else:
|
|
546
648
|
print("- tool without metadata")
|
|
547
649
|
print(f"Agent LLM = {get_llm(LLMRole.MAIN, config=self.agent_config).metadata.model_name}")
|
|
@@ -559,13 +661,27 @@ class Agent:
|
|
|
559
661
|
"tool token count": self.tool_token_counter.total_llm_token_count if self.tool_token_counter else -1,
|
|
560
662
|
}
|
|
561
663
|
|
|
664
|
+
def _get_current_agent(self):
|
|
665
|
+
return self.agent if self.agent_config_type == AgentConfigType.DEFAULT else self.fallback_agent
|
|
666
|
+
|
|
667
|
+
def _get_current_agent_type(self):
|
|
668
|
+
return (
|
|
669
|
+
self.agent_config.agent_type if self.agent_config_type == AgentConfigType.DEFAULT
|
|
670
|
+
else self.fallback_agent_config.agent_type
|
|
671
|
+
)
|
|
672
|
+
|
|
562
673
|
async def _aformat_for_lats(self, prompt, agent_response):
|
|
563
674
|
llm_prompt = f"""
|
|
564
675
|
Given the question '{prompt}', and agent response '{agent_response.response}',
|
|
565
676
|
Please provide a well formatted final response to the query.
|
|
566
677
|
final response:
|
|
567
678
|
"""
|
|
568
|
-
|
|
679
|
+
agent_type = self._get_current_agent_type()
|
|
680
|
+
if agent_type != AgentType.LATS:
|
|
681
|
+
return
|
|
682
|
+
|
|
683
|
+
agent = self._get_current_agent()
|
|
684
|
+
agent_response.response = str(agent.llm.acomplete(llm_prompt))
|
|
569
685
|
|
|
570
686
|
def chat(self, prompt: str) -> AgentResponse: # type: ignore
|
|
571
687
|
"""
|
|
@@ -579,12 +695,7 @@ class Agent:
|
|
|
579
695
|
"""
|
|
580
696
|
return asyncio.run(self.achat(prompt))
|
|
581
697
|
|
|
582
|
-
|
|
583
|
-
retry_on_exception=_retry_if_exception,
|
|
584
|
-
stop_max_attempt_number=3,
|
|
585
|
-
wait_fixed=2000,
|
|
586
|
-
)
|
|
587
|
-
async def achat(self, prompt: str) -> AgentResponse: # type: ignore
|
|
698
|
+
async def achat(self, prompt: str) -> AgentResponse: # type: ignore
|
|
588
699
|
"""
|
|
589
700
|
Interact with the agent using a chat prompt.
|
|
590
701
|
|
|
@@ -594,26 +705,30 @@ class Agent:
|
|
|
594
705
|
Returns:
|
|
595
706
|
AgentResponse: The response from the agent.
|
|
596
707
|
"""
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
708
|
+
max_attempts = 4 if self.fallback_agent_config else 2
|
|
709
|
+
attempt = 0
|
|
710
|
+
while attempt < max_attempts:
|
|
711
|
+
try:
|
|
712
|
+
current_agent = self._get_current_agent()
|
|
713
|
+
agent_response = await current_agent.achat(prompt)
|
|
602
714
|
await self._aformat_for_lats(prompt, agent_response)
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
715
|
+
if self.observability_enabled:
|
|
716
|
+
eval_fcs()
|
|
717
|
+
if self.query_logging_callback:
|
|
718
|
+
self.query_logging_callback(prompt, agent_response.response)
|
|
719
|
+
return agent_response
|
|
720
|
+
|
|
721
|
+
except Exception:
|
|
722
|
+
if attempt >= 2:
|
|
723
|
+
if self.verbose:
|
|
724
|
+
print(f"LLM call failed on attempt {attempt+1}. Switching agent configuration.")
|
|
725
|
+
self._switch_agent_config()
|
|
726
|
+
time.sleep(1)
|
|
727
|
+
attempt += 1
|
|
728
|
+
|
|
729
|
+
return AgentResponse(
|
|
730
|
+
response=f"LLM failure can't be resolved after {max_attempts} attempts."
|
|
731
|
+
)
|
|
617
732
|
|
|
618
733
|
def stream_chat(self, prompt: str) -> AgentStreamingResponse: # type: ignore
|
|
619
734
|
"""
|
|
@@ -625,11 +740,6 @@ class Agent:
|
|
|
625
740
|
"""
|
|
626
741
|
return asyncio.run(self.astream_chat(prompt))
|
|
627
742
|
|
|
628
|
-
@retry(
|
|
629
|
-
retry_on_exception=_retry_if_exception,
|
|
630
|
-
stop_max_attempt_number=3,
|
|
631
|
-
wait_fixed=2000,
|
|
632
|
-
)
|
|
633
743
|
async def astream_chat(self, prompt: str) -> AgentStreamingResponse: # type: ignore
|
|
634
744
|
"""
|
|
635
745
|
Interact with the agent using a chat prompt asynchronously with streaming.
|
|
@@ -638,29 +748,85 @@ class Agent:
|
|
|
638
748
|
Returns:
|
|
639
749
|
AgentStreamingResponse: The streaming response from the agent.
|
|
640
750
|
"""
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
751
|
+
max_attempts = 4 if self.fallback_agent_config else 2
|
|
752
|
+
attempt = 0
|
|
753
|
+
while attempt < max_attempts:
|
|
754
|
+
try:
|
|
755
|
+
current_agent = self._get_current_agent()
|
|
756
|
+
agent_response = await current_agent.astream_chat(prompt)
|
|
757
|
+
original_async_response_gen = agent_response.async_response_gen
|
|
758
|
+
|
|
759
|
+
# Define a wrapper to preserve streaming behavior while executing post-stream logic.
|
|
760
|
+
async def _stream_response_wrapper():
|
|
761
|
+
async for token in original_async_response_gen():
|
|
762
|
+
yield token # Yield tokens as they are generated
|
|
763
|
+
# Post-streaming additional logic:
|
|
764
|
+
await self._aformat_for_lats(prompt, agent_response)
|
|
765
|
+
if self.query_logging_callback:
|
|
766
|
+
self.query_logging_callback(prompt, agent_response.response)
|
|
767
|
+
if self.observability_enabled:
|
|
768
|
+
eval_fcs()
|
|
769
|
+
|
|
770
|
+
agent_response.async_response_gen = _stream_response_wrapper # Override the generator
|
|
771
|
+
return agent_response
|
|
772
|
+
|
|
773
|
+
except Exception:
|
|
774
|
+
if attempt >= 2:
|
|
775
|
+
if self.verbose:
|
|
776
|
+
print("LLM call failed. Switching agent configuration.")
|
|
777
|
+
self._switch_agent_config()
|
|
778
|
+
time.sleep(1)
|
|
779
|
+
attempt += 1
|
|
780
|
+
|
|
781
|
+
return AgentResponse(
|
|
782
|
+
response=f"LLM failure can't be resolved after {max_attempts} attempts."
|
|
783
|
+
)
|
|
644
784
|
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
785
|
+
#
|
|
786
|
+
# run() method for running a workflow
|
|
787
|
+
# workflow will always get these arguments in the StartEvent: agent, tools, llm, verbose
|
|
788
|
+
# the inputs argument comes from the call to run()
|
|
789
|
+
#
|
|
790
|
+
async def run(
|
|
791
|
+
self,
|
|
792
|
+
inputs: Any,
|
|
793
|
+
verbose: bool = False
|
|
794
|
+
) -> Any:
|
|
795
|
+
"""
|
|
796
|
+
Run a workflow using the agent.
|
|
797
|
+
workflow class must be provided in the agent constructor.
|
|
798
|
+
Args:
|
|
799
|
+
inputs (Any): The inputs to the workflow.
|
|
800
|
+
verbose (bool, optional): Whether to print verbose output. Defaults to False.
|
|
801
|
+
Returns:
|
|
802
|
+
Any: The output of the workflow.
|
|
803
|
+
"""
|
|
804
|
+
# Create workflow
|
|
805
|
+
if self.workflow_cls:
|
|
806
|
+
workflow = self.workflow_cls(timeout=self.workflow_timeout, verbose=verbose)
|
|
807
|
+
else:
|
|
808
|
+
raise ValueError("Workflow is not defined.")
|
|
649
809
|
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
if self.query_logging_callback:
|
|
654
|
-
self.query_logging_callback(prompt, agent_response.response)
|
|
655
|
-
if self.observability_enabled:
|
|
656
|
-
eval_fcs()
|
|
810
|
+
# Validate inputs is in the form of workflow.InputsModel
|
|
811
|
+
if not isinstance(inputs, self.workflow_cls.InputsModel):
|
|
812
|
+
raise ValueError(f"Inputs must be an instance of {workflow.InputsModel}.")
|
|
657
813
|
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
814
|
+
# run workflow
|
|
815
|
+
result = await workflow.run(
|
|
816
|
+
agent=self,
|
|
817
|
+
tools=self.tools,
|
|
818
|
+
llm=self.llm,
|
|
819
|
+
verbose=verbose,
|
|
820
|
+
inputs=inputs,
|
|
821
|
+
)
|
|
822
|
+
|
|
823
|
+
# return output in the form of workflow.OutputsModel
|
|
824
|
+
try:
|
|
825
|
+
output = workflow.OutputsModel.model_validate(result)
|
|
826
|
+
except ValidationError as e:
|
|
827
|
+
raise ValueError(f"Failed to map workflow output to model: {e}") from e
|
|
828
|
+
|
|
829
|
+
return output
|
|
664
830
|
|
|
665
831
|
#
|
|
666
832
|
# Serialization methods
|
|
@@ -679,56 +845,85 @@ class Agent:
|
|
|
679
845
|
tool_info = []
|
|
680
846
|
|
|
681
847
|
for tool in self.tools:
|
|
682
|
-
|
|
848
|
+
if hasattr(tool.metadata, "fn_schema"):
|
|
849
|
+
fn_schema_cls = tool.metadata.fn_schema
|
|
850
|
+
fn_schema_serialized = {
|
|
851
|
+
"schema": (
|
|
852
|
+
fn_schema_cls.model_json_schema()
|
|
853
|
+
if hasattr(fn_schema_cls, "model_json_schema")
|
|
854
|
+
else None
|
|
855
|
+
),
|
|
856
|
+
"metadata": {
|
|
857
|
+
"module": fn_schema_cls.__module__,
|
|
858
|
+
"class": fn_schema_cls.__name__,
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
else:
|
|
862
|
+
fn_schema_serialized = None
|
|
863
|
+
|
|
683
864
|
tool_dict = {
|
|
684
865
|
"tool_type": tool.metadata.tool_type.value,
|
|
685
866
|
"name": tool.metadata.name,
|
|
686
867
|
"description": tool.metadata.description,
|
|
687
|
-
"fn": pickle.dumps(tool
|
|
688
|
-
"async_fn": pickle.dumps(tool
|
|
689
|
-
if tool
|
|
690
|
-
|
|
691
|
-
"fn_schema": tool.metadata.fn_schema.model_json_schema()
|
|
692
|
-
if hasattr(tool.metadata, "fn_schema")
|
|
693
|
-
else None, # Serialize schema if available
|
|
868
|
+
"fn": pickle.dumps(getattr(tool, 'fn', None)).decode("latin-1") if getattr(tool, 'fn', None) else None,
|
|
869
|
+
"async_fn": pickle.dumps(getattr(tool, 'async_fn', None)).decode("latin-1")
|
|
870
|
+
if getattr(tool, 'async_fn', None) else None,
|
|
871
|
+
"fn_schema": fn_schema_serialized,
|
|
694
872
|
}
|
|
695
873
|
tool_info.append(tool_dict)
|
|
696
874
|
|
|
697
875
|
return {
|
|
698
|
-
"agent_type": self.agent_type.value,
|
|
876
|
+
"agent_type": self.agent_config.agent_type.value,
|
|
699
877
|
"memory": pickle.dumps(self.agent.memory).decode("latin-1"),
|
|
700
878
|
"tools": tool_info,
|
|
701
879
|
"topic": self._topic,
|
|
702
880
|
"custom_instructions": self._custom_instructions,
|
|
703
881
|
"verbose": self.verbose,
|
|
704
882
|
"agent_config": self.agent_config.to_dict(),
|
|
883
|
+
"fallback_agent": self.fallback_agent_config.to_dict() if self.fallback_agent_config else None,
|
|
884
|
+
"workflow_cls": self.workflow_cls if self.workflow_cls else None,
|
|
705
885
|
}
|
|
706
886
|
|
|
707
887
|
@classmethod
|
|
708
888
|
def from_dict(cls, data: Dict[str, Any]) -> "Agent":
|
|
709
889
|
"""Create an Agent instance from a dictionary."""
|
|
710
890
|
agent_config = AgentConfig.from_dict(data["agent_config"])
|
|
891
|
+
fallback_agent_config = (
|
|
892
|
+
AgentConfig.from_dict(data["fallback_agent_config"])
|
|
893
|
+
if data.get("fallback_agent_config")
|
|
894
|
+
else None
|
|
895
|
+
)
|
|
711
896
|
tools = []
|
|
712
897
|
|
|
713
898
|
for tool_data in data["tools"]:
|
|
714
899
|
# Recreate the dynamic model using the schema info
|
|
715
900
|
if tool_data.get("fn_schema"):
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
901
|
+
schema_info = tool_data["fn_schema"]
|
|
902
|
+
try:
|
|
903
|
+
module_name = schema_info["metadata"]["module"]
|
|
904
|
+
class_name = schema_info["metadata"]["class"]
|
|
905
|
+
mod = importlib.import_module(module_name)
|
|
906
|
+
fn_schema_cls = getattr(mod, class_name)
|
|
907
|
+
query_args_model = fn_schema_cls
|
|
908
|
+
except Exception:
|
|
909
|
+
# Fallback: rebuild using the JSON schema
|
|
910
|
+
field_definitions = {}
|
|
911
|
+
for field, values in schema_info.get("schema", {}).get("properties", {}).items():
|
|
912
|
+
field_type = get_field_type(values)
|
|
913
|
+
if "default" in values:
|
|
914
|
+
field_definitions[field] = (
|
|
915
|
+
field_type,
|
|
916
|
+
Field(description=values.get("description", ""), default=values["default"]),
|
|
917
|
+
)
|
|
918
|
+
else:
|
|
919
|
+
field_definitions[field] = (
|
|
920
|
+
field_type,
|
|
921
|
+
Field(description=values.get("description", "")),
|
|
922
|
+
)
|
|
923
|
+
query_args_model = create_model(
|
|
924
|
+
schema_info.get("schema", {}).get("title", "QueryArgs"),
|
|
925
|
+
**field_definitions
|
|
926
|
+
)
|
|
732
927
|
else:
|
|
733
928
|
query_args_model = create_model("QueryArgs")
|
|
734
929
|
|
|
@@ -751,6 +946,8 @@ class Agent:
|
|
|
751
946
|
topic=data["topic"],
|
|
752
947
|
custom_instructions=data["custom_instructions"],
|
|
753
948
|
verbose=data["verbose"],
|
|
949
|
+
fallback_agent_config=fallback_agent_config,
|
|
950
|
+
workflow_cls=data["workflow_cls"],
|
|
754
951
|
)
|
|
755
952
|
memory = pickle.loads(data["memory"].encode("latin-1")) if data.get("memory") else None
|
|
756
953
|
if memory:
|