vectara-agentic 0.3.3__py3-none-any.whl → 0.4.0__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/__init__.py +7 -0
- tests/conftest.py +312 -0
- tests/endpoint.py +54 -17
- tests/run_tests.py +111 -0
- tests/test_agent.py +10 -5
- tests/test_agent_type.py +82 -143
- tests/test_api_endpoint.py +4 -0
- tests/test_bedrock.py +4 -0
- tests/test_fallback.py +4 -0
- tests/test_gemini.py +28 -45
- tests/test_groq.py +4 -0
- tests/test_private_llm.py +11 -2
- tests/test_return_direct.py +6 -2
- tests/test_serialization.py +4 -0
- tests/test_streaming.py +88 -0
- tests/test_tools.py +10 -82
- tests/test_vectara_llms.py +4 -0
- tests/test_vhc.py +66 -0
- tests/test_workflow.py +4 -0
- vectara_agentic/__init__.py +27 -4
- vectara_agentic/_callback.py +65 -67
- vectara_agentic/_observability.py +30 -30
- vectara_agentic/_version.py +1 -1
- vectara_agentic/agent.py +375 -848
- vectara_agentic/agent_config.py +15 -14
- vectara_agentic/agent_core/__init__.py +22 -0
- vectara_agentic/agent_core/factory.py +501 -0
- vectara_agentic/{_prompts.py → agent_core/prompts.py} +3 -35
- vectara_agentic/agent_core/serialization.py +345 -0
- vectara_agentic/agent_core/streaming.py +495 -0
- vectara_agentic/agent_core/utils/__init__.py +34 -0
- vectara_agentic/agent_core/utils/hallucination.py +202 -0
- vectara_agentic/agent_core/utils/logging.py +52 -0
- vectara_agentic/agent_core/utils/prompt_formatting.py +56 -0
- vectara_agentic/agent_core/utils/schemas.py +87 -0
- vectara_agentic/agent_core/utils/tools.py +125 -0
- vectara_agentic/agent_endpoint.py +4 -6
- vectara_agentic/db_tools.py +37 -12
- vectara_agentic/llm_utils.py +41 -42
- vectara_agentic/sub_query_workflow.py +9 -14
- vectara_agentic/tool_utils.py +138 -83
- vectara_agentic/tools.py +36 -21
- vectara_agentic/tools_catalog.py +16 -16
- vectara_agentic/types.py +98 -6
- {vectara_agentic-0.3.3.dist-info → vectara_agentic-0.4.0.dist-info}/METADATA +69 -30
- vectara_agentic-0.4.0.dist-info/RECORD +50 -0
- tests/test_agent_planning.py +0 -64
- tests/test_hhem.py +0 -100
- vectara_agentic/hhem.py +0 -82
- vectara_agentic-0.3.3.dist-info/RECORD +0 -39
- {vectara_agentic-0.3.3.dist-info → vectara_agentic-0.4.0.dist-info}/WHEEL +0 -0
- {vectara_agentic-0.3.3.dist-info → vectara_agentic-0.4.0.dist-info}/licenses/LICENSE +0 -0
- {vectara_agentic-0.3.3.dist-info → vectara_agentic-0.4.0.dist-info}/top_level.txt +0 -0
vectara_agentic/agent.py
CHANGED
|
@@ -2,85 +2,70 @@
|
|
|
2
2
|
This module contains the Agent class for handling different types of agents and their interactions.
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
import warnings
|
|
6
|
+
warnings.simplefilter("ignore", DeprecationWarning)
|
|
7
|
+
|
|
8
|
+
# pylint: disable=wrong-import-position
|
|
9
|
+
from typing import List, Callable, Optional, Dict, Any, Union, Tuple, TYPE_CHECKING
|
|
6
10
|
import os
|
|
7
|
-
import re
|
|
8
11
|
from datetime import date
|
|
9
|
-
import time
|
|
10
12
|
import json
|
|
11
13
|
import logging
|
|
12
14
|
import asyncio
|
|
13
|
-
import importlib
|
|
14
|
-
from collections import Counter
|
|
15
|
-
import inspect
|
|
16
|
-
from inspect import Signature, Parameter, ismethod
|
|
17
|
-
from pydantic import Field, create_model, ValidationError, BaseModel
|
|
18
|
-
from pydantic_core import PydanticUndefined
|
|
19
15
|
|
|
20
|
-
|
|
16
|
+
from pydantic import ValidationError
|
|
17
|
+
from pydantic_core import PydanticUndefined
|
|
21
18
|
|
|
22
19
|
from dotenv import load_dotenv
|
|
23
20
|
|
|
24
|
-
|
|
25
|
-
from llama_index.core.llms import
|
|
26
|
-
from llama_index.core.
|
|
27
|
-
from llama_index.core.
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
from llama_index.core.
|
|
33
|
-
from llama_index.agent.
|
|
34
|
-
from llama_index.agent.
|
|
35
|
-
from llama_index.core.callbacks import
|
|
36
|
-
|
|
37
|
-
from llama_index.agent.openai import OpenAIAgent
|
|
38
|
-
from llama_index.core.agent.runner.base import AgentRunner
|
|
39
|
-
from llama_index.core.agent.types import BaseAgent
|
|
40
|
-
from llama_index.core.workflow import Workflow, Context
|
|
21
|
+
# Runtime imports for components used at module level
|
|
22
|
+
from llama_index.core.llms import MessageRole
|
|
23
|
+
from llama_index.core.callbacks import CallbackManager
|
|
24
|
+
from llama_index.core.memory import Memory
|
|
25
|
+
|
|
26
|
+
# Heavy llama_index imports moved to TYPE_CHECKING for lazy loading
|
|
27
|
+
if TYPE_CHECKING:
|
|
28
|
+
from llama_index.core.tools import FunctionTool
|
|
29
|
+
from llama_index.core.workflow import Workflow
|
|
30
|
+
from llama_index.core.agent.runner.base import AgentRunner
|
|
31
|
+
from llama_index.core.agent.types import BaseAgent
|
|
32
|
+
from llama_index.core.callbacks.base_handler import BaseCallbackHandler
|
|
33
|
+
|
|
41
34
|
|
|
42
35
|
from .types import (
|
|
43
36
|
AgentType,
|
|
44
37
|
AgentStatusType,
|
|
45
38
|
LLMRole,
|
|
46
|
-
ToolType,
|
|
47
39
|
ModelProvider,
|
|
48
40
|
AgentResponse,
|
|
49
41
|
AgentStreamingResponse,
|
|
50
42
|
AgentConfigType,
|
|
51
43
|
)
|
|
52
|
-
from .llm_utils import get_llm
|
|
53
|
-
from .
|
|
54
|
-
REACT_PROMPT_TEMPLATE,
|
|
55
|
-
GENERAL_PROMPT_TEMPLATE,
|
|
56
|
-
GENERAL_INSTRUCTIONS,
|
|
57
|
-
STRUCTURED_PLANNER_PLAN_REFINE_PROMPT,
|
|
58
|
-
STRUCTURED_PLANNER_INITIAL_PLAN_PROMPT,
|
|
59
|
-
)
|
|
44
|
+
from .llm_utils import get_llm
|
|
45
|
+
from .agent_core.prompts import GENERAL_INSTRUCTIONS
|
|
60
46
|
from ._callback import AgentCallbackHandler
|
|
61
|
-
from ._observability import setup_observer
|
|
62
|
-
from .tools import
|
|
63
|
-
from .tool_utils import _is_human_readable_output
|
|
47
|
+
from ._observability import setup_observer
|
|
48
|
+
from .tools import ToolsFactory
|
|
64
49
|
from .tools_catalog import get_current_date
|
|
65
50
|
from .agent_config import AgentConfig
|
|
66
|
-
from .hhem import HHEM
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
class IgnoreUnpickleableAttributeFilter(logging.Filter):
|
|
70
|
-
"""
|
|
71
|
-
Filter to ignore log messages that contain certain strings
|
|
72
|
-
"""
|
|
73
|
-
|
|
74
|
-
def filter(self, record):
|
|
75
|
-
msgs_to_ignore = [
|
|
76
|
-
"Removing unpickleable private attribute _chunking_tokenizer_fn",
|
|
77
|
-
"Removing unpickleable private attribute _split_fns",
|
|
78
|
-
"Removing unpickleable private attribute _sub_sentence_split_fns",
|
|
79
|
-
]
|
|
80
|
-
return all(msg not in record.getMessage() for msg in msgs_to_ignore)
|
|
81
51
|
|
|
52
|
+
# Import utilities from agent core modules
|
|
53
|
+
from .agent_core.streaming import (
|
|
54
|
+
FunctionCallingStreamHandler,
|
|
55
|
+
execute_post_stream_processing,
|
|
56
|
+
)
|
|
57
|
+
from .agent_core.factory import create_agent_from_config, create_agent_from_corpus
|
|
58
|
+
from .agent_core.serialization import (
|
|
59
|
+
serialize_agent_to_dict,
|
|
60
|
+
deserialize_agent_from_dict,
|
|
61
|
+
)
|
|
62
|
+
from .agent_core.utils import (
|
|
63
|
+
sanitize_tools_for_gemini,
|
|
64
|
+
setup_agent_logging,
|
|
65
|
+
)
|
|
66
|
+
from .agent_core.utils.tools import validate_tool_consistency
|
|
82
67
|
|
|
83
|
-
|
|
68
|
+
setup_agent_logging()
|
|
84
69
|
|
|
85
70
|
logger = logging.getLogger("opentelemetry.exporter.otlp.proto.http.trace_exporter")
|
|
86
71
|
logger.setLevel(logging.CRITICAL)
|
|
@@ -88,113 +73,6 @@ logger.setLevel(logging.CRITICAL)
|
|
|
88
73
|
load_dotenv(override=True)
|
|
89
74
|
|
|
90
75
|
|
|
91
|
-
def _get_prompt(
|
|
92
|
-
prompt_template: str,
|
|
93
|
-
general_instructions: str,
|
|
94
|
-
topic: str,
|
|
95
|
-
custom_instructions: str,
|
|
96
|
-
):
|
|
97
|
-
"""
|
|
98
|
-
Generate a prompt by replacing placeholders with topic and date.
|
|
99
|
-
|
|
100
|
-
Args:
|
|
101
|
-
prompt_template (str): The template for the prompt.
|
|
102
|
-
general_instructions (str): General instructions to be included in the prompt.
|
|
103
|
-
topic (str): The topic to be included in the prompt.
|
|
104
|
-
custom_instructions(str): The custom instructions to be included in the prompt.
|
|
105
|
-
|
|
106
|
-
Returns:
|
|
107
|
-
str: The formatted prompt.
|
|
108
|
-
"""
|
|
109
|
-
return (
|
|
110
|
-
prompt_template.replace("{chat_topic}", topic)
|
|
111
|
-
.replace("{today}", date.today().strftime("%A, %B %d, %Y"))
|
|
112
|
-
.replace("{custom_instructions}", custom_instructions)
|
|
113
|
-
.replace("{INSTRUCTIONS}", general_instructions)
|
|
114
|
-
)
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
def _get_llm_compiler_prompt(
|
|
118
|
-
prompt: str, general_instructions: str, topic: str, custom_instructions: str
|
|
119
|
-
) -> str:
|
|
120
|
-
"""
|
|
121
|
-
Add custom instructions to the prompt.
|
|
122
|
-
|
|
123
|
-
Args:
|
|
124
|
-
prompt (str): The prompt to which custom instructions should be added.
|
|
125
|
-
|
|
126
|
-
Returns:
|
|
127
|
-
str: The prompt with custom instructions added.
|
|
128
|
-
"""
|
|
129
|
-
prompt += "\nAdditional Instructions:\n"
|
|
130
|
-
prompt += f"You have experise in {topic}.\n"
|
|
131
|
-
prompt += general_instructions
|
|
132
|
-
prompt += custom_instructions
|
|
133
|
-
prompt += f"Today is {date.today().strftime('%A, %B %d, %Y')}"
|
|
134
|
-
return prompt
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
def get_field_type(field_schema: dict) -> Any:
|
|
138
|
-
"""
|
|
139
|
-
Convert a JSON schema field definition to a Python type.
|
|
140
|
-
Handles 'type' and 'anyOf' cases.
|
|
141
|
-
"""
|
|
142
|
-
json_type_to_python = {
|
|
143
|
-
"string": str,
|
|
144
|
-
"integer": int,
|
|
145
|
-
"boolean": bool,
|
|
146
|
-
"array": list,
|
|
147
|
-
"object": dict,
|
|
148
|
-
"number": float,
|
|
149
|
-
"null": type(None),
|
|
150
|
-
}
|
|
151
|
-
if not field_schema: # Handles empty schema {}
|
|
152
|
-
return Any
|
|
153
|
-
|
|
154
|
-
if "anyOf" in field_schema:
|
|
155
|
-
types = []
|
|
156
|
-
for option_schema in field_schema["anyOf"]:
|
|
157
|
-
types.append(get_field_type(option_schema)) # Recursive call
|
|
158
|
-
if not types:
|
|
159
|
-
return Any
|
|
160
|
-
return Union[tuple(types)]
|
|
161
|
-
|
|
162
|
-
if "type" in field_schema and isinstance(field_schema["type"], list):
|
|
163
|
-
types = []
|
|
164
|
-
for type_name in field_schema["type"]:
|
|
165
|
-
if type_name == "array":
|
|
166
|
-
item_schema = field_schema.get("items", {})
|
|
167
|
-
types.append(List[get_field_type(item_schema)])
|
|
168
|
-
elif type_name in json_type_to_python:
|
|
169
|
-
types.append(json_type_to_python[type_name])
|
|
170
|
-
else:
|
|
171
|
-
types.append(Any) # Fallback for unknown types in the list
|
|
172
|
-
if not types:
|
|
173
|
-
return Any
|
|
174
|
-
return Union[tuple(types)] # type: ignore
|
|
175
|
-
|
|
176
|
-
if "type" in field_schema:
|
|
177
|
-
schema_type_name = field_schema["type"]
|
|
178
|
-
if schema_type_name == "array":
|
|
179
|
-
item_schema = field_schema.get(
|
|
180
|
-
"items", {}
|
|
181
|
-
) # Default to Any if "items" is missing
|
|
182
|
-
return List[get_field_type(item_schema)]
|
|
183
|
-
|
|
184
|
-
return json_type_to_python.get(schema_type_name, Any)
|
|
185
|
-
|
|
186
|
-
# If only "items" is present (implies array by some conventions, but less standard)
|
|
187
|
-
# Or if it's a schema with other keywords like 'properties' (implying object)
|
|
188
|
-
# For simplicity, if no "type" or "anyOf" at this point, default to Any or add more specific handling.
|
|
189
|
-
# If 'properties' in field_schema or 'additionalProperties' in field_schema, it's likely an object.
|
|
190
|
-
if "properties" in field_schema or "additionalProperties" in field_schema:
|
|
191
|
-
# This path might need to reconstruct a nested Pydantic model if you encounter such schemas.
|
|
192
|
-
# For now, treating as 'dict' or 'Any' might be a simpler placeholder.
|
|
193
|
-
return dict # Or Any, or more sophisticated object reconstruction.
|
|
194
|
-
|
|
195
|
-
return Any
|
|
196
|
-
|
|
197
|
-
|
|
198
76
|
class Agent:
|
|
199
77
|
"""
|
|
200
78
|
Agent class for handling different types of agents and their interactions.
|
|
@@ -202,13 +80,11 @@ class Agent:
|
|
|
202
80
|
|
|
203
81
|
def __init__(
|
|
204
82
|
self,
|
|
205
|
-
tools: List[FunctionTool],
|
|
83
|
+
tools: List["FunctionTool"],
|
|
206
84
|
topic: str = "general",
|
|
207
85
|
custom_instructions: str = "",
|
|
208
86
|
general_instructions: str = GENERAL_INSTRUCTIONS,
|
|
209
|
-
verbose: bool =
|
|
210
|
-
use_structured_planning: bool = False,
|
|
211
|
-
update_func: Optional[Callable[[AgentStatusType, dict, str], None]] = None,
|
|
87
|
+
verbose: bool = False,
|
|
212
88
|
agent_progress_callback: Optional[
|
|
213
89
|
Callable[[AgentStatusType, dict, str], None]
|
|
214
90
|
] = None,
|
|
@@ -217,7 +93,7 @@ class Agent:
|
|
|
217
93
|
fallback_agent_config: Optional[AgentConfig] = None,
|
|
218
94
|
chat_history: Optional[list[Tuple[str, str]]] = None,
|
|
219
95
|
validate_tools: bool = False,
|
|
220
|
-
workflow_cls: Optional[Workflow] = None,
|
|
96
|
+
workflow_cls: Optional["Workflow"] = None,
|
|
221
97
|
workflow_timeout: int = 120,
|
|
222
98
|
vectara_api_key: Optional[str] = None,
|
|
223
99
|
) -> None:
|
|
@@ -232,11 +108,8 @@ class Agent:
|
|
|
232
108
|
general_instructions (str, optional): General instructions for the agent.
|
|
233
109
|
The Agent has a default set of instructions that are crafted to help it operate effectively.
|
|
234
110
|
This allows you to customize the agent's behavior and personality, but use with caution.
|
|
235
|
-
verbose (bool, optional): Whether the agent should print its steps. Defaults to
|
|
236
|
-
use_structured_planning (bool, optional)
|
|
237
|
-
Whether or not we want to wrap the agent with LlamaIndex StructuredPlannerAgent.
|
|
111
|
+
verbose (bool, optional): Whether the agent should print its steps. Defaults to False.
|
|
238
112
|
agent_progress_callback (Callable): A callback function the code calls on any agent updates.
|
|
239
|
-
update_func (Callable): old name for agent_progress_callback. Will be deprecated in future.
|
|
240
113
|
query_logging_callback (Callable): A callback function the code calls upon completion of a query
|
|
241
114
|
agent_config (AgentConfig, optional): The configuration of the agent.
|
|
242
115
|
Defaults to AgentConfig(), which reads from environment variables.
|
|
@@ -253,97 +126,51 @@ class Agent:
|
|
|
253
126
|
self.agent_config_type = AgentConfigType.DEFAULT
|
|
254
127
|
self.tools = tools
|
|
255
128
|
if not any(tool.metadata.name == "get_current_date" for tool in self.tools):
|
|
256
|
-
self.tools += [
|
|
129
|
+
self.tools += [
|
|
130
|
+
ToolsFactory().create_tool(get_current_date, vhc_eligible=False)
|
|
131
|
+
]
|
|
257
132
|
self.agent_type = self.agent_config.agent_type
|
|
258
|
-
self.use_structured_planning = use_structured_planning
|
|
259
133
|
self._llm = None # Lazy loading
|
|
260
134
|
self._custom_instructions = custom_instructions
|
|
261
135
|
self._general_instructions = general_instructions
|
|
262
136
|
self._topic = topic
|
|
263
|
-
self.agent_progress_callback =
|
|
264
|
-
agent_progress_callback if agent_progress_callback else update_func
|
|
265
|
-
)
|
|
266
|
-
self.query_logging_callback = query_logging_callback
|
|
137
|
+
self.agent_progress_callback = agent_progress_callback
|
|
267
138
|
|
|
139
|
+
self.query_logging_callback = query_logging_callback
|
|
268
140
|
self.workflow_cls = workflow_cls
|
|
269
141
|
self.workflow_timeout = workflow_timeout
|
|
270
142
|
self.vectara_api_key = vectara_api_key or os.environ.get("VECTARA_API_KEY", "")
|
|
271
143
|
|
|
272
144
|
# Sanitize tools for Gemini if needed
|
|
273
145
|
if self.agent_config.main_llm_provider == ModelProvider.GEMINI:
|
|
274
|
-
self.tools =
|
|
146
|
+
self.tools = sanitize_tools_for_gemini(self.tools)
|
|
275
147
|
|
|
276
148
|
# Validate tools
|
|
277
|
-
# Check for:
|
|
278
|
-
# 1. multiple copies of the same tool
|
|
279
|
-
# 2. Instructions for using tools that do not exist
|
|
280
|
-
tool_names = [tool.metadata.name for tool in self.tools]
|
|
281
|
-
duplicates = [tool for tool, count in Counter(tool_names).items() if count > 1]
|
|
282
|
-
if duplicates:
|
|
283
|
-
raise ValueError(f"Duplicate tools detected: {', '.join(duplicates)}")
|
|
284
|
-
|
|
285
149
|
if validate_tools:
|
|
286
|
-
|
|
287
|
-
You are provided these tools:
|
|
288
|
-
<tools>{','.join(tool_names)}</tools>
|
|
289
|
-
And these instructions:
|
|
290
|
-
<instructions>
|
|
291
|
-
{self._custom_instructions}
|
|
292
|
-
</instructions>
|
|
293
|
-
Your task is to identify invalid tools.
|
|
294
|
-
A tool is invalid if it is mentioned in the instructions but not in the tools list.
|
|
295
|
-
A tool's name must have at least two characters.
|
|
296
|
-
Your response should be a comma-separated list of the invalid tools.
|
|
297
|
-
If no invalid tools exist, respond with "<OKAY>" (and nothing else).
|
|
298
|
-
"""
|
|
299
|
-
llm = get_llm(LLMRole.MAIN, config=self.agent_config)
|
|
300
|
-
bad_tools_str = llm.complete(prompt).text.strip("\n")
|
|
301
|
-
if bad_tools_str and bad_tools_str != "<OKAY>":
|
|
302
|
-
bad_tools = [tool.strip() for tool in bad_tools_str.split(",")]
|
|
303
|
-
numbered = ", ".join(
|
|
304
|
-
f"({i}) {tool}" for i, tool in enumerate(bad_tools, 1)
|
|
305
|
-
)
|
|
306
|
-
raise ValueError(
|
|
307
|
-
f"The Agent custom instructions mention these invalid tools: {numbered}"
|
|
308
|
-
)
|
|
309
|
-
|
|
310
|
-
# Create token counters for the main and tool LLMs
|
|
311
|
-
main_tok = get_tokenizer_for_model(role=LLMRole.MAIN)
|
|
312
|
-
self.main_token_counter = (
|
|
313
|
-
TokenCountingHandler(tokenizer=main_tok) if main_tok else None
|
|
314
|
-
)
|
|
315
|
-
tool_tok = get_tokenizer_for_model(role=LLMRole.TOOL)
|
|
316
|
-
self.tool_token_counter = (
|
|
317
|
-
TokenCountingHandler(tokenizer=tool_tok) if tool_tok else None
|
|
318
|
-
)
|
|
150
|
+
validate_tool_consistency(self.tools, self._custom_instructions, self.agent_config)
|
|
319
151
|
|
|
320
152
|
# Setup callback manager
|
|
321
153
|
callbacks: list[BaseCallbackHandler] = [
|
|
322
154
|
AgentCallbackHandler(self.agent_progress_callback)
|
|
323
155
|
]
|
|
324
|
-
if self.main_token_counter:
|
|
325
|
-
callbacks.append(self.main_token_counter)
|
|
326
|
-
if self.tool_token_counter:
|
|
327
|
-
callbacks.append(self.tool_token_counter)
|
|
328
156
|
self.callback_manager = CallbackManager(callbacks) # type: ignore
|
|
329
157
|
self.verbose = verbose
|
|
330
158
|
|
|
159
|
+
self.session_id = (
|
|
160
|
+
getattr(self, "session_id", None) or f"{topic}:{date.today().isoformat()}"
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
self.memory = Memory.from_defaults(
|
|
164
|
+
session_id=self.session_id, token_limit=65536
|
|
165
|
+
)
|
|
331
166
|
if chat_history:
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
)
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
content=text_pairs[1], role=MessageRole.ASSISTANT
|
|
340
|
-
)
|
|
341
|
-
)
|
|
342
|
-
self.memory = ChatMemoryBuffer.from_defaults(
|
|
343
|
-
token_limit=128000, chat_history=msg_history
|
|
344
|
-
)
|
|
345
|
-
else:
|
|
346
|
-
self.memory = ChatMemoryBuffer.from_defaults(token_limit=128000)
|
|
167
|
+
from llama_index.core.llms import ChatMessage
|
|
168
|
+
|
|
169
|
+
msgs = []
|
|
170
|
+
for u, a in chat_history:
|
|
171
|
+
msgs.append(ChatMessage.from_str(u, role=MessageRole.USER))
|
|
172
|
+
msgs.append(ChatMessage.from_str(a, role=MessageRole.ASSISTANT))
|
|
173
|
+
self.memory.put_messages(msgs)
|
|
347
174
|
|
|
348
175
|
# Set up main agent and fallback agent
|
|
349
176
|
self._agent = None # Lazy loading
|
|
@@ -354,7 +181,7 @@ class Agent:
|
|
|
354
181
|
try:
|
|
355
182
|
self.observability_enabled = setup_observer(self.agent_config, self.verbose)
|
|
356
183
|
except Exception as e:
|
|
357
|
-
|
|
184
|
+
logger.warning(f"Failed to set up observer ({e}), ignoring")
|
|
358
185
|
self.observability_enabled = False
|
|
359
186
|
|
|
360
187
|
@property
|
|
@@ -380,231 +207,57 @@ class Agent:
|
|
|
380
207
|
)
|
|
381
208
|
return self._fallback_agent
|
|
382
209
|
|
|
383
|
-
def _sanitize_tools_for_gemini(
|
|
384
|
-
self, tools: list[FunctionTool]
|
|
385
|
-
) -> list[FunctionTool]:
|
|
386
|
-
"""
|
|
387
|
-
Strip all default values from:
|
|
388
|
-
- tool.fn
|
|
389
|
-
- tool.async_fn
|
|
390
|
-
- tool.metadata.fn_schema
|
|
391
|
-
so Gemini sees *only* required parameters, no defaults.
|
|
392
|
-
"""
|
|
393
|
-
for tool in tools:
|
|
394
|
-
# 1) strip defaults off the actual callables
|
|
395
|
-
for func in (tool.fn, tool.async_fn):
|
|
396
|
-
if not func:
|
|
397
|
-
continue
|
|
398
|
-
orig_sig = inspect.signature(func)
|
|
399
|
-
new_params = [
|
|
400
|
-
p.replace(default=Parameter.empty)
|
|
401
|
-
for p in orig_sig.parameters.values()
|
|
402
|
-
]
|
|
403
|
-
new_sig = Signature(
|
|
404
|
-
new_params, return_annotation=orig_sig.return_annotation
|
|
405
|
-
)
|
|
406
|
-
if ismethod(func):
|
|
407
|
-
func.__func__.__signature__ = new_sig
|
|
408
|
-
else:
|
|
409
|
-
func.__signature__ = new_sig
|
|
410
|
-
|
|
411
|
-
# 2) rebuild the Pydantic schema so that *every* field is required
|
|
412
|
-
schema_cls = getattr(tool.metadata, "fn_schema", None)
|
|
413
|
-
if schema_cls and hasattr(schema_cls, "model_fields"):
|
|
414
|
-
# collect (name → (type, Field(...))) for all fields
|
|
415
|
-
new_fields: dict[str, tuple[type, Any]] = {}
|
|
416
|
-
for name, mf in schema_cls.model_fields.items():
|
|
417
|
-
typ = mf.annotation
|
|
418
|
-
desc = getattr(mf, "description", "")
|
|
419
|
-
# force required (no default) with Field(...)
|
|
420
|
-
new_fields[name] = (typ, Field(..., description=desc))
|
|
421
|
-
|
|
422
|
-
# make a brand-new schema class where every field is required
|
|
423
|
-
no_default_schema = create_model(
|
|
424
|
-
f"{schema_cls.__name__}", # new class name
|
|
425
|
-
**new_fields, # type: ignore
|
|
426
|
-
)
|
|
427
|
-
|
|
428
|
-
# give it a clean __signature__ so inspect.signature sees no defaults
|
|
429
|
-
params = [
|
|
430
|
-
Parameter(n, Parameter.POSITIONAL_OR_KEYWORD, annotation=typ)
|
|
431
|
-
for n, (typ, _) in new_fields.items()
|
|
432
|
-
]
|
|
433
|
-
no_default_schema.__signature__ = Signature(params)
|
|
434
|
-
|
|
435
|
-
# swap it back onto the tool
|
|
436
|
-
tool.metadata.fn_schema = no_default_schema
|
|
437
|
-
|
|
438
|
-
return tools
|
|
439
|
-
|
|
440
210
|
def _create_agent(
|
|
441
|
-
self, config: AgentConfig, llm_callback_manager: CallbackManager
|
|
442
|
-
) -> Union[BaseAgent, AgentRunner]:
|
|
211
|
+
self, config: AgentConfig, llm_callback_manager: "CallbackManager"
|
|
212
|
+
) -> Union["BaseAgent", "AgentRunner"]:
|
|
443
213
|
"""
|
|
444
214
|
Creates the agent based on the configuration object.
|
|
445
215
|
|
|
446
216
|
Args:
|
|
447
|
-
|
|
448
217
|
config: The configuration of the agent.
|
|
449
218
|
llm_callback_manager: The callback manager for the agent's llm.
|
|
450
219
|
|
|
451
220
|
Returns:
|
|
452
221
|
Union[BaseAgent, AgentRunner]: The configured agent object.
|
|
453
222
|
"""
|
|
454
|
-
agent_type = config.agent_type
|
|
455
223
|
# Use the same LLM instance for consistency
|
|
456
|
-
llm =
|
|
224
|
+
llm = (
|
|
225
|
+
self.llm
|
|
226
|
+
if config == self.agent_config
|
|
227
|
+
else get_llm(LLMRole.MAIN, config=config)
|
|
228
|
+
)
|
|
457
229
|
llm.callback_manager = llm_callback_manager
|
|
458
230
|
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
agent = FunctionCallingAgent.from_tools(
|
|
471
|
-
tools=self.tools,
|
|
472
|
-
llm=llm,
|
|
473
|
-
memory=self.memory,
|
|
474
|
-
verbose=self.verbose,
|
|
475
|
-
max_function_calls=config.max_reasoning_steps,
|
|
476
|
-
callback_manager=llm_callback_manager,
|
|
477
|
-
system_prompt=prompt,
|
|
478
|
-
allow_parallel_tool_calls=True,
|
|
479
|
-
)
|
|
480
|
-
elif agent_type == AgentType.REACT:
|
|
481
|
-
prompt = _get_prompt(
|
|
482
|
-
REACT_PROMPT_TEMPLATE,
|
|
483
|
-
self._general_instructions,
|
|
484
|
-
self._topic,
|
|
485
|
-
self._custom_instructions,
|
|
486
|
-
)
|
|
487
|
-
agent = ReActAgent.from_tools(
|
|
488
|
-
tools=self.tools,
|
|
489
|
-
llm=llm,
|
|
490
|
-
memory=self.memory,
|
|
491
|
-
verbose=self.verbose,
|
|
492
|
-
react_chat_formatter=ReActChatFormatter(system_header=prompt),
|
|
493
|
-
max_iterations=config.max_reasoning_steps,
|
|
494
|
-
callable_manager=llm_callback_manager,
|
|
495
|
-
)
|
|
496
|
-
elif agent_type == AgentType.OPENAI:
|
|
497
|
-
if config.tool_llm_provider != ModelProvider.OPENAI:
|
|
498
|
-
raise ValueError(
|
|
499
|
-
"Vectara-agentic: OPENAI agent type requires the OpenAI LLM."
|
|
500
|
-
)
|
|
501
|
-
prompt = _get_prompt(
|
|
502
|
-
GENERAL_PROMPT_TEMPLATE,
|
|
503
|
-
self._general_instructions,
|
|
504
|
-
self._topic,
|
|
505
|
-
self._custom_instructions,
|
|
506
|
-
)
|
|
507
|
-
agent = OpenAIAgent.from_tools(
|
|
508
|
-
tools=self.tools,
|
|
509
|
-
llm=llm,
|
|
510
|
-
memory=self.memory,
|
|
511
|
-
verbose=self.verbose,
|
|
512
|
-
callback_manager=llm_callback_manager,
|
|
513
|
-
max_function_calls=config.max_reasoning_steps,
|
|
514
|
-
system_prompt=prompt,
|
|
515
|
-
)
|
|
516
|
-
elif agent_type == AgentType.LLMCOMPILER:
|
|
517
|
-
agent_worker = LLMCompilerAgentWorker.from_tools(
|
|
518
|
-
tools=self.tools,
|
|
519
|
-
llm=llm,
|
|
520
|
-
verbose=self.verbose,
|
|
521
|
-
callback_manager=llm_callback_manager,
|
|
522
|
-
)
|
|
523
|
-
agent_worker.system_prompt = _get_prompt(
|
|
524
|
-
prompt_template=_get_llm_compiler_prompt(
|
|
525
|
-
prompt=agent_worker.system_prompt,
|
|
526
|
-
general_instructions=self._general_instructions,
|
|
527
|
-
topic=self._topic,
|
|
528
|
-
custom_instructions=self._custom_instructions,
|
|
529
|
-
),
|
|
530
|
-
general_instructions=self._general_instructions,
|
|
531
|
-
topic=self._topic,
|
|
532
|
-
custom_instructions=self._custom_instructions,
|
|
533
|
-
)
|
|
534
|
-
agent_worker.system_prompt_replan = _get_prompt(
|
|
535
|
-
prompt_template=_get_llm_compiler_prompt(
|
|
536
|
-
prompt=agent_worker.system_prompt_replan,
|
|
537
|
-
general_instructions=GENERAL_INSTRUCTIONS,
|
|
538
|
-
topic=self._topic,
|
|
539
|
-
custom_instructions=self._custom_instructions,
|
|
540
|
-
),
|
|
541
|
-
general_instructions=GENERAL_INSTRUCTIONS,
|
|
542
|
-
topic=self._topic,
|
|
543
|
-
custom_instructions=self._custom_instructions,
|
|
544
|
-
)
|
|
545
|
-
agent = agent_worker.as_agent()
|
|
546
|
-
elif agent_type == AgentType.LATS:
|
|
547
|
-
agent_worker = LATSAgentWorker.from_tools(
|
|
548
|
-
tools=self.tools,
|
|
549
|
-
llm=llm,
|
|
550
|
-
num_expansions=3,
|
|
551
|
-
max_rollouts=-1,
|
|
552
|
-
verbose=self.verbose,
|
|
553
|
-
callback_manager=llm_callback_manager,
|
|
554
|
-
)
|
|
555
|
-
prompt = _get_prompt(
|
|
556
|
-
REACT_PROMPT_TEMPLATE,
|
|
557
|
-
self._general_instructions,
|
|
558
|
-
self._topic,
|
|
559
|
-
self._custom_instructions,
|
|
560
|
-
)
|
|
561
|
-
agent_worker.chat_formatter = ReActChatFormatter(system_header=prompt)
|
|
562
|
-
agent = agent_worker.as_agent()
|
|
563
|
-
else:
|
|
564
|
-
raise ValueError(f"Unknown agent type: {agent_type}")
|
|
565
|
-
|
|
566
|
-
# Set up structured planner if needed
|
|
567
|
-
if self.use_structured_planning or self.agent_type in [
|
|
568
|
-
AgentType.LLMCOMPILER,
|
|
569
|
-
AgentType.LATS,
|
|
570
|
-
]:
|
|
571
|
-
planner_llm = get_llm(LLMRole.TOOL, config=config)
|
|
572
|
-
agent = StructuredPlannerAgent(
|
|
573
|
-
agent_worker=agent.agent_worker,
|
|
574
|
-
tools=self.tools,
|
|
575
|
-
llm=planner_llm,
|
|
576
|
-
memory=self.memory,
|
|
577
|
-
verbose=self.verbose,
|
|
578
|
-
initial_plan_prompt=STRUCTURED_PLANNER_INITIAL_PLAN_PROMPT,
|
|
579
|
-
plan_refine_prompt=STRUCTURED_PLANNER_PLAN_REFINE_PROMPT,
|
|
580
|
-
)
|
|
581
|
-
|
|
582
|
-
return agent
|
|
231
|
+
return create_agent_from_config(
|
|
232
|
+
tools=self.tools,
|
|
233
|
+
llm=llm,
|
|
234
|
+
memory=self.memory,
|
|
235
|
+
config=config,
|
|
236
|
+
callback_manager=llm_callback_manager,
|
|
237
|
+
general_instructions=self._general_instructions,
|
|
238
|
+
topic=self._topic,
|
|
239
|
+
custom_instructions=self._custom_instructions,
|
|
240
|
+
verbose=self.verbose,
|
|
241
|
+
)
|
|
583
242
|
|
|
584
243
|
def clear_memory(self) -> None:
|
|
585
|
-
"""
|
|
586
|
-
|
|
587
|
-
""
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
self.agent_config_type == AgentConfigType.FALLBACK
|
|
592
|
-
and self.fallback_agent_config
|
|
593
|
-
):
|
|
594
|
-
self.fallback_agent.memory.reset()
|
|
595
|
-
else:
|
|
596
|
-
raise ValueError(f"Invalid agent config type {self.agent_config_type}")
|
|
244
|
+
"""Clear the agent's memory."""
|
|
245
|
+
self.memory.reset()
|
|
246
|
+
if getattr(self, "_agent", None):
|
|
247
|
+
self._agent.memory = self.memory
|
|
248
|
+
if getattr(self, "_fallback_agent", None):
|
|
249
|
+
self._fallback_agent.memory = self.memory
|
|
597
250
|
|
|
598
251
|
def __eq__(self, other):
|
|
599
252
|
if not isinstance(other, Agent):
|
|
600
|
-
|
|
253
|
+
logger.debug(
|
|
601
254
|
f"Comparison failed: other is not an instance of Agent. (self: {type(self)}, other: {type(other)})"
|
|
602
255
|
)
|
|
603
256
|
return False
|
|
604
257
|
|
|
605
258
|
# Compare agent_type
|
|
606
259
|
if self.agent_config.agent_type != other.agent_config.agent_type:
|
|
607
|
-
|
|
260
|
+
logger.debug(
|
|
608
261
|
f"Comparison failed: agent_type differs. (self.agent_config.agent_type: {self.agent_config.agent_type},"
|
|
609
262
|
f" other.agent_config.agent_type: {other.agent_config.agent_type})"
|
|
610
263
|
)
|
|
@@ -612,7 +265,7 @@ class Agent:
|
|
|
612
265
|
|
|
613
266
|
# Compare tools
|
|
614
267
|
if self.tools != other.tools:
|
|
615
|
-
|
|
268
|
+
logger.debug(
|
|
616
269
|
"Comparison failed: tools differ."
|
|
617
270
|
f"(self.tools: {[t.metadata.name for t in self.tools]}, "
|
|
618
271
|
f"other.tools: {[t.metadata.name for t in other.tools]})"
|
|
@@ -621,14 +274,14 @@ class Agent:
|
|
|
621
274
|
|
|
622
275
|
# Compare topic
|
|
623
276
|
if self._topic != other._topic:
|
|
624
|
-
|
|
277
|
+
logger.debug(
|
|
625
278
|
f"Comparison failed: topic differs. (self.topic: {self._topic}, other.topic: {other._topic})"
|
|
626
279
|
)
|
|
627
280
|
return False
|
|
628
281
|
|
|
629
282
|
# Compare custom_instructions
|
|
630
283
|
if self._custom_instructions != other._custom_instructions:
|
|
631
|
-
|
|
284
|
+
logger.debug(
|
|
632
285
|
"Comparison failed: custom_instructions differ. (self.custom_instructions: "
|
|
633
286
|
f"{self._custom_instructions}, other.custom_instructions: {other._custom_instructions})"
|
|
634
287
|
)
|
|
@@ -636,31 +289,27 @@ class Agent:
|
|
|
636
289
|
|
|
637
290
|
# Compare verbose
|
|
638
291
|
if self.verbose != other.verbose:
|
|
639
|
-
|
|
292
|
+
logger.debug(
|
|
640
293
|
f"Comparison failed: verbose differs. (self.verbose: {self.verbose}, other.verbose: {other.verbose})"
|
|
641
294
|
)
|
|
642
295
|
return False
|
|
643
296
|
|
|
644
297
|
# Compare agent memory
|
|
645
|
-
if self.
|
|
646
|
-
|
|
647
|
-
f"Comparison failed: agent memory differs. (self.agent: {repr(self.agent.memory.chat_store)}, "
|
|
648
|
-
f"other.agent: {repr(other.agent.memory.chat_store)})"
|
|
649
|
-
)
|
|
298
|
+
if self.memory.get() != other.memory.get():
|
|
299
|
+
logger.debug("Comparison failed: agent memory differs.")
|
|
650
300
|
return False
|
|
651
301
|
|
|
652
302
|
# If all comparisons pass
|
|
653
|
-
|
|
303
|
+
logger.debug("All comparisons passed. Objects are equal.")
|
|
654
304
|
return True
|
|
655
305
|
|
|
656
306
|
@classmethod
|
|
657
307
|
def from_tools(
|
|
658
308
|
cls,
|
|
659
|
-
tools: List[FunctionTool],
|
|
309
|
+
tools: List["FunctionTool"],
|
|
660
310
|
topic: str = "general",
|
|
661
311
|
custom_instructions: str = "",
|
|
662
312
|
verbose: bool = True,
|
|
663
|
-
update_func: Optional[Callable[[AgentStatusType, dict, str], None]] = None,
|
|
664
313
|
agent_progress_callback: Optional[
|
|
665
314
|
Callable[[AgentStatusType, dict, str], None]
|
|
666
315
|
] = None,
|
|
@@ -669,7 +318,7 @@ class Agent:
|
|
|
669
318
|
validate_tools: bool = False,
|
|
670
319
|
fallback_agent_config: Optional[AgentConfig] = None,
|
|
671
320
|
chat_history: Optional[list[Tuple[str, str]]] = None,
|
|
672
|
-
workflow_cls: Optional[Workflow] = None,
|
|
321
|
+
workflow_cls: Optional["Workflow"] = None,
|
|
673
322
|
workflow_timeout: int = 120,
|
|
674
323
|
) -> "Agent":
|
|
675
324
|
"""
|
|
@@ -682,7 +331,6 @@ class Agent:
|
|
|
682
331
|
custom_instructions (str, optional): custom instructions for the agent. Defaults to ''.
|
|
683
332
|
verbose (bool, optional): Whether the agent should print its steps. Defaults to True.
|
|
684
333
|
agent_progress_callback (Callable): A callback function the code calls on any agent updates.
|
|
685
|
-
update_func (Callable): old name for agent_progress_callback. Will be deprecated in future.
|
|
686
334
|
query_logging_callback (Callable): A callback function the code calls upon completion of a query
|
|
687
335
|
agent_config (AgentConfig, optional): The configuration of the agent.
|
|
688
336
|
fallback_agent_config (AgentConfig, optional): The fallback configuration of the agent.
|
|
@@ -702,7 +350,6 @@ class Agent:
|
|
|
702
350
|
verbose=verbose,
|
|
703
351
|
agent_progress_callback=agent_progress_callback,
|
|
704
352
|
query_logging_callback=query_logging_callback,
|
|
705
|
-
update_func=update_func,
|
|
706
353
|
agent_config=agent_config,
|
|
707
354
|
chat_history=chat_history,
|
|
708
355
|
validate_tools=validate_tools,
|
|
@@ -754,133 +401,54 @@ class Agent:
|
|
|
754
401
|
vectara_save_history: bool = True,
|
|
755
402
|
return_direct: bool = False,
|
|
756
403
|
) -> "Agent":
|
|
757
|
-
"""
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
agent_progress_callback (Callable): A callback function the code calls on any agent updates.
|
|
765
|
-
query_logging_callback (Callable): A callback function the code calls upon completion of a query
|
|
766
|
-
agent_config (AgentConfig, optional): The configuration of the agent.
|
|
767
|
-
fallback_agent_config (AgentConfig, optional): The fallback configuration of the agent.
|
|
768
|
-
chat_history (Tuple[str, str], optional): A list of user/agent chat pairs to initialize the agent memory.
|
|
769
|
-
data_description (str): The description of the data.
|
|
770
|
-
assistant_specialty (str): The specialty of the assistant.
|
|
771
|
-
general_instructions (str, optional): General instructions for the agent.
|
|
772
|
-
The Agent has a default set of instructions that are crafted to help it operate effectively.
|
|
773
|
-
This allows you to customize the agent's behavior and personality, but use with caution.
|
|
774
|
-
verbose (bool, optional): Whether to print verbose output.
|
|
775
|
-
vectara_filter_fields (List[dict], optional): The filterable attributes
|
|
776
|
-
(each dict maps field name to Tuple[type, description]).
|
|
777
|
-
vectara_offset (int, optional): Number of results to skip.
|
|
778
|
-
vectara_lambda_val (float, optional): Lambda value for Vectara hybrid search.
|
|
779
|
-
vectara_semantics: (str, optional): Indicates whether the query is intended as a query or response.
|
|
780
|
-
vectara_custom_dimensions: (Dict, optional): Custom dimensions for the query.
|
|
781
|
-
vectara_reranker (str, optional): The Vectara reranker name (default "slingshot")
|
|
782
|
-
vectara_rerank_k (int, optional): The number of results to use with reranking.
|
|
783
|
-
vectara_rerank_limit: (int, optional): The maximum number of results to return after reranking.
|
|
784
|
-
vectara_rerank_cutoff: (float, optional): The minimum score threshold for results to include after
|
|
785
|
-
reranking.
|
|
786
|
-
vectara_diversity_bias (float, optional): The MMR diversity bias.
|
|
787
|
-
vectara_udf_expression (str, optional): The user defined expression for reranking results.
|
|
788
|
-
vectara_rerank_chain (List[Dict], optional): A list of Vectara rerankers to be applied sequentially.
|
|
789
|
-
vectara_n_sentences_before (int, optional): The number of sentences before the matching text
|
|
790
|
-
vectara_n_sentences_after (int, optional): The number of sentences after the matching text.
|
|
791
|
-
vectara_summary_num_results (int, optional): The number of results to use in summarization.
|
|
792
|
-
vectara_summarizer (str, optional): The Vectara summarizer name.
|
|
793
|
-
vectara_summary_response_language (str, optional): The response language for the Vectara summary.
|
|
794
|
-
vectara_summary_prompt_text (str, optional): The custom prompt, using appropriate prompt variables and
|
|
795
|
-
functions.
|
|
796
|
-
vectara_max_response_chars (int, optional): The desired maximum number of characters for the generated
|
|
797
|
-
summary.
|
|
798
|
-
vectara_max_tokens (int, optional): The maximum number of tokens to be returned by the LLM.
|
|
799
|
-
vectara_temperature (float, optional): The sampling temperature; higher values lead to more randomness.
|
|
800
|
-
vectara_frequency_penalty (float, optional): How much to penalize repeating tokens in the response,
|
|
801
|
-
higher values reducing likelihood of repeating the same line.
|
|
802
|
-
vectara_presence_penalty (float, optional): How much to penalize repeating tokens in the response,
|
|
803
|
-
higher values increasing the diversity of topics.
|
|
804
|
-
vectara_save_history (bool, optional): Whether to save the query in history.
|
|
805
|
-
return_direct (bool, optional): Whether the agent should return the tool's response directly.
|
|
806
|
-
|
|
807
|
-
Returns:
|
|
808
|
-
Agent: An instance of the Agent class.
|
|
809
|
-
"""
|
|
810
|
-
vec_factory = VectaraToolFactory(
|
|
811
|
-
vectara_api_key=vectara_api_key,
|
|
404
|
+
"""Create an agent from a single Vectara corpus using the factory function."""
|
|
405
|
+
# Use the factory function to avoid code duplication
|
|
406
|
+
config = create_agent_from_corpus(
|
|
407
|
+
tool_name=tool_name,
|
|
408
|
+
data_description=data_description,
|
|
409
|
+
assistant_specialty=assistant_specialty,
|
|
410
|
+
general_instructions=general_instructions,
|
|
812
411
|
vectara_corpus_key=vectara_corpus_key,
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
for field in vectara_filter_fields:
|
|
817
|
-
field_definitions[field["name"]] = (
|
|
818
|
-
eval(field["type"]),
|
|
819
|
-
Field(description=field["description"]),
|
|
820
|
-
) # type: ignore
|
|
821
|
-
query_args = create_model("QueryArgs", **field_definitions) # type: ignore
|
|
822
|
-
|
|
823
|
-
# tool name must be valid Python function name
|
|
824
|
-
if tool_name:
|
|
825
|
-
tool_name = re.sub(r"[^A-Za-z0-9_]", "_", tool_name)
|
|
826
|
-
|
|
827
|
-
vectara_tool = vec_factory.create_rag_tool(
|
|
828
|
-
tool_name=tool_name or f"vectara_{vectara_corpus_key}",
|
|
829
|
-
tool_description=f"""
|
|
830
|
-
Given a user query,
|
|
831
|
-
returns a response (str) to a user question about {data_description}.
|
|
832
|
-
""",
|
|
833
|
-
tool_args_schema=query_args,
|
|
834
|
-
reranker=vectara_reranker,
|
|
835
|
-
rerank_k=vectara_rerank_k,
|
|
836
|
-
rerank_limit=vectara_rerank_limit,
|
|
837
|
-
rerank_cutoff=vectara_rerank_cutoff,
|
|
838
|
-
mmr_diversity_bias=vectara_diversity_bias,
|
|
839
|
-
udf_expression=vectara_udf_expression,
|
|
840
|
-
rerank_chain=vectara_rerank_chain,
|
|
841
|
-
n_sentences_before=vectara_n_sentences_before,
|
|
842
|
-
n_sentences_after=vectara_n_sentences_after,
|
|
843
|
-
offset=vectara_offset,
|
|
844
|
-
lambda_val=vectara_lambda_val,
|
|
845
|
-
semantics=vectara_semantics,
|
|
846
|
-
custom_dimensions=vectara_custom_dimensions,
|
|
847
|
-
summary_num_results=vectara_summary_num_results,
|
|
848
|
-
vectara_summarizer=vectara_summarizer,
|
|
849
|
-
summary_response_lang=vectara_summary_response_language,
|
|
850
|
-
vectara_prompt_text=vectara_summary_prompt_text,
|
|
851
|
-
max_response_chars=vectara_max_response_chars,
|
|
852
|
-
max_tokens=vectara_max_tokens,
|
|
853
|
-
temperature=vectara_temperature,
|
|
854
|
-
frequency_penalty=vectara_frequency_penalty,
|
|
855
|
-
presence_penalty=vectara_presence_penalty,
|
|
856
|
-
save_history=vectara_save_history,
|
|
857
|
-
include_citations=True,
|
|
412
|
+
vectara_api_key=vectara_api_key,
|
|
413
|
+
agent_config=agent_config,
|
|
414
|
+
fallback_agent_config=fallback_agent_config,
|
|
858
415
|
verbose=verbose,
|
|
416
|
+
vectara_filter_fields=vectara_filter_fields,
|
|
417
|
+
vectara_offset=vectara_offset,
|
|
418
|
+
vectara_lambda_val=vectara_lambda_val,
|
|
419
|
+
vectara_semantics=vectara_semantics,
|
|
420
|
+
vectara_custom_dimensions=vectara_custom_dimensions,
|
|
421
|
+
vectara_reranker=vectara_reranker,
|
|
422
|
+
vectara_rerank_k=vectara_rerank_k,
|
|
423
|
+
vectara_rerank_limit=vectara_rerank_limit,
|
|
424
|
+
vectara_rerank_cutoff=vectara_rerank_cutoff,
|
|
425
|
+
vectara_diversity_bias=vectara_diversity_bias,
|
|
426
|
+
vectara_udf_expression=vectara_udf_expression,
|
|
427
|
+
vectara_rerank_chain=vectara_rerank_chain,
|
|
428
|
+
vectara_n_sentences_before=vectara_n_sentences_before,
|
|
429
|
+
vectara_n_sentences_after=vectara_n_sentences_after,
|
|
430
|
+
vectara_summary_num_results=vectara_summary_num_results,
|
|
431
|
+
vectara_summarizer=vectara_summarizer,
|
|
432
|
+
vectara_summary_response_language=vectara_summary_response_language,
|
|
433
|
+
vectara_summary_prompt_text=vectara_summary_prompt_text,
|
|
434
|
+
vectara_max_response_chars=vectara_max_response_chars,
|
|
435
|
+
vectara_max_tokens=vectara_max_tokens,
|
|
436
|
+
vectara_temperature=vectara_temperature,
|
|
437
|
+
vectara_frequency_penalty=vectara_frequency_penalty,
|
|
438
|
+
vectara_presence_penalty=vectara_presence_penalty,
|
|
439
|
+
vectara_save_history=vectara_save_history,
|
|
859
440
|
return_direct=return_direct,
|
|
860
441
|
)
|
|
861
442
|
|
|
862
|
-
assistant_instructions = f"""
|
|
863
|
-
- You are a helpful {assistant_specialty} assistant.
|
|
864
|
-
- You can answer questions about {data_description}.
|
|
865
|
-
- Never discuss politics, and always respond politely.
|
|
866
|
-
"""
|
|
867
|
-
|
|
868
443
|
return cls(
|
|
869
|
-
|
|
870
|
-
topic=assistant_specialty,
|
|
871
|
-
custom_instructions=assistant_instructions,
|
|
872
|
-
general_instructions=general_instructions,
|
|
873
|
-
verbose=verbose,
|
|
444
|
+
chat_history=chat_history,
|
|
874
445
|
agent_progress_callback=agent_progress_callback,
|
|
875
446
|
query_logging_callback=query_logging_callback,
|
|
876
|
-
|
|
877
|
-
fallback_agent_config=fallback_agent_config,
|
|
878
|
-
chat_history=chat_history,
|
|
879
|
-
vectara_api_key=vectara_api_key,
|
|
447
|
+
**config,
|
|
880
448
|
)
|
|
881
449
|
|
|
882
450
|
def _switch_agent_config(self) -> None:
|
|
883
|
-
"""
|
|
451
|
+
"""
|
|
884
452
|
Switch the configuration type of the agent.
|
|
885
453
|
This function is called automatically to switch the agent configuration if the current configuration fails.
|
|
886
454
|
"""
|
|
@@ -899,45 +467,25 @@ class Agent:
|
|
|
899
467
|
Returns:
|
|
900
468
|
str: The report from the agent.
|
|
901
469
|
"""
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
470
|
+
logger.info("Vectara agentic Report:")
|
|
471
|
+
logger.info(f"Agent Type = {self.agent_config.agent_type}")
|
|
472
|
+
logger.info(f"Topic = {self._topic}")
|
|
473
|
+
logger.info("Tools:")
|
|
906
474
|
for tool in self.tools:
|
|
907
475
|
if hasattr(tool, "metadata"):
|
|
908
476
|
if detailed:
|
|
909
|
-
|
|
477
|
+
logger.info(f"- {tool.metadata.description}")
|
|
910
478
|
else:
|
|
911
|
-
|
|
479
|
+
logger.info(f"- {tool.metadata.name}")
|
|
912
480
|
else:
|
|
913
|
-
|
|
914
|
-
|
|
481
|
+
logger.info("- tool without metadata")
|
|
482
|
+
logger.info(
|
|
915
483
|
f"Agent LLM = {get_llm(LLMRole.MAIN, config=self.agent_config).metadata.model_name}"
|
|
916
484
|
)
|
|
917
|
-
|
|
485
|
+
logger.info(
|
|
918
486
|
f"Tool LLM = {get_llm(LLMRole.TOOL, config=self.agent_config).metadata.model_name}"
|
|
919
487
|
)
|
|
920
488
|
|
|
921
|
-
def token_counts(self) -> dict:
|
|
922
|
-
"""
|
|
923
|
-
Get the token counts for the agent and tools.
|
|
924
|
-
|
|
925
|
-
Returns:
|
|
926
|
-
dict: The token counts for the agent and tools.
|
|
927
|
-
"""
|
|
928
|
-
return {
|
|
929
|
-
"main token count": (
|
|
930
|
-
self.main_token_counter.total_llm_token_count
|
|
931
|
-
if self.main_token_counter
|
|
932
|
-
else -1
|
|
933
|
-
),
|
|
934
|
-
"tool token count": (
|
|
935
|
-
self.tool_token_counter.total_llm_token_count
|
|
936
|
-
if self.tool_token_counter
|
|
937
|
-
else -1
|
|
938
|
-
),
|
|
939
|
-
}
|
|
940
|
-
|
|
941
489
|
def _get_current_agent(self):
|
|
942
490
|
return (
|
|
943
491
|
self.agent
|
|
@@ -949,6 +497,7 @@ class Agent:
|
|
|
949
497
|
return (
|
|
950
498
|
self.agent_config.agent_type
|
|
951
499
|
if self.agent_config_type == AgentConfigType.DEFAULT
|
|
500
|
+
or not self.fallback_agent_config
|
|
952
501
|
else self.fallback_agent_config.agent_type
|
|
953
502
|
)
|
|
954
503
|
|
|
@@ -963,9 +512,9 @@ class Agent:
|
|
|
963
512
|
return
|
|
964
513
|
|
|
965
514
|
agent = self._get_current_agent()
|
|
966
|
-
agent_response.response =
|
|
515
|
+
agent_response.response = (await agent.llm.acomplete(llm_prompt)).text
|
|
967
516
|
|
|
968
|
-
def chat(self, prompt: str) -> AgentResponse:
|
|
517
|
+
def chat(self, prompt: str) -> AgentResponse:
|
|
969
518
|
"""
|
|
970
519
|
Interact with the agent using a chat prompt.
|
|
971
520
|
|
|
@@ -975,48 +524,15 @@ class Agent:
|
|
|
975
524
|
Returns:
|
|
976
525
|
AgentResponse: The response from the agent.
|
|
977
526
|
"""
|
|
978
|
-
return asyncio.run(self.achat(prompt))
|
|
979
|
-
|
|
980
|
-
def _calc_fcs(self, agent_response: AgentResponse) -> None:
|
|
981
|
-
"""
|
|
982
|
-
Calculate the Factual consistency score for the agent response.
|
|
983
|
-
"""
|
|
984
|
-
if not self.vectara_api_key:
|
|
985
|
-
logging.debug("FCS calculation skipped: 'vectara_api_key' is missing.")
|
|
986
|
-
return # can't calculate FCS without Vectara API key
|
|
987
|
-
|
|
988
|
-
chat_history = self.memory.get()
|
|
989
|
-
context = []
|
|
990
|
-
for msg in chat_history:
|
|
991
|
-
if msg.role == MessageRole.TOOL:
|
|
992
|
-
content = msg.content
|
|
993
|
-
if _is_human_readable_output(content):
|
|
994
|
-
try:
|
|
995
|
-
content = content.to_human_readable()
|
|
996
|
-
except Exception as e:
|
|
997
|
-
logging.debug(
|
|
998
|
-
f"Failed to get human-readable format for FCS calculation: {e}"
|
|
999
|
-
)
|
|
1000
|
-
# Fall back to string representation of the object
|
|
1001
|
-
content = str(content)
|
|
1002
|
-
|
|
1003
|
-
context.append(content)
|
|
1004
|
-
elif msg.role in [MessageRole.USER, MessageRole.ASSISTANT] and msg.content:
|
|
1005
|
-
context.append(msg.content)
|
|
1006
|
-
|
|
1007
|
-
if not context:
|
|
1008
|
-
return
|
|
1009
|
-
|
|
1010
|
-
context_str = "\n".join(context)
|
|
1011
527
|
try:
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
)
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
528
|
+
_ = asyncio.get_running_loop()
|
|
529
|
+
except RuntimeError:
|
|
530
|
+
return asyncio.run(self.achat(prompt))
|
|
531
|
+
|
|
532
|
+
# We are inside a running loop (Jupyter, uvicorn, etc.)
|
|
533
|
+
raise RuntimeError(
|
|
534
|
+
"Use `await agent.achat(...)` inside an event loop (e.g. Jupyter)."
|
|
535
|
+
)
|
|
1020
536
|
|
|
1021
537
|
async def achat(self, prompt: str) -> AgentResponse: # type: ignore
|
|
1022
538
|
"""
|
|
@@ -1028,6 +544,9 @@ class Agent:
|
|
|
1028
544
|
Returns:
|
|
1029
545
|
AgentResponse: The response from the agent.
|
|
1030
546
|
"""
|
|
547
|
+
if not prompt:
|
|
548
|
+
return AgentResponse(response="")
|
|
549
|
+
|
|
1031
550
|
max_attempts = 4 if self.fallback_agent_config else 2
|
|
1032
551
|
attempt = 0
|
|
1033
552
|
orig_llm = self.llm.metadata.model_name
|
|
@@ -1035,36 +554,179 @@ class Agent:
|
|
|
1035
554
|
while attempt < max_attempts:
|
|
1036
555
|
try:
|
|
1037
556
|
current_agent = self._get_current_agent()
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
557
|
+
|
|
558
|
+
# Deal with workflow-based agent types (Function Calling and ReAct)
|
|
559
|
+
if self._get_current_agent_type() in [
|
|
560
|
+
AgentType.FUNCTION_CALLING,
|
|
561
|
+
AgentType.REACT,
|
|
562
|
+
]:
|
|
563
|
+
from llama_index.core.workflow import Context
|
|
564
|
+
|
|
565
|
+
ctx = Context(current_agent)
|
|
566
|
+
handler = current_agent.run(
|
|
567
|
+
user_msg=prompt, ctx=ctx, memory=self.memory
|
|
568
|
+
)
|
|
569
|
+
|
|
570
|
+
# Listen to workflow events if progress callback is set
|
|
571
|
+
if self.agent_progress_callback:
|
|
572
|
+
# Create event tracker for consistent event ID generation
|
|
573
|
+
from .agent_core.streaming import ToolEventTracker
|
|
574
|
+
|
|
575
|
+
event_tracker = ToolEventTracker()
|
|
576
|
+
|
|
577
|
+
async for event in handler.stream_events():
|
|
578
|
+
# Use consistent event ID tracking to ensure tool calls and outputs are paired
|
|
579
|
+
event_id = event_tracker.get_event_id(event)
|
|
580
|
+
|
|
581
|
+
# Handle different types of workflow events using same logic as FunctionCallingStreamHandler
|
|
582
|
+
from llama_index.core.agent.workflow import (
|
|
583
|
+
ToolCall,
|
|
584
|
+
ToolCallResult,
|
|
585
|
+
AgentInput,
|
|
586
|
+
AgentOutput,
|
|
587
|
+
)
|
|
588
|
+
|
|
589
|
+
if isinstance(event, ToolCall):
|
|
590
|
+
self.agent_progress_callback(
|
|
591
|
+
status_type=AgentStatusType.TOOL_CALL,
|
|
592
|
+
msg={
|
|
593
|
+
"tool_name": event.tool_name,
|
|
594
|
+
"arguments": json.dumps(event.tool_kwargs),
|
|
595
|
+
},
|
|
596
|
+
event_id=event_id,
|
|
597
|
+
)
|
|
598
|
+
elif isinstance(event, ToolCallResult):
|
|
599
|
+
self.agent_progress_callback(
|
|
600
|
+
status_type=AgentStatusType.TOOL_OUTPUT,
|
|
601
|
+
msg={
|
|
602
|
+
"tool_name": event.tool_name,
|
|
603
|
+
"content": str(event.tool_output),
|
|
604
|
+
},
|
|
605
|
+
event_id=event_id,
|
|
606
|
+
)
|
|
607
|
+
elif isinstance(event, AgentInput):
|
|
608
|
+
self.agent_progress_callback(
|
|
609
|
+
status_type=AgentStatusType.AGENT_UPDATE,
|
|
610
|
+
msg={"content": f"Agent input: {event.input}"},
|
|
611
|
+
event_id=event_id,
|
|
612
|
+
)
|
|
613
|
+
elif isinstance(event, AgentOutput):
|
|
614
|
+
self.agent_progress_callback(
|
|
615
|
+
status_type=AgentStatusType.AGENT_UPDATE,
|
|
616
|
+
msg={"content": f"Agent output: {event.response}"},
|
|
617
|
+
event_id=event_id,
|
|
618
|
+
)
|
|
619
|
+
|
|
620
|
+
result = await handler
|
|
621
|
+
|
|
622
|
+
# Ensure we have an AgentResponse object with a string response
|
|
623
|
+
if hasattr(result, "response"):
|
|
624
|
+
response_text = result.response
|
|
625
|
+
else:
|
|
626
|
+
response_text = str(result)
|
|
627
|
+
|
|
628
|
+
# Handle case where response is a ChatMessage object
|
|
629
|
+
if hasattr(response_text, "content"):
|
|
630
|
+
response_text = response_text.content
|
|
631
|
+
elif not isinstance(response_text, str):
|
|
632
|
+
response_text = str(response_text)
|
|
633
|
+
|
|
634
|
+
if response_text is None or response_text == "None":
|
|
635
|
+
# Try to find tool outputs in the result object
|
|
636
|
+
response_text = None
|
|
637
|
+
|
|
638
|
+
# Check various possible locations for tool outputs
|
|
639
|
+
if hasattr(result, "tool_outputs") and result.tool_outputs:
|
|
640
|
+
# Get the latest tool output
|
|
641
|
+
latest_output = (
|
|
642
|
+
result.tool_outputs[-1]
|
|
643
|
+
if isinstance(result.tool_outputs, list)
|
|
644
|
+
else result.tool_outputs
|
|
645
|
+
)
|
|
646
|
+
response_text = str(latest_output)
|
|
647
|
+
|
|
648
|
+
# Check if there are tool_calls with results
|
|
649
|
+
elif hasattr(result, "tool_calls") and result.tool_calls:
|
|
650
|
+
# Tool calls might contain the outputs - let's try to extract them
|
|
651
|
+
for tool_call in result.tool_calls:
|
|
652
|
+
if (
|
|
653
|
+
hasattr(tool_call, "tool_output")
|
|
654
|
+
and tool_call.tool_output is not None
|
|
655
|
+
):
|
|
656
|
+
response_text = str(tool_call.tool_output)
|
|
657
|
+
break
|
|
658
|
+
|
|
659
|
+
elif hasattr(result, "sources") or hasattr(
|
|
660
|
+
result, "source_nodes"
|
|
661
|
+
):
|
|
662
|
+
sources = getattr(
|
|
663
|
+
result, "sources", getattr(result, "source_nodes", [])
|
|
664
|
+
)
|
|
665
|
+
if (
|
|
666
|
+
sources
|
|
667
|
+
and len(sources) > 0
|
|
668
|
+
and hasattr(sources[0], "text")
|
|
669
|
+
):
|
|
670
|
+
response_text = sources[0].text
|
|
671
|
+
|
|
672
|
+
# Check for workflow context or chat history that might contain tool results
|
|
673
|
+
elif hasattr(result, "chat_history"):
|
|
674
|
+
# Look for the most recent assistant message that might contain tool results
|
|
675
|
+
chat_history = result.chat_history
|
|
676
|
+
if chat_history and len(chat_history) > 0:
|
|
677
|
+
for msg in reversed(chat_history):
|
|
678
|
+
if (
|
|
679
|
+
msg.role == MessageRole.TOOL
|
|
680
|
+
and msg.content
|
|
681
|
+
and str(msg.content).strip()
|
|
682
|
+
):
|
|
683
|
+
response_text = msg.content
|
|
684
|
+
break
|
|
685
|
+
if (
|
|
686
|
+
hasattr(msg, "content")
|
|
687
|
+
and msg.content
|
|
688
|
+
and str(msg.content).strip()
|
|
689
|
+
):
|
|
690
|
+
response_text = msg.content
|
|
691
|
+
break
|
|
692
|
+
|
|
693
|
+
# If we still don't have a response, provide a fallback
|
|
694
|
+
if response_text is None or response_text == "None":
|
|
695
|
+
response_text = "Response completed."
|
|
696
|
+
|
|
697
|
+
agent_response = AgentResponse(
|
|
698
|
+
response=response_text, metadata=getattr(result, "metadata", {})
|
|
699
|
+
)
|
|
700
|
+
|
|
701
|
+
# Standard chat interaction for other agent types
|
|
702
|
+
else:
|
|
703
|
+
agent_response = await current_agent.achat(prompt)
|
|
704
|
+
|
|
705
|
+
# Post processing after response is generated
|
|
706
|
+
agent_response.metadata = agent_response.metadata or {}
|
|
707
|
+
user_metadata = agent_response.metadata
|
|
708
|
+
agent_response = await execute_post_stream_processing(
|
|
709
|
+
agent_response, prompt, self, user_metadata
|
|
710
|
+
)
|
|
1045
711
|
return agent_response
|
|
1046
712
|
|
|
1047
713
|
except Exception as e:
|
|
1048
714
|
last_error = e
|
|
1049
715
|
if self.verbose:
|
|
1050
|
-
|
|
1051
|
-
if attempt >= 2:
|
|
1052
|
-
if self.verbose:
|
|
1053
|
-
print(
|
|
1054
|
-
f"LLM call failed on attempt {attempt}. Switching agent configuration."
|
|
1055
|
-
)
|
|
716
|
+
logger.warning(f"LLM call failed on attempt {attempt}. " f"Error: {e}.")
|
|
717
|
+
if attempt >= 2 and self.fallback_agent_config:
|
|
1056
718
|
self._switch_agent_config()
|
|
1057
|
-
|
|
719
|
+
await asyncio.sleep(1)
|
|
1058
720
|
attempt += 1
|
|
1059
721
|
|
|
1060
722
|
return AgentResponse(
|
|
1061
723
|
response=(
|
|
1062
724
|
f"For {orig_llm} LLM - failure can't be resolved after "
|
|
1063
|
-
f"{max_attempts} attempts ({last_error}."
|
|
725
|
+
f"{max_attempts} attempts ({last_error})."
|
|
1064
726
|
)
|
|
1065
727
|
)
|
|
1066
728
|
|
|
1067
|
-
def stream_chat(self, prompt: str) -> AgentStreamingResponse:
|
|
729
|
+
def stream_chat(self, prompt: str) -> AgentStreamingResponse:
|
|
1068
730
|
"""
|
|
1069
731
|
Interact with the agent using a chat prompt with streaming.
|
|
1070
732
|
Args:
|
|
@@ -1072,7 +734,13 @@ class Agent:
|
|
|
1072
734
|
Returns:
|
|
1073
735
|
AgentStreamingResponse: The streaming response from the agent.
|
|
1074
736
|
"""
|
|
1075
|
-
|
|
737
|
+
try:
|
|
738
|
+
_ = asyncio.get_running_loop()
|
|
739
|
+
except RuntimeError:
|
|
740
|
+
return asyncio.run(self.astream_chat(prompt))
|
|
741
|
+
raise RuntimeError(
|
|
742
|
+
"Use `await agent.astream_chat(...)` inside an event loop (e.g. Jupyter)."
|
|
743
|
+
)
|
|
1076
744
|
|
|
1077
745
|
async def astream_chat(self, prompt: str) -> AgentStreamingResponse: # type: ignore
|
|
1078
746
|
"""
|
|
@@ -1085,45 +753,60 @@ class Agent:
|
|
|
1085
753
|
max_attempts = 4 if self.fallback_agent_config else 2
|
|
1086
754
|
attempt = 0
|
|
1087
755
|
orig_llm = self.llm.metadata.model_name
|
|
756
|
+
last_error = None
|
|
1088
757
|
while attempt < max_attempts:
|
|
1089
758
|
try:
|
|
1090
759
|
current_agent = self._get_current_agent()
|
|
1091
|
-
|
|
1092
|
-
|
|
760
|
+
user_meta: Dict[str, Any] = {}
|
|
761
|
+
|
|
762
|
+
# Deal with Function Calling agent type
|
|
763
|
+
if self._get_current_agent_type() == AgentType.FUNCTION_CALLING:
|
|
764
|
+
from llama_index.core.workflow import Context
|
|
765
|
+
|
|
766
|
+
ctx = Context(current_agent)
|
|
767
|
+
handler = current_agent.run(
|
|
768
|
+
user_msg=prompt, ctx=ctx, memory=self.memory
|
|
769
|
+
)
|
|
770
|
+
|
|
771
|
+
# Use the dedicated FunctionCallingStreamHandler
|
|
772
|
+
stream_handler = FunctionCallingStreamHandler(self, handler, prompt)
|
|
773
|
+
streaming_adapter = stream_handler.create_streaming_response(
|
|
774
|
+
user_meta
|
|
775
|
+
)
|
|
776
|
+
|
|
777
|
+
return AgentStreamingResponse(
|
|
778
|
+
base=streaming_adapter, metadata=user_meta
|
|
779
|
+
)
|
|
780
|
+
|
|
781
|
+
#
|
|
782
|
+
# For other agent types, use the standard async chat method
|
|
783
|
+
#
|
|
784
|
+
li_stream = await current_agent.astream_chat(prompt)
|
|
785
|
+
orig_async = li_stream.async_response_gen
|
|
1093
786
|
|
|
1094
787
|
# Define a wrapper to preserve streaming behavior while executing post-stream logic.
|
|
1095
788
|
async def _stream_response_wrapper():
|
|
1096
|
-
async for
|
|
1097
|
-
yield
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
agent_response.async_response_gen = (
|
|
1107
|
-
_stream_response_wrapper # Override the generator
|
|
1108
|
-
)
|
|
1109
|
-
return agent_response
|
|
789
|
+
async for tok in orig_async():
|
|
790
|
+
yield tok
|
|
791
|
+
|
|
792
|
+
# Use shared post-processing function
|
|
793
|
+
await execute_post_stream_processing(
|
|
794
|
+
li_stream, prompt, self, user_meta
|
|
795
|
+
)
|
|
796
|
+
|
|
797
|
+
li_stream.async_response_gen = _stream_response_wrapper
|
|
798
|
+
return AgentStreamingResponse(base=li_stream, metadata=user_meta)
|
|
1110
799
|
|
|
1111
800
|
except Exception as e:
|
|
1112
801
|
last_error = e
|
|
1113
|
-
if attempt >= 2:
|
|
1114
|
-
if self.verbose:
|
|
1115
|
-
print(
|
|
1116
|
-
f"LLM call failed on attempt {attempt}. Switching agent configuration."
|
|
1117
|
-
)
|
|
802
|
+
if attempt >= 2 and self.fallback_agent_config:
|
|
1118
803
|
self._switch_agent_config()
|
|
1119
|
-
|
|
804
|
+
await asyncio.sleep(1)
|
|
1120
805
|
attempt += 1
|
|
1121
806
|
|
|
1122
|
-
return AgentStreamingResponse(
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
f"{max_attempts} attempts ({last_error})."
|
|
1126
|
-
)
|
|
807
|
+
return AgentStreamingResponse.from_error(
|
|
808
|
+
f"For {orig_llm} LLM - failure can't be resolved after "
|
|
809
|
+
f"{max_attempts} attempts ({last_error})."
|
|
1127
810
|
)
|
|
1128
811
|
|
|
1129
812
|
#
|
|
@@ -1168,6 +851,8 @@ class Agent:
|
|
|
1168
851
|
f"Fields without default values: {fields_without_default}"
|
|
1169
852
|
)
|
|
1170
853
|
|
|
854
|
+
from llama_index.core.workflow import Context
|
|
855
|
+
|
|
1171
856
|
workflow_context = Context(workflow=workflow)
|
|
1172
857
|
try:
|
|
1173
858
|
# run workflow
|
|
@@ -1197,7 +882,7 @@ class Agent:
|
|
|
1197
882
|
input_dict[key] = value
|
|
1198
883
|
output = outputs_model_on_fail_cls.model_validate(input_dict)
|
|
1199
884
|
else:
|
|
1200
|
-
|
|
885
|
+
logger.warning(f"Vectara Agentic: Workflow failed with unexpected error: {e}")
|
|
1201
886
|
raise type(e)(str(e)).with_traceback(e.__traceback__)
|
|
1202
887
|
|
|
1203
888
|
return output
|
|
@@ -1225,57 +910,7 @@ class Agent:
|
|
|
1225
910
|
|
|
1226
911
|
def to_dict(self) -> Dict[str, Any]:
|
|
1227
912
|
"""Serialize the Agent instance to a dictionary."""
|
|
1228
|
-
|
|
1229
|
-
for tool in self.tools:
|
|
1230
|
-
if hasattr(tool.metadata, "fn_schema"):
|
|
1231
|
-
fn_schema_cls = tool.metadata.fn_schema
|
|
1232
|
-
fn_schema_serialized = {
|
|
1233
|
-
"schema": (
|
|
1234
|
-
fn_schema_cls.model_json_schema()
|
|
1235
|
-
if fn_schema_cls and hasattr(fn_schema_cls, "model_json_schema")
|
|
1236
|
-
else None
|
|
1237
|
-
),
|
|
1238
|
-
"metadata": {
|
|
1239
|
-
"module": fn_schema_cls.__module__ if fn_schema_cls else None,
|
|
1240
|
-
"class": fn_schema_cls.__name__ if fn_schema_cls else None,
|
|
1241
|
-
},
|
|
1242
|
-
}
|
|
1243
|
-
else:
|
|
1244
|
-
fn_schema_serialized = None
|
|
1245
|
-
|
|
1246
|
-
tool_dict = {
|
|
1247
|
-
"tool_type": tool.metadata.tool_type.value,
|
|
1248
|
-
"name": tool.metadata.name,
|
|
1249
|
-
"description": tool.metadata.description,
|
|
1250
|
-
"fn": (
|
|
1251
|
-
pickle.dumps(getattr(tool, "fn", None)).decode("latin-1")
|
|
1252
|
-
if getattr(tool, "fn", None)
|
|
1253
|
-
else None
|
|
1254
|
-
),
|
|
1255
|
-
"async_fn": (
|
|
1256
|
-
pickle.dumps(getattr(tool, "async_fn", None)).decode("latin-1")
|
|
1257
|
-
if getattr(tool, "async_fn", None)
|
|
1258
|
-
else None
|
|
1259
|
-
),
|
|
1260
|
-
"fn_schema": fn_schema_serialized,
|
|
1261
|
-
}
|
|
1262
|
-
tool_info.append(tool_dict)
|
|
1263
|
-
|
|
1264
|
-
return {
|
|
1265
|
-
"agent_type": self.agent_config.agent_type.value,
|
|
1266
|
-
"memory": pickle.dumps(self.agent.memory).decode("latin-1"),
|
|
1267
|
-
"tools": tool_info,
|
|
1268
|
-
"topic": self._topic,
|
|
1269
|
-
"custom_instructions": self._custom_instructions,
|
|
1270
|
-
"verbose": self.verbose,
|
|
1271
|
-
"agent_config": self.agent_config.to_dict(),
|
|
1272
|
-
"fallback_agent": (
|
|
1273
|
-
self.fallback_agent_config.to_dict()
|
|
1274
|
-
if self.fallback_agent_config
|
|
1275
|
-
else None
|
|
1276
|
-
),
|
|
1277
|
-
"workflow_cls": self.workflow_cls if self.workflow_cls else None,
|
|
1278
|
-
}
|
|
913
|
+
return serialize_agent_to_dict(self)
|
|
1279
914
|
|
|
1280
915
|
@classmethod
|
|
1281
916
|
def from_dict(
|
|
@@ -1285,114 +920,6 @@ class Agent:
|
|
|
1285
920
|
query_logging_callback: Optional[Callable] = None,
|
|
1286
921
|
) -> "Agent":
|
|
1287
922
|
"""Create an Agent instance from a dictionary."""
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
AgentConfig.from_dict(data["fallback_agent_config"])
|
|
1291
|
-
if data.get("fallback_agent_config")
|
|
1292
|
-
else None
|
|
1293
|
-
)
|
|
1294
|
-
tools: list[FunctionTool] = []
|
|
1295
|
-
|
|
1296
|
-
for tool_data in data["tools"]:
|
|
1297
|
-
query_args_model = None
|
|
1298
|
-
if tool_data.get("fn_schema"):
|
|
1299
|
-
schema_info = tool_data["fn_schema"]
|
|
1300
|
-
try:
|
|
1301
|
-
module_name = schema_info["metadata"]["module"]
|
|
1302
|
-
class_name = schema_info["metadata"]["class"]
|
|
1303
|
-
mod = importlib.import_module(module_name)
|
|
1304
|
-
candidate_cls = getattr(mod, class_name)
|
|
1305
|
-
if inspect.isclass(candidate_cls) and issubclass(
|
|
1306
|
-
candidate_cls, BaseModel
|
|
1307
|
-
):
|
|
1308
|
-
query_args_model = candidate_cls
|
|
1309
|
-
else:
|
|
1310
|
-
# It's not the Pydantic model class we expected (e.g., it's the function itself)
|
|
1311
|
-
# Force fallback to JSON schema reconstruction by raising an error.
|
|
1312
|
-
raise ImportError(
|
|
1313
|
-
f"Retrieved '{class_name}' from '{module_name}' is not a Pydantic BaseModel class. "
|
|
1314
|
-
"Falling back to JSON schema reconstruction."
|
|
1315
|
-
)
|
|
1316
|
-
except Exception:
|
|
1317
|
-
# Fallback: rebuild using the JSON schema
|
|
1318
|
-
field_definitions = {}
|
|
1319
|
-
json_schema_to_rebuild = schema_info.get("schema")
|
|
1320
|
-
if json_schema_to_rebuild and isinstance(
|
|
1321
|
-
json_schema_to_rebuild, dict
|
|
1322
|
-
):
|
|
1323
|
-
for field, values in json_schema_to_rebuild.get(
|
|
1324
|
-
"properties", {}
|
|
1325
|
-
).items():
|
|
1326
|
-
field_type = get_field_type(values)
|
|
1327
|
-
field_description = values.get(
|
|
1328
|
-
"description"
|
|
1329
|
-
) # Defaults to None
|
|
1330
|
-
if "default" in values:
|
|
1331
|
-
field_definitions[field] = (
|
|
1332
|
-
field_type,
|
|
1333
|
-
Field(
|
|
1334
|
-
description=field_description,
|
|
1335
|
-
default=values["default"],
|
|
1336
|
-
),
|
|
1337
|
-
)
|
|
1338
|
-
else:
|
|
1339
|
-
field_definitions[field] = (
|
|
1340
|
-
field_type,
|
|
1341
|
-
Field(description=field_description),
|
|
1342
|
-
)
|
|
1343
|
-
query_args_model = create_model(
|
|
1344
|
-
json_schema_to_rebuild.get(
|
|
1345
|
-
"title", f"{tool_data['name']}_QueryArgs"
|
|
1346
|
-
),
|
|
1347
|
-
**field_definitions,
|
|
1348
|
-
)
|
|
1349
|
-
else: # If schema part is missing or not a dict, create a default empty model
|
|
1350
|
-
query_args_model = create_model(
|
|
1351
|
-
f"{tool_data['name']}_QueryArgs"
|
|
1352
|
-
)
|
|
1353
|
-
|
|
1354
|
-
# If fn_schema was not in tool_data or reconstruction failed badly, default to empty pydantic model
|
|
1355
|
-
if query_args_model is None:
|
|
1356
|
-
query_args_model = create_model(f"{tool_data['name']}_QueryArgs")
|
|
1357
|
-
|
|
1358
|
-
fn = (
|
|
1359
|
-
pickle.loads(tool_data["fn"].encode("latin-1"))
|
|
1360
|
-
if tool_data["fn"]
|
|
1361
|
-
else None
|
|
1362
|
-
)
|
|
1363
|
-
async_fn = (
|
|
1364
|
-
pickle.loads(tool_data["async_fn"].encode("latin-1"))
|
|
1365
|
-
if tool_data["async_fn"]
|
|
1366
|
-
else None
|
|
1367
|
-
)
|
|
1368
|
-
|
|
1369
|
-
tool = VectaraTool.from_defaults(
|
|
1370
|
-
name=tool_data["name"],
|
|
1371
|
-
description=tool_data["description"],
|
|
1372
|
-
fn=fn,
|
|
1373
|
-
async_fn=async_fn,
|
|
1374
|
-
fn_schema=query_args_model, # Re-assign the recreated dynamic model
|
|
1375
|
-
tool_type=ToolType(tool_data["tool_type"]),
|
|
1376
|
-
)
|
|
1377
|
-
tools.append(tool)
|
|
1378
|
-
|
|
1379
|
-
agent = cls(
|
|
1380
|
-
tools=tools,
|
|
1381
|
-
agent_config=agent_config,
|
|
1382
|
-
topic=data["topic"],
|
|
1383
|
-
custom_instructions=data["custom_instructions"],
|
|
1384
|
-
verbose=data["verbose"],
|
|
1385
|
-
fallback_agent_config=fallback_agent_config,
|
|
1386
|
-
workflow_cls=data["workflow_cls"],
|
|
1387
|
-
agent_progress_callback=agent_progress_callback,
|
|
1388
|
-
query_logging_callback=query_logging_callback,
|
|
1389
|
-
)
|
|
1390
|
-
memory = (
|
|
1391
|
-
pickle.loads(data["memory"].encode("latin-1"))
|
|
1392
|
-
if data.get("memory")
|
|
1393
|
-
else None
|
|
923
|
+
return deserialize_agent_from_dict(
|
|
924
|
+
cls, data, agent_progress_callback, query_logging_callback
|
|
1394
925
|
)
|
|
1395
|
-
if memory:
|
|
1396
|
-
agent.agent.memory = memory
|
|
1397
|
-
agent.memory = memory
|
|
1398
|
-
return agent
|