vectara-agentic 0.3.2__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 +43 -21
- vectara_agentic/tools_catalog.py +16 -16
- vectara_agentic/types.py +98 -6
- {vectara_agentic-0.3.2.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.2.dist-info/RECORD +0 -39
- {vectara_agentic-0.3.2.dist-info → vectara_agentic-0.4.0.dist-info}/WHEEL +0 -0
- {vectara_agentic-0.3.2.dist-info → vectara_agentic-0.4.0.dist-info}/licenses/LICENSE +0 -0
- {vectara_agentic-0.3.2.dist-info → vectara_agentic-0.4.0.dist-info}/top_level.txt +0 -0
tests/test_tools.py
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
# Suppress external dependency warnings before any other imports
|
|
2
|
+
import warnings
|
|
3
|
+
|
|
4
|
+
warnings.simplefilter("ignore", DeprecationWarning)
|
|
5
|
+
|
|
1
6
|
import unittest
|
|
2
7
|
from pydantic import Field, BaseModel
|
|
3
8
|
from unittest.mock import patch, MagicMock
|
|
@@ -13,7 +18,6 @@ from vectara_agentic.tools import (
|
|
|
13
18
|
)
|
|
14
19
|
from vectara_agentic.agent import Agent
|
|
15
20
|
from vectara_agentic.agent_config import AgentConfig
|
|
16
|
-
from vectara_agentic.types import AgentType, ModelProvider
|
|
17
21
|
|
|
18
22
|
from llama_index.core.tools import FunctionTool
|
|
19
23
|
|
|
@@ -96,7 +100,6 @@ class TestToolsPackage(unittest.TestCase):
|
|
|
96
100
|
examples=["AAPL", "GOOG"],
|
|
97
101
|
)
|
|
98
102
|
year: Optional[int | str] = Field(
|
|
99
|
-
default=None,
|
|
100
103
|
description="The year this query relates to. An integer between 2015 and 2024 or a string specifying a condition on the year",
|
|
101
104
|
examples=[
|
|
102
105
|
2020,
|
|
@@ -154,8 +157,7 @@ class TestToolsPackage(unittest.TestCase):
|
|
|
154
157
|
description="The ticker symbol for the company",
|
|
155
158
|
examples=["AAPL", "GOOG"],
|
|
156
159
|
)
|
|
157
|
-
year: int | str = Field(
|
|
158
|
-
default=None,
|
|
160
|
+
year: Optional[int | str] = Field(
|
|
159
161
|
description="The year this query relates to. An integer between 2015 and 2024 or a string specifying a condition on the year",
|
|
160
162
|
examples=[
|
|
161
163
|
2020,
|
|
@@ -235,80 +237,6 @@ class TestToolsPackage(unittest.TestCase):
|
|
|
235
237
|
self.assertIsInstance(tool, FunctionTool)
|
|
236
238
|
self.assertEqual(tool.metadata.tool_type, ToolType.QUERY)
|
|
237
239
|
|
|
238
|
-
def test_tool_with_many_arguments(self):
|
|
239
|
-
vec_factory = VectaraToolFactory(vectara_corpus_key, vectara_api_key)
|
|
240
|
-
|
|
241
|
-
class QueryToolArgs(BaseModel):
|
|
242
|
-
arg1: str = Field(description="the first argument", examples=["val1"])
|
|
243
|
-
arg2: str = Field(description="the second argument", examples=["val2"])
|
|
244
|
-
arg3: str = Field(description="the third argument", examples=["val3"])
|
|
245
|
-
arg4: str = Field(description="the fourth argument", examples=["val4"])
|
|
246
|
-
arg5: str = Field(description="the fifth argument", examples=["val5"])
|
|
247
|
-
arg6: str = Field(description="the sixth argument", examples=["val6"])
|
|
248
|
-
arg7: str = Field(description="the seventh argument", examples=["val7"])
|
|
249
|
-
arg8: str = Field(description="the eighth argument", examples=["val8"])
|
|
250
|
-
arg9: str = Field(description="the ninth argument", examples=["val9"])
|
|
251
|
-
arg10: str = Field(description="the tenth argument", examples=["val10"])
|
|
252
|
-
arg11: str = Field(description="the eleventh argument", examples=["val11"])
|
|
253
|
-
arg12: str = Field(description="the twelfth argument", examples=["val12"])
|
|
254
|
-
arg13: str = Field(
|
|
255
|
-
description="the thirteenth argument", examples=["val13"]
|
|
256
|
-
)
|
|
257
|
-
arg14: str = Field(
|
|
258
|
-
description="the fourteenth argument", examples=["val14"]
|
|
259
|
-
)
|
|
260
|
-
arg15: str = Field(description="the fifteenth argument", examples=["val15"])
|
|
261
|
-
|
|
262
|
-
query_tool_1 = vec_factory.create_rag_tool(
|
|
263
|
-
tool_name="rag_tool",
|
|
264
|
-
tool_description="""
|
|
265
|
-
A dummy tool that takes 15 arguments and returns a response (str) to the user query based on the data in this corpus.
|
|
266
|
-
We are using this tool to test the tool factory works and does not crash with OpenAI.
|
|
267
|
-
""",
|
|
268
|
-
tool_args_schema=QueryToolArgs,
|
|
269
|
-
)
|
|
270
|
-
|
|
271
|
-
# Test with 15 arguments to make sure no issues occur
|
|
272
|
-
config = AgentConfig(agent_type=AgentType.OPENAI)
|
|
273
|
-
agent = Agent(
|
|
274
|
-
tools=[query_tool_1],
|
|
275
|
-
topic="Sample topic",
|
|
276
|
-
custom_instructions="Call the tool with 15 arguments for OPENAI",
|
|
277
|
-
agent_config=config,
|
|
278
|
-
)
|
|
279
|
-
res = agent.chat("What is the stock price for Yahoo on 12/31/22?")
|
|
280
|
-
self.assertNotIn("maximum length of 1024 characters", str(res))
|
|
281
|
-
|
|
282
|
-
# Same test but with GROQ, should not have this limit
|
|
283
|
-
config = AgentConfig(
|
|
284
|
-
agent_type=AgentType.FUNCTION_CALLING,
|
|
285
|
-
main_llm_provider=ModelProvider.GROQ,
|
|
286
|
-
tool_llm_provider=ModelProvider.GROQ,
|
|
287
|
-
)
|
|
288
|
-
agent = Agent(
|
|
289
|
-
tools=[query_tool_1],
|
|
290
|
-
topic="Sample topic",
|
|
291
|
-
custom_instructions="Call the tool with 15 arguments for GROQ",
|
|
292
|
-
agent_config=config,
|
|
293
|
-
)
|
|
294
|
-
res = agent.chat("What is the stock price?")
|
|
295
|
-
self.assertNotIn("maximum length of 1024 characters", str(res))
|
|
296
|
-
|
|
297
|
-
# Same test but with ANTHROPIC, should not have this limit
|
|
298
|
-
config = AgentConfig(
|
|
299
|
-
agent_type=AgentType.FUNCTION_CALLING,
|
|
300
|
-
main_llm_provider=ModelProvider.ANTHROPIC,
|
|
301
|
-
tool_llm_provider=ModelProvider.ANTHROPIC,
|
|
302
|
-
)
|
|
303
|
-
agent = Agent(
|
|
304
|
-
tools=[query_tool_1],
|
|
305
|
-
topic="Sample topic",
|
|
306
|
-
custom_instructions="Call the tool with 15 arguments for ANTHROPIC",
|
|
307
|
-
agent_config=config,
|
|
308
|
-
)
|
|
309
|
-
res = agent.chat("What is the stock price?")
|
|
310
|
-
self.assertIn("stock price", str(res))
|
|
311
|
-
|
|
312
240
|
@patch.object(VectaraIndex, "as_query_engine")
|
|
313
241
|
def test_vectara_tool_args_type(
|
|
314
242
|
self,
|
|
@@ -384,7 +312,7 @@ class TestToolsPackage(unittest.TestCase):
|
|
|
384
312
|
def __init__(self):
|
|
385
313
|
pass
|
|
386
314
|
|
|
387
|
-
def mult(self, x, y):
|
|
315
|
+
def mult(self, x: float, y: float) -> float:
|
|
388
316
|
return x * y
|
|
389
317
|
|
|
390
318
|
test_class = TestClass()
|
|
@@ -410,7 +338,7 @@ class TestToolsPackage(unittest.TestCase):
|
|
|
410
338
|
class DummyArgs(BaseModel):
|
|
411
339
|
foo: int = Field(..., description="how many foos", examples=[1, 2, 3])
|
|
412
340
|
bar: str = Field(
|
|
413
|
-
"baz",
|
|
341
|
+
default="baz",
|
|
414
342
|
description="what bar to use",
|
|
415
343
|
examples=["x", "y"],
|
|
416
344
|
)
|
|
@@ -425,7 +353,7 @@ class TestToolsPackage(unittest.TestCase):
|
|
|
425
353
|
doc = dummy_tool.metadata.description
|
|
426
354
|
self.assertTrue(
|
|
427
355
|
doc.startswith(
|
|
428
|
-
"dummy_tool(query: str, foo: int, bar: str) -> dict[str, Any]"
|
|
356
|
+
"dummy_tool(query: str, foo: int, bar: str | None) -> dict[str, Any]"
|
|
429
357
|
)
|
|
430
358
|
)
|
|
431
359
|
self.assertIn("Args:", doc)
|
|
@@ -433,7 +361,7 @@ class TestToolsPackage(unittest.TestCase):
|
|
|
433
361
|
"query (str): The search query to perform, in the form of a question", doc
|
|
434
362
|
)
|
|
435
363
|
self.assertIn("foo (int): how many foos (e.g., 1, 2, 3)", doc)
|
|
436
|
-
self.assertIn("bar (str, default='baz'): what bar to use (e.g., 'x', 'y')", doc)
|
|
364
|
+
self.assertIn("bar (str | None, default='baz'): what bar to use (e.g., 'x', 'y')", doc)
|
|
437
365
|
self.assertIn("Returns:", doc)
|
|
438
366
|
self.assertIn("dict[str, Any]: A dictionary containing the result data.", doc)
|
|
439
367
|
|
tests/test_vectara_llms.py
CHANGED
tests/test_vhc.py
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# Suppress external dependency warnings before any other imports
|
|
2
|
+
import warnings
|
|
3
|
+
warnings.simplefilter("ignore", DeprecationWarning)
|
|
4
|
+
|
|
5
|
+
import unittest
|
|
6
|
+
|
|
7
|
+
from vectara_agentic.agent import Agent, AgentType
|
|
8
|
+
from vectara_agentic.agent_config import AgentConfig
|
|
9
|
+
from vectara_agentic.tools import ToolsFactory
|
|
10
|
+
from vectara_agentic.types import ModelProvider
|
|
11
|
+
|
|
12
|
+
import nest_asyncio
|
|
13
|
+
nest_asyncio.apply()
|
|
14
|
+
|
|
15
|
+
statements = [
|
|
16
|
+
"The sky is blue.",
|
|
17
|
+
"Cats are better than dogs.",
|
|
18
|
+
"Python is a great programming language.",
|
|
19
|
+
"The Earth revolves around the Sun.",
|
|
20
|
+
"Chocolate is the best ice cream flavor.",
|
|
21
|
+
]
|
|
22
|
+
st_inx = 0
|
|
23
|
+
def get_statement() -> str:
|
|
24
|
+
"Generate next statement"
|
|
25
|
+
global st_inx
|
|
26
|
+
st = statements[st_inx]
|
|
27
|
+
st_inx += 1
|
|
28
|
+
return st
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
fc_config = AgentConfig(
|
|
32
|
+
agent_type=AgentType.FUNCTION_CALLING,
|
|
33
|
+
main_llm_provider=ModelProvider.OPENAI,
|
|
34
|
+
tool_llm_provider=ModelProvider.OPENAI,
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
vectara_api_key = 'zqt_UXrBcnI2UXINZkrv4g1tQPhzj02vfdtqYJIDiA'
|
|
38
|
+
|
|
39
|
+
class TestVHC(unittest.TestCase):
|
|
40
|
+
|
|
41
|
+
def test_vhc(self):
|
|
42
|
+
tools = [ToolsFactory().create_tool(get_statement)]
|
|
43
|
+
topic = "statements"
|
|
44
|
+
instructions = (
|
|
45
|
+
f"Call the get_statement tool multiple times to get all {len(statements)} statements."
|
|
46
|
+
f"Respond to the user question based exclusively on the statements you receive - do not use any other knowledge or information."
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
agent = Agent(
|
|
50
|
+
tools=tools,
|
|
51
|
+
topic=topic,
|
|
52
|
+
agent_config=fc_config,
|
|
53
|
+
custom_instructions=instructions,
|
|
54
|
+
vectara_api_key=vectara_api_key,
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
res = agent.chat("Are large cats better than small dogs?")
|
|
58
|
+
vhc_corrections = res.metadata.get("corrections", None)
|
|
59
|
+
self.assertTrue(
|
|
60
|
+
len(vhc_corrections) >= 0 and len(vhc_corrections) <= 2,
|
|
61
|
+
"Corrections should be between 0 and 2"
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
if __name__ == "__main__":
|
|
66
|
+
unittest.main()
|
tests/test_workflow.py
CHANGED
vectara_agentic/__init__.py
CHANGED
|
@@ -2,25 +2,48 @@
|
|
|
2
2
|
vectara_agentic package.
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
|
+
# Simple global warning suppression for end users
|
|
6
|
+
import warnings
|
|
7
|
+
|
|
8
|
+
warnings.simplefilter("ignore", DeprecationWarning)
|
|
9
|
+
|
|
10
|
+
# pylint: disable=wrong-import-position
|
|
5
11
|
from .agent import Agent
|
|
6
12
|
from .tools import VectaraToolFactory, VectaraTool, ToolsFactory
|
|
7
13
|
from .tools_catalog import ToolsCatalog
|
|
8
14
|
from .agent_config import AgentConfig
|
|
9
15
|
from .agent_endpoint import create_app, start_app
|
|
10
16
|
from .types import (
|
|
11
|
-
AgentType,
|
|
17
|
+
AgentType,
|
|
18
|
+
ObserverType,
|
|
19
|
+
ModelProvider,
|
|
20
|
+
AgentStatusType,
|
|
21
|
+
LLMRole,
|
|
22
|
+
ToolType,
|
|
12
23
|
)
|
|
13
24
|
|
|
14
25
|
# Define the __all__ variable for wildcard imports
|
|
15
26
|
__all__ = [
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
27
|
+
"Agent",
|
|
28
|
+
"VectaraToolFactory",
|
|
29
|
+
"VectaraTool",
|
|
30
|
+
"ToolsFactory",
|
|
31
|
+
"AgentConfig",
|
|
32
|
+
"create_app",
|
|
33
|
+
"start_app",
|
|
34
|
+
"ToolsCatalog",
|
|
35
|
+
"AgentType",
|
|
36
|
+
"ObserverType",
|
|
37
|
+
"ModelProvider",
|
|
38
|
+
"AgentStatusType",
|
|
39
|
+
"LLMRole",
|
|
40
|
+
"ToolType",
|
|
19
41
|
]
|
|
20
42
|
|
|
21
43
|
# Ensure package version is available
|
|
22
44
|
try:
|
|
23
45
|
import importlib.metadata
|
|
46
|
+
|
|
24
47
|
__version__ = importlib.metadata.version("vectara_agentic")
|
|
25
48
|
except Exception:
|
|
26
49
|
__version__ = "0.0.0" # fallback if not installed
|
vectara_agentic/_callback.py
CHANGED
|
@@ -3,14 +3,17 @@ Module to handle agent callbacks
|
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
5
|
import inspect
|
|
6
|
+
import logging
|
|
6
7
|
from typing import Any, Dict, Optional, List, Callable
|
|
7
8
|
from functools import wraps
|
|
9
|
+
import traceback
|
|
8
10
|
|
|
9
11
|
from llama_index.core.callbacks.base_handler import BaseCallbackHandler
|
|
10
12
|
from llama_index.core.callbacks.schema import CBEventType, EventPayload
|
|
11
13
|
|
|
12
14
|
from .types import AgentStatusType
|
|
13
15
|
|
|
16
|
+
|
|
14
17
|
def wrap_callback_fn(callback):
|
|
15
18
|
"""
|
|
16
19
|
Wrap a callback function to ensure it only receives the parameters it can accept.
|
|
@@ -34,6 +37,7 @@ def wrap_callback_fn(callback):
|
|
|
34
37
|
|
|
35
38
|
return new_callback
|
|
36
39
|
|
|
40
|
+
|
|
37
41
|
class AgentCallbackHandler(BaseCallbackHandler):
|
|
38
42
|
"""
|
|
39
43
|
Callback handler to track agent status
|
|
@@ -153,10 +157,8 @@ class AgentCallbackHandler(BaseCallbackHandler):
|
|
|
153
157
|
self._handle_function_call(payload, event_id)
|
|
154
158
|
elif event_type == CBEventType.AGENT_STEP:
|
|
155
159
|
self._handle_agent_step(payload, event_id)
|
|
156
|
-
elif event_type == CBEventType.EXCEPTION:
|
|
157
|
-
print(f"Exception event in handle_event: {payload.get(EventPayload.EXCEPTION)}")
|
|
158
160
|
else:
|
|
159
|
-
|
|
161
|
+
pass
|
|
160
162
|
|
|
161
163
|
async def _ahandle_event(
|
|
162
164
|
self, event_type: CBEventType, payload: Dict[str, Any], event_id: str
|
|
@@ -167,10 +169,8 @@ class AgentCallbackHandler(BaseCallbackHandler):
|
|
|
167
169
|
await self._ahandle_function_call(payload, event_id)
|
|
168
170
|
elif event_type == CBEventType.AGENT_STEP:
|
|
169
171
|
await self._ahandle_agent_step(payload, event_id)
|
|
170
|
-
elif event_type == CBEventType.EXCEPTION:
|
|
171
|
-
print(f"Exception event in ahandle_event: {payload.get(EventPayload.EXCEPTION)}")
|
|
172
172
|
else:
|
|
173
|
-
|
|
173
|
+
pass
|
|
174
174
|
|
|
175
175
|
# Synchronous handlers
|
|
176
176
|
def _handle_llm(
|
|
@@ -196,37 +196,38 @@ class AgentCallbackHandler(BaseCallbackHandler):
|
|
|
196
196
|
event_id=event_id,
|
|
197
197
|
)
|
|
198
198
|
else:
|
|
199
|
-
|
|
200
|
-
f"vectara-agentic llm callback: no messages or prompt found in payload {payload}"
|
|
201
|
-
)
|
|
199
|
+
pass
|
|
202
200
|
|
|
203
201
|
def _handle_function_call(self, payload: dict, event_id: str) -> None:
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
if
|
|
202
|
+
try:
|
|
203
|
+
if EventPayload.FUNCTION_CALL in payload:
|
|
204
|
+
fcall = payload.get(EventPayload.FUNCTION_CALL)
|
|
205
|
+
tool = payload.get(EventPayload.TOOL)
|
|
206
|
+
|
|
207
|
+
if tool:
|
|
208
|
+
tool_name = tool.name
|
|
209
|
+
if self.fn:
|
|
210
|
+
self.fn(
|
|
211
|
+
status_type=AgentStatusType.TOOL_CALL,
|
|
212
|
+
msg={"tool_name": tool_name, "arguments": fcall},
|
|
213
|
+
event_id=event_id,
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
elif EventPayload.FUNCTION_OUTPUT in payload:
|
|
217
|
+
response = payload.get(EventPayload.FUNCTION_OUTPUT)
|
|
218
|
+
tool = payload.get(EventPayload.TOOL)
|
|
219
|
+
|
|
220
|
+
if tool and self.fn:
|
|
210
221
|
self.fn(
|
|
211
|
-
status_type=AgentStatusType.
|
|
212
|
-
msg={
|
|
213
|
-
"tool_name": tool_name,
|
|
214
|
-
"arguments": fcall
|
|
215
|
-
},
|
|
222
|
+
status_type=AgentStatusType.TOOL_OUTPUT,
|
|
223
|
+
msg={"tool_name": tool.name, "content": response},
|
|
216
224
|
event_id=event_id,
|
|
217
225
|
)
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
msg=response,
|
|
224
|
-
event_id=event_id,
|
|
225
|
-
)
|
|
226
|
-
else:
|
|
227
|
-
print(
|
|
228
|
-
f"Vectara-agentic callback handler: no function call or output found in payload {payload}"
|
|
229
|
-
)
|
|
226
|
+
|
|
227
|
+
except Exception as e:
|
|
228
|
+
logging.error(f"Exception in _handle_function_call: {e}")
|
|
229
|
+
logging.error(f"Traceback: {traceback.format_exc()}")
|
|
230
|
+
# Continue execution to prevent callback failures from breaking the agent
|
|
230
231
|
|
|
231
232
|
def _handle_agent_step(self, payload: dict, event_id: str) -> None:
|
|
232
233
|
if EventPayload.MESSAGES in payload:
|
|
@@ -245,10 +246,6 @@ class AgentCallbackHandler(BaseCallbackHandler):
|
|
|
245
246
|
msg=response,
|
|
246
247
|
event_id=event_id,
|
|
247
248
|
)
|
|
248
|
-
else:
|
|
249
|
-
print(
|
|
250
|
-
f"Vectara-agentic agent_step: no messages or prompt found in payload {payload}"
|
|
251
|
-
)
|
|
252
249
|
|
|
253
250
|
# Asynchronous handlers
|
|
254
251
|
async def _ahandle_llm(self, payload: dict, event_id: str) -> None:
|
|
@@ -276,52 +273,55 @@ class AgentCallbackHandler(BaseCallbackHandler):
|
|
|
276
273
|
msg=prompt,
|
|
277
274
|
event_id=event_id,
|
|
278
275
|
)
|
|
279
|
-
else:
|
|
280
|
-
print(
|
|
281
|
-
f"vectara-agentic llm callback: no messages or prompt found in payload {payload}"
|
|
282
|
-
)
|
|
283
276
|
|
|
284
277
|
async def _ahandle_function_call(self, payload: dict, event_id: str) -> None:
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
278
|
+
try:
|
|
279
|
+
if EventPayload.FUNCTION_CALL in payload:
|
|
280
|
+
fcall = payload.get(EventPayload.FUNCTION_CALL)
|
|
281
|
+
tool = payload.get(EventPayload.TOOL)
|
|
282
|
+
|
|
283
|
+
if tool and self.fn:
|
|
290
284
|
if inspect.iscoroutinefunction(self.fn):
|
|
291
285
|
await self.fn(
|
|
292
286
|
status_type=AgentStatusType.TOOL_CALL,
|
|
287
|
+
msg={"tool_name": tool.name, "arguments": fcall},
|
|
288
|
+
event_id=event_id,
|
|
289
|
+
)
|
|
290
|
+
else:
|
|
291
|
+
self.fn(
|
|
292
|
+
status_type=AgentStatusType.TOOL_CALL,
|
|
293
|
+
msg={"tool_name": tool.name, "arguments": fcall},
|
|
294
|
+
event_id=event_id,
|
|
295
|
+
)
|
|
296
|
+
|
|
297
|
+
elif EventPayload.FUNCTION_OUTPUT in payload:
|
|
298
|
+
response = payload.get(EventPayload.FUNCTION_OUTPUT)
|
|
299
|
+
tool = payload.get(EventPayload.TOOL)
|
|
300
|
+
|
|
301
|
+
if tool and self.fn:
|
|
302
|
+
if inspect.iscoroutinefunction(self.fn):
|
|
303
|
+
await self.fn(
|
|
304
|
+
status_type=AgentStatusType.TOOL_OUTPUT,
|
|
293
305
|
msg={
|
|
294
306
|
"tool_name": tool.name,
|
|
295
|
-
"
|
|
307
|
+
"content": response,
|
|
296
308
|
},
|
|
297
309
|
event_id=event_id,
|
|
298
310
|
)
|
|
299
311
|
else:
|
|
300
312
|
self.fn(
|
|
301
|
-
status_type=AgentStatusType.
|
|
313
|
+
status_type=AgentStatusType.TOOL_OUTPUT,
|
|
302
314
|
msg={
|
|
303
315
|
"tool_name": tool.name,
|
|
304
|
-
"
|
|
316
|
+
"content": response,
|
|
305
317
|
},
|
|
306
318
|
event_id=event_id,
|
|
307
319
|
)
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
status_type=AgentStatusType.TOOL_OUTPUT,
|
|
314
|
-
msg=response,
|
|
315
|
-
event_id=event_id,
|
|
316
|
-
)
|
|
317
|
-
else:
|
|
318
|
-
self.fn(
|
|
319
|
-
status_type=AgentStatusType.TOOL_OUTPUT,
|
|
320
|
-
msg=response,
|
|
321
|
-
event_id=event_id,
|
|
322
|
-
)
|
|
323
|
-
else:
|
|
324
|
-
print(f"No function call or output found in payload {payload}")
|
|
320
|
+
|
|
321
|
+
except Exception as e:
|
|
322
|
+
logging.error(f"Exception in _ahandle_function_call: {e}")
|
|
323
|
+
logging.error(f"Traceback: {traceback.format_exc()}")
|
|
324
|
+
# Continue execution to prevent callback failures from breaking the agent
|
|
325
325
|
|
|
326
326
|
async def _ahandle_agent_step(self, payload: dict, event_id: str) -> None:
|
|
327
327
|
if EventPayload.MESSAGES in payload:
|
|
@@ -354,5 +354,3 @@ class AgentCallbackHandler(BaseCallbackHandler):
|
|
|
354
354
|
msg=response,
|
|
355
355
|
event_id=event_id,
|
|
356
356
|
)
|
|
357
|
-
else:
|
|
358
|
-
print(f"No messages or prompt found in payload {payload}")
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"""
|
|
2
2
|
Observability for Vectara Agentic.
|
|
3
3
|
"""
|
|
4
|
+
|
|
4
5
|
import os
|
|
5
6
|
import json
|
|
6
7
|
from typing import Optional, Union
|
|
@@ -13,9 +14,9 @@ SPAN_NAME: str = "VectaraQueryEngine._query"
|
|
|
13
14
|
|
|
14
15
|
|
|
15
16
|
def setup_observer(config: AgentConfig, verbose: bool) -> bool:
|
|
16
|
-
|
|
17
|
+
"""
|
|
17
18
|
Setup the observer.
|
|
18
|
-
|
|
19
|
+
"""
|
|
19
20
|
if config.observer != ObserverType.ARIZE_PHOENIX:
|
|
20
21
|
if verbose:
|
|
21
22
|
print("No Phoenix observer set.")
|
|
@@ -38,9 +39,11 @@ def setup_observer(config: AgentConfig, verbose: bool) -> bool:
|
|
|
38
39
|
if not phoenix_endpoint:
|
|
39
40
|
print("Phoenix endpoint not set. Attempting to launch local Phoenix UI...")
|
|
40
41
|
px.launch_app()
|
|
41
|
-
print(
|
|
42
|
+
print(
|
|
43
|
+
"Local Phoenix UI launched. You can view traces at the UI address (usually http://localhost:6006)."
|
|
44
|
+
)
|
|
42
45
|
|
|
43
|
-
if phoenix_endpoint and
|
|
46
|
+
if phoenix_endpoint and "app.phoenix.arize.com" in phoenix_endpoint:
|
|
44
47
|
phoenix_api_key = os.getenv("PHOENIX_API_KEY")
|
|
45
48
|
if not phoenix_api_key:
|
|
46
49
|
raise ValueError(
|
|
@@ -50,7 +53,7 @@ def setup_observer(config: AgentConfig, verbose: bool) -> bool:
|
|
|
50
53
|
os.environ["PHOENIX_COLLECTOR_ENDPOINT"] = "https://app.phoenix.arize.com"
|
|
51
54
|
|
|
52
55
|
reg_kwargs = {
|
|
53
|
-
"endpoint": phoenix_endpoint or
|
|
56
|
+
"endpoint": phoenix_endpoint or "http://localhost:6006/v1/traces",
|
|
54
57
|
"project_name": PROJECT_NAME,
|
|
55
58
|
"batch": False,
|
|
56
59
|
"set_global_tracer_provider": False,
|
|
@@ -58,35 +61,35 @@ def setup_observer(config: AgentConfig, verbose: bool) -> bool:
|
|
|
58
61
|
tracer_provider = register(**reg_kwargs)
|
|
59
62
|
LlamaIndexInstrumentor().instrument(tracer_provider=tracer_provider)
|
|
60
63
|
if verbose:
|
|
61
|
-
print(
|
|
64
|
+
print(
|
|
65
|
+
f"Phoenix observer configured for project 'vectara-agentic' at endpoint: {reg_kwargs['endpoint']}"
|
|
66
|
+
)
|
|
62
67
|
return True
|
|
63
68
|
|
|
64
69
|
|
|
65
70
|
def _extract_fcs_value(output: Union[str, dict]) -> Optional[float]:
|
|
66
|
-
|
|
71
|
+
"""
|
|
67
72
|
Extract the FCS value from the output.
|
|
68
|
-
|
|
73
|
+
"""
|
|
69
74
|
try:
|
|
70
75
|
output_json = json.loads(output)
|
|
71
|
-
if
|
|
72
|
-
return output_json[
|
|
73
|
-
except
|
|
74
|
-
print(f"
|
|
75
|
-
except KeyError:
|
|
76
|
-
print(f"'fcs' not found in: {output_json}")
|
|
76
|
+
if "metadata" in output_json and "fcs" in output_json["metadata"]:
|
|
77
|
+
return output_json["metadata"]["fcs"]
|
|
78
|
+
except Exception as e:
|
|
79
|
+
print(f"Error extracting FCS value: {e}")
|
|
77
80
|
return None
|
|
78
81
|
|
|
79
82
|
|
|
80
83
|
def _find_top_level_parent_id(row: pd.Series, all_spans: pd.DataFrame) -> Optional[str]:
|
|
81
|
-
|
|
84
|
+
"""
|
|
82
85
|
Find the top level parent id for the given span.
|
|
83
|
-
|
|
84
|
-
current_id = row[
|
|
86
|
+
"""
|
|
87
|
+
current_id = row["parent_id"]
|
|
85
88
|
while current_id is not None:
|
|
86
89
|
parent_row = all_spans[all_spans.index == current_id]
|
|
87
90
|
if parent_row.empty:
|
|
88
91
|
break
|
|
89
|
-
new_parent_id = parent_row[
|
|
92
|
+
new_parent_id = parent_row["parent_id"].iloc[0]
|
|
90
93
|
if new_parent_id == current_id:
|
|
91
94
|
break
|
|
92
95
|
if new_parent_id is None:
|
|
@@ -96,17 +99,14 @@ def _find_top_level_parent_id(row: pd.Series, all_spans: pd.DataFrame) -> Option
|
|
|
96
99
|
|
|
97
100
|
|
|
98
101
|
def eval_fcs() -> None:
|
|
99
|
-
|
|
102
|
+
"""
|
|
100
103
|
Evaluate the FCS score for the VectaraQueryEngine._query span.
|
|
101
|
-
|
|
104
|
+
"""
|
|
102
105
|
import phoenix as px
|
|
103
106
|
from phoenix.trace.dsl import SpanQuery
|
|
104
107
|
from phoenix.trace import SpanEvaluations
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
"parent_id",
|
|
108
|
-
"name"
|
|
109
|
-
)
|
|
108
|
+
|
|
109
|
+
query = SpanQuery().select("output.value", "parent_id", "name")
|
|
110
110
|
try:
|
|
111
111
|
client = px.Client()
|
|
112
112
|
all_spans = client.query_spans(query, project_name=PROJECT_NAME)
|
|
@@ -114,17 +114,17 @@ def eval_fcs() -> None:
|
|
|
114
114
|
print(f"Failed to query spans: {e}")
|
|
115
115
|
return
|
|
116
116
|
|
|
117
|
-
vectara_spans = all_spans[all_spans[
|
|
118
|
-
vectara_spans[
|
|
117
|
+
vectara_spans = all_spans[all_spans["name"] == SPAN_NAME].copy()
|
|
118
|
+
vectara_spans["top_level_parent_id"] = vectara_spans.apply(
|
|
119
119
|
lambda row: _find_top_level_parent_id(row, all_spans), axis=1
|
|
120
120
|
)
|
|
121
|
-
vectara_spans[
|
|
121
|
+
vectara_spans["score"] = vectara_spans["output.value"].apply(_extract_fcs_value)
|
|
122
122
|
|
|
123
123
|
vectara_spans.reset_index(inplace=True)
|
|
124
124
|
top_level_spans = vectara_spans.copy()
|
|
125
|
-
top_level_spans[
|
|
125
|
+
top_level_spans["context.span_id"] = top_level_spans["top_level_parent_id"]
|
|
126
126
|
vectara_spans = pd.concat([vectara_spans, top_level_spans], ignore_index=True)
|
|
127
|
-
vectara_spans.set_index(
|
|
127
|
+
vectara_spans.set_index("context.span_id", inplace=True)
|
|
128
128
|
|
|
129
129
|
px.Client().log_evaluations(
|
|
130
130
|
SpanEvaluations(
|
vectara_agentic/_version.py
CHANGED