vectara-agentic 0.2.0__py3-none-any.whl → 0.2.1__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/endpoint.py +42 -0
- tests/test_agent.py +25 -0
- tests/test_private_llm.py +67 -0
- tests/test_tools.py +32 -5
- vectara_agentic/_prompts.py +3 -1
- vectara_agentic/_version.py +1 -1
- vectara_agentic/agent.py +93 -32
- vectara_agentic/agent_config.py +9 -0
- vectara_agentic/tools.py +10 -4
- vectara_agentic/tools_catalog.py +1 -1
- vectara_agentic/types.py +1 -0
- vectara_agentic/utils.py +4 -0
- {vectara_agentic-0.2.0.dist-info → vectara_agentic-0.2.1.dist-info}/METADATA +31 -4
- vectara_agentic-0.2.1.dist-info/RECORD +23 -0
- vectara_agentic-0.2.0.dist-info/RECORD +0 -21
- {vectara_agentic-0.2.0.dist-info → vectara_agentic-0.2.1.dist-info}/LICENSE +0 -0
- {vectara_agentic-0.2.0.dist-info → vectara_agentic-0.2.1.dist-info}/WHEEL +0 -0
- {vectara_agentic-0.2.0.dist-info → vectara_agentic-0.2.1.dist-info}/top_level.txt +0 -0
tests/endpoint.py
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
from openai import OpenAI
|
|
2
|
+
from flask import Flask, request, jsonify
|
|
3
|
+
from functools import wraps
|
|
4
|
+
|
|
5
|
+
app = Flask(__name__)
|
|
6
|
+
|
|
7
|
+
# Set your OpenAI API key (ensure you've set this in your environment)
|
|
8
|
+
|
|
9
|
+
EXPECTED_API_KEY = "TEST_API_KEY"
|
|
10
|
+
|
|
11
|
+
def require_api_key(f):
|
|
12
|
+
@wraps(f)
|
|
13
|
+
def decorated_function(*args, **kwargs):
|
|
14
|
+
api_key = request.headers.get("Authorization").split("Bearer ")[-1]
|
|
15
|
+
if not api_key or api_key != EXPECTED_API_KEY:
|
|
16
|
+
return jsonify({"error": "Unauthorized"}), 401
|
|
17
|
+
return f(*args, **kwargs)
|
|
18
|
+
return decorated_function
|
|
19
|
+
|
|
20
|
+
@app.before_request
|
|
21
|
+
def log_request_info():
|
|
22
|
+
app.logger.info("Request received: %s %s", request.method, request.path)
|
|
23
|
+
|
|
24
|
+
@app.route("/v1/chat/completions", methods=["POST"])
|
|
25
|
+
@require_api_key
|
|
26
|
+
def chat_completions():
|
|
27
|
+
app.logger.info("Received request on /v1/chat/completions")
|
|
28
|
+
data = request.get_json()
|
|
29
|
+
if not data:
|
|
30
|
+
return jsonify({"error": "Invalid JSON payload"}), 400
|
|
31
|
+
|
|
32
|
+
client = OpenAI()
|
|
33
|
+
try:
|
|
34
|
+
completion = client.chat.completions.create(**data)
|
|
35
|
+
return jsonify(completion.model_dump()), 200
|
|
36
|
+
except Exception as e:
|
|
37
|
+
return jsonify({"error": str(e)}), 400
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
if __name__ == "__main__":
|
|
41
|
+
# Run on port 5000 by default; adjust as needed.
|
|
42
|
+
app.run(debug=True, port=5000)
|
tests/test_agent.py
CHANGED
|
@@ -77,6 +77,24 @@ class TestAgentPackage(unittest.TestCase):
|
|
|
77
77
|
"50",
|
|
78
78
|
)
|
|
79
79
|
|
|
80
|
+
def test_multiturn(self):
|
|
81
|
+
def mult(x, y):
|
|
82
|
+
return x * y
|
|
83
|
+
|
|
84
|
+
tools = [ToolsFactory().create_tool(mult)]
|
|
85
|
+
topic = "AI topic"
|
|
86
|
+
instructions = "Always do as your father tells you, if your mother agrees!"
|
|
87
|
+
agent = Agent(
|
|
88
|
+
tools=tools,
|
|
89
|
+
topic=topic,
|
|
90
|
+
custom_instructions=instructions,
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
agent.chat("What is 5 times 10. Only give the answer, nothing else")
|
|
94
|
+
agent.chat("what is 3 times 7. Only give the answer, nothing else")
|
|
95
|
+
res = agent.chat("multiply the results of the last two questions. Output only the answer.")
|
|
96
|
+
self.assertEqual(res.response, "1050")
|
|
97
|
+
|
|
80
98
|
def test_from_corpus(self):
|
|
81
99
|
agent = Agent.from_corpus(
|
|
82
100
|
tool_name="RAG Tool",
|
|
@@ -99,8 +117,15 @@ class TestAgentPackage(unittest.TestCase):
|
|
|
99
117
|
)
|
|
100
118
|
|
|
101
119
|
agent_reloaded = agent.loads(agent.dumps())
|
|
120
|
+
agent_reloaded_again = agent_reloaded.loads(agent_reloaded.dumps())
|
|
121
|
+
|
|
102
122
|
self.assertIsInstance(agent_reloaded, Agent)
|
|
103
123
|
self.assertEqual(agent, agent_reloaded)
|
|
124
|
+
self.assertEqual(agent.agent_type, agent_reloaded.agent_type)
|
|
125
|
+
|
|
126
|
+
self.assertIsInstance(agent_reloaded, Agent)
|
|
127
|
+
self.assertEqual(agent, agent_reloaded_again)
|
|
128
|
+
self.assertEqual(agent.agent_type, agent_reloaded_again.agent_type)
|
|
104
129
|
|
|
105
130
|
|
|
106
131
|
if __name__ == "__main__":
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import unittest
|
|
3
|
+
import subprocess
|
|
4
|
+
import time
|
|
5
|
+
import requests
|
|
6
|
+
import signal
|
|
7
|
+
|
|
8
|
+
from vectara_agentic.agent import Agent, AgentType
|
|
9
|
+
from vectara_agentic.agent_config import AgentConfig
|
|
10
|
+
from vectara_agentic.types import ModelProvider
|
|
11
|
+
from vectara_agentic.tools import ToolsFactory
|
|
12
|
+
|
|
13
|
+
class TestPrivateLLM(unittest.TestCase):
|
|
14
|
+
|
|
15
|
+
@classmethod
|
|
16
|
+
def setUp(cls):
|
|
17
|
+
# Start the Flask server as a subprocess
|
|
18
|
+
cls.flask_process = subprocess.Popen(
|
|
19
|
+
['flask', 'run', '--port=5000'],
|
|
20
|
+
env={**os.environ, 'FLASK_APP': 'tests.endpoint:app', 'FLASK_ENV': 'development'},
|
|
21
|
+
stdout=None, stderr=None,
|
|
22
|
+
)
|
|
23
|
+
# Wait for the server to start
|
|
24
|
+
timeout = 10
|
|
25
|
+
url = 'http://127.0.0.1:5000/'
|
|
26
|
+
for _ in range(timeout):
|
|
27
|
+
try:
|
|
28
|
+
requests.get(url)
|
|
29
|
+
return
|
|
30
|
+
except requests.ConnectionError:
|
|
31
|
+
time.sleep(1)
|
|
32
|
+
raise RuntimeError(f"Failed to start Flask server at {url}")
|
|
33
|
+
|
|
34
|
+
@classmethod
|
|
35
|
+
def tearDown(cls):
|
|
36
|
+
# Terminate the Flask server
|
|
37
|
+
cls.flask_process.send_signal(signal.SIGINT)
|
|
38
|
+
cls.flask_process.wait()
|
|
39
|
+
|
|
40
|
+
def test_endpoint(self):
|
|
41
|
+
def mult(x, y):
|
|
42
|
+
return x * y
|
|
43
|
+
|
|
44
|
+
tools = [ToolsFactory().create_tool(mult)]
|
|
45
|
+
topic = "calculator"
|
|
46
|
+
custom_instructions = "you are an agent specializing in math, assisting a user."
|
|
47
|
+
config = AgentConfig(
|
|
48
|
+
agent_type=AgentType.REACT,
|
|
49
|
+
main_llm_provider=ModelProvider.PRIVATE,
|
|
50
|
+
main_llm_model_name="gpt-4o",
|
|
51
|
+
private_llm_api_base="http://127.0.0.1:5000/v1",
|
|
52
|
+
private_llm_api_key="TEST_API_KEY",
|
|
53
|
+
)
|
|
54
|
+
agent = Agent(agent_config=config, tools=tools, topic=topic,
|
|
55
|
+
custom_instructions=custom_instructions)
|
|
56
|
+
|
|
57
|
+
# To run this test, you must have OPENAI_API_KEY in your environment
|
|
58
|
+
self.assertEqual(
|
|
59
|
+
agent.chat(
|
|
60
|
+
"What is 5 times 10. Only give the answer, nothing else"
|
|
61
|
+
).response.replace("$", "\\$"),
|
|
62
|
+
"50",
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
if __name__ == "__main__":
|
|
67
|
+
unittest.main()
|
tests/test_tools.py
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import unittest
|
|
2
2
|
|
|
3
|
+
from pydantic import Field, BaseModel
|
|
4
|
+
|
|
3
5
|
from vectara_agentic.tools import VectaraTool, VectaraToolFactory, ToolsFactory, ToolType
|
|
4
6
|
from vectara_agentic.agent import Agent
|
|
5
|
-
from
|
|
7
|
+
from vectara_agentic.agent_config import AgentConfig
|
|
8
|
+
|
|
6
9
|
from llama_index.core.tools import FunctionTool
|
|
7
10
|
|
|
8
11
|
|
|
@@ -60,9 +63,6 @@ class TestToolsPackage(unittest.TestCase):
|
|
|
60
63
|
vectara_corpus_key = "vectara-docs_1"
|
|
61
64
|
vectara_api_key = "zqt_UXrBcnI2UXINZkrv4g1tQPhzj02vfdtqYJIDiA"
|
|
62
65
|
|
|
63
|
-
class QueryToolArgs(BaseModel):
|
|
64
|
-
query: str = Field(description="The user query")
|
|
65
|
-
|
|
66
66
|
agent = Agent.from_corpus(
|
|
67
67
|
vectara_corpus_key=vectara_corpus_key,
|
|
68
68
|
vectara_api_key=vectara_api_key,
|
|
@@ -72,7 +72,34 @@ class TestToolsPackage(unittest.TestCase):
|
|
|
72
72
|
vectara_summarizer="mockingbird-1.0-2024-07-16"
|
|
73
73
|
)
|
|
74
74
|
|
|
75
|
-
self.assertIn("Vectara is an end-to-end platform", agent.chat("What is Vectara?"))
|
|
75
|
+
self.assertIn("Vectara is an end-to-end platform", str(agent.chat("What is Vectara?")))
|
|
76
|
+
|
|
77
|
+
def test_class_method_as_tool(self):
|
|
78
|
+
class TestClass:
|
|
79
|
+
def __init__(self):
|
|
80
|
+
pass
|
|
81
|
+
|
|
82
|
+
def mult(self, x, y):
|
|
83
|
+
return x * y
|
|
84
|
+
|
|
85
|
+
test_class = TestClass()
|
|
86
|
+
tools = [ToolsFactory().create_tool(test_class.mult)]
|
|
87
|
+
topic = "AI topic"
|
|
88
|
+
instructions = "Always do as your father tells you, if your mother agrees!"
|
|
89
|
+
config = AgentConfig()
|
|
90
|
+
agent = Agent(
|
|
91
|
+
tools=tools,
|
|
92
|
+
topic=topic,
|
|
93
|
+
custom_instructions=instructions,
|
|
94
|
+
agent_config=config
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
self.assertEqual(
|
|
98
|
+
agent.chat(
|
|
99
|
+
"What is 5 times 10. Only give the answer, nothing else"
|
|
100
|
+
).response.replace("$", "\\$"),
|
|
101
|
+
"50",
|
|
102
|
+
)
|
|
76
103
|
|
|
77
104
|
|
|
78
105
|
if __name__ == "__main__":
|
vectara_agentic/_prompts.py
CHANGED
|
@@ -5,7 +5,9 @@ This file contains the prompt templates for the different types of agents.
|
|
|
5
5
|
# General (shared) instructions
|
|
6
6
|
GENERAL_INSTRUCTIONS = """
|
|
7
7
|
- Use tools as your main source of information, do not respond without using a tool. Do not respond based on pre-trained knowledge.
|
|
8
|
-
-
|
|
8
|
+
- Before responding to a user query that requires knowledge of the current date, call the 'get_current_date' tool to get the current date.
|
|
9
|
+
Never rely on previous knowledge of the current date.
|
|
10
|
+
Example queries that require the current date: "What is the revenue of Apple last october?" or "What was the stock price 5 days ago?".
|
|
9
11
|
- When using a tool with arguments, simplify the query as much as possible if you use the tool with arguments.
|
|
10
12
|
For example, if the original query is "revenue for apple in 2021", you can use the tool with a query "revenue" with arguments year=2021 and company=apple.
|
|
11
13
|
- If a tool responds with "I do not have enough information", try one of the following:
|
vectara_agentic/_version.py
CHANGED
vectara_agentic/agent.py
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
"""
|
|
2
2
|
This module contains the Agent class for handling different types of agents and their interactions.
|
|
3
3
|
"""
|
|
4
|
-
from typing import List, Callable, Optional, Dict, Any
|
|
4
|
+
from typing import List, Callable, Optional, Dict, Any, Union, Tuple
|
|
5
5
|
import os
|
|
6
|
+
import re
|
|
6
7
|
from datetime import date
|
|
7
8
|
import time
|
|
8
9
|
import json
|
|
@@ -10,12 +11,15 @@ import logging
|
|
|
10
11
|
import traceback
|
|
11
12
|
import asyncio
|
|
12
13
|
|
|
13
|
-
import
|
|
14
|
+
import cloudpickle as pickle
|
|
15
|
+
|
|
14
16
|
from dotenv import load_dotenv
|
|
15
17
|
|
|
16
18
|
from retrying import retry
|
|
17
19
|
from pydantic import Field, create_model
|
|
18
20
|
|
|
21
|
+
from llama_index.core.memory import ChatMemoryBuffer
|
|
22
|
+
from llama_index.core.llms import ChatMessage, MessageRole
|
|
19
23
|
from llama_index.core.tools import FunctionTool
|
|
20
24
|
from llama_index.core.agent import ReActAgent
|
|
21
25
|
from llama_index.core.agent.react.formatter import ReActChatFormatter
|
|
@@ -24,7 +28,7 @@ from llama_index.agent.lats import LATSAgentWorker
|
|
|
24
28
|
from llama_index.core.callbacks import CallbackManager, TokenCountingHandler
|
|
25
29
|
from llama_index.core.callbacks.base_handler import BaseCallbackHandler
|
|
26
30
|
from llama_index.agent.openai import OpenAIAgent
|
|
27
|
-
|
|
31
|
+
|
|
28
32
|
|
|
29
33
|
from .types import AgentType, AgentStatusType, LLMRole, ToolType, AgentResponse, AgentStreamingResponse
|
|
30
34
|
from .utils import get_llm, get_tokenizer_for_model
|
|
@@ -35,6 +39,21 @@ from .tools import VectaraToolFactory, VectaraTool, ToolsFactory
|
|
|
35
39
|
from .tools_catalog import get_current_date
|
|
36
40
|
from .agent_config import AgentConfig
|
|
37
41
|
|
|
42
|
+
class IgnoreUnpickleableAttributeFilter(logging.Filter):
|
|
43
|
+
'''
|
|
44
|
+
Filter to ignore log messages that contain certain strings
|
|
45
|
+
'''
|
|
46
|
+
def filter(self, record):
|
|
47
|
+
msgs_to_ignore = [
|
|
48
|
+
"Removing unpickleable private attribute _chunking_tokenizer_fn",
|
|
49
|
+
"Removing unpickleable private attribute _split_fns",
|
|
50
|
+
"Removing unpickleable private attribute _sub_sentence_split_fns",
|
|
51
|
+
]
|
|
52
|
+
return all(msg not in record.getMessage() for msg in msgs_to_ignore)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
logging.getLogger().addFilter(IgnoreUnpickleableAttributeFilter())
|
|
56
|
+
|
|
38
57
|
logger = logging.getLogger("opentelemetry.exporter.otlp.proto.http.trace_exporter")
|
|
39
58
|
logger.setLevel(logging.CRITICAL)
|
|
40
59
|
|
|
@@ -81,6 +100,34 @@ def _retry_if_exception(exception):
|
|
|
81
100
|
return isinstance(exception, (TimeoutError))
|
|
82
101
|
|
|
83
102
|
|
|
103
|
+
def get_field_type(field_schema: dict) -> Any:
|
|
104
|
+
"""
|
|
105
|
+
Convert a JSON schema field definition to a Python type.
|
|
106
|
+
Handles 'type' and 'anyOf' cases.
|
|
107
|
+
"""
|
|
108
|
+
json_type_to_python = {
|
|
109
|
+
"string": str,
|
|
110
|
+
"integer": int,
|
|
111
|
+
"boolean": bool,
|
|
112
|
+
"array": list,
|
|
113
|
+
"object": dict,
|
|
114
|
+
"number": float,
|
|
115
|
+
}
|
|
116
|
+
if "anyOf" in field_schema:
|
|
117
|
+
types = []
|
|
118
|
+
for option in field_schema["anyOf"]:
|
|
119
|
+
# If the option has a type, convert it; otherwise, use Any.
|
|
120
|
+
if "type" in option:
|
|
121
|
+
types.append(json_type_to_python.get(option["type"], Any))
|
|
122
|
+
else:
|
|
123
|
+
types.append(Any)
|
|
124
|
+
# Return a Union of the types. For example, Union[str, int]
|
|
125
|
+
return Union[tuple(types)]
|
|
126
|
+
elif "type" in field_schema:
|
|
127
|
+
return json_type_to_python.get(field_schema["type"], Any)
|
|
128
|
+
else:
|
|
129
|
+
return Any
|
|
130
|
+
|
|
84
131
|
class Agent:
|
|
85
132
|
"""
|
|
86
133
|
Agent class for handling different types of agents and their interactions.
|
|
@@ -96,6 +143,7 @@ class Agent:
|
|
|
96
143
|
agent_progress_callback: Optional[Callable[[AgentStatusType, str], None]] = None,
|
|
97
144
|
query_logging_callback: Optional[Callable[[str, str], None]] = None,
|
|
98
145
|
agent_config: Optional[AgentConfig] = None,
|
|
146
|
+
chat_history: Optional[list[Tuple[str, str]]] = None,
|
|
99
147
|
) -> None:
|
|
100
148
|
"""
|
|
101
149
|
Initialize the agent with the specified type, tools, topic, and system message.
|
|
@@ -111,10 +159,13 @@ class Agent:
|
|
|
111
159
|
query_logging_callback (Callable): A callback function the code calls upon completion of a query
|
|
112
160
|
agent_config (AgentConfig, optional): The configuration of the agent.
|
|
113
161
|
Defaults to AgentConfig(), which reads from environment variables.
|
|
162
|
+
chat_history (Tuple[str, str], optional): A list of user/agent chat pairs to initialize the agent memory.
|
|
114
163
|
"""
|
|
115
164
|
self.agent_config = agent_config or AgentConfig()
|
|
116
165
|
self.agent_type = self.agent_config.agent_type
|
|
117
|
-
self.tools = tools
|
|
166
|
+
self.tools = tools
|
|
167
|
+
if not any(tool.metadata.name == 'get_current_date' for tool in self.tools):
|
|
168
|
+
self.tools += [ToolsFactory().create_tool(get_current_date)]
|
|
118
169
|
self.llm = get_llm(LLMRole.MAIN, config=self.agent_config)
|
|
119
170
|
self._custom_instructions = custom_instructions
|
|
120
171
|
self._topic = topic
|
|
@@ -135,7 +186,14 @@ class Agent:
|
|
|
135
186
|
self.llm.callback_manager = callback_manager
|
|
136
187
|
self.verbose = verbose
|
|
137
188
|
|
|
138
|
-
|
|
189
|
+
if chat_history:
|
|
190
|
+
msg_history = []
|
|
191
|
+
for inx, text in enumerate(chat_history):
|
|
192
|
+
role = MessageRole.USER if inx % 2 == 0 else MessageRole.ASSISTANT
|
|
193
|
+
msg_history.append(ChatMessage.from_str(content=text, role=role))
|
|
194
|
+
self.memory = ChatMemoryBuffer.from_defaults(token_limit=128000, chat_history=msg_history)
|
|
195
|
+
else:
|
|
196
|
+
self.memory = ChatMemoryBuffer.from_defaults(token_limit=128000)
|
|
139
197
|
if self.agent_type == AgentType.REACT:
|
|
140
198
|
prompt = _get_prompt(REACT_PROMPT_TEMPLATE, topic, custom_instructions)
|
|
141
199
|
self.agent = ReActAgent.from_tools(
|
|
@@ -219,7 +277,10 @@ class Agent:
|
|
|
219
277
|
|
|
220
278
|
# Compare tools
|
|
221
279
|
if self.tools != other.tools:
|
|
222
|
-
print(
|
|
280
|
+
print(
|
|
281
|
+
"Comparison failed: tools differ."
|
|
282
|
+
f"(self.tools: {[t.metadata.name for t in self.tools]}, "
|
|
283
|
+
f"other.tools: {[t.metadata.name for t in other.tools]})")
|
|
223
284
|
return False
|
|
224
285
|
|
|
225
286
|
# Compare topic
|
|
@@ -263,6 +324,7 @@ class Agent:
|
|
|
263
324
|
agent_progress_callback: Optional[Callable[[AgentStatusType, str], None]] = None,
|
|
264
325
|
query_logging_callback: Optional[Callable[[str, str], None]] = None,
|
|
265
326
|
agent_config: AgentConfig = AgentConfig(),
|
|
327
|
+
chat_history: Optional[list[Tuple[str, str]]] = None,
|
|
266
328
|
) -> "Agent":
|
|
267
329
|
"""
|
|
268
330
|
Create an agent from tools, agent type, and language model.
|
|
@@ -277,6 +339,7 @@ class Agent:
|
|
|
277
339
|
update_func (Callable): old name for agent_progress_callback. Will be deprecated in future.
|
|
278
340
|
query_logging_callback (Callable): A callback function the code calls upon completion of a query
|
|
279
341
|
agent_config (AgentConfig, optional): The configuration of the agent.
|
|
342
|
+
chat_history (Tuple[str, str], optional): A list of user/agent chat pairs to initialize the agent memory.
|
|
280
343
|
|
|
281
344
|
Returns:
|
|
282
345
|
Agent: An instance of the Agent class.
|
|
@@ -285,7 +348,8 @@ class Agent:
|
|
|
285
348
|
tools=tools, topic=topic, custom_instructions=custom_instructions,
|
|
286
349
|
verbose=verbose, agent_progress_callback=agent_progress_callback,
|
|
287
350
|
query_logging_callback=query_logging_callback,
|
|
288
|
-
update_func=update_func, agent_config=agent_config
|
|
351
|
+
update_func=update_func, agent_config=agent_config,
|
|
352
|
+
chat_history=chat_history,
|
|
289
353
|
)
|
|
290
354
|
|
|
291
355
|
@classmethod
|
|
@@ -322,7 +386,7 @@ class Agent:
|
|
|
322
386
|
vectara_temperature: Optional[float] = None,
|
|
323
387
|
vectara_frequency_penalty: Optional[float] = None,
|
|
324
388
|
vectara_presence_penalty: Optional[float] = None,
|
|
325
|
-
vectara_save_history: bool =
|
|
389
|
+
vectara_save_history: bool = True,
|
|
326
390
|
) -> "Agent":
|
|
327
391
|
"""
|
|
328
392
|
Create an agent from a single Vectara corpus
|
|
@@ -383,6 +447,10 @@ class Agent:
|
|
|
383
447
|
) # type: ignore
|
|
384
448
|
query_args = create_model("QueryArgs", **field_definitions) # type: ignore
|
|
385
449
|
|
|
450
|
+
# tool name must be valid Python function name
|
|
451
|
+
if tool_name:
|
|
452
|
+
tool_name = re.sub(r"[^A-Za-z0-9_]", "_", tool_name)
|
|
453
|
+
|
|
386
454
|
vectara_tool = vec_factory.create_rag_tool(
|
|
387
455
|
tool_name=tool_name or f"vectara_{vectara_corpus_key}",
|
|
388
456
|
tool_description=f"""
|
|
@@ -414,6 +482,7 @@ class Agent:
|
|
|
414
482
|
presence_penalty=vectara_presence_penalty,
|
|
415
483
|
save_history=vectara_save_history,
|
|
416
484
|
include_citations=True,
|
|
485
|
+
verbose=verbose,
|
|
417
486
|
)
|
|
418
487
|
|
|
419
488
|
assistant_instructions = f"""
|
|
@@ -583,12 +652,13 @@ class Agent:
|
|
|
583
652
|
|
|
584
653
|
for tool in self.tools:
|
|
585
654
|
# Serialize each tool's metadata, function, and dynamic model schema (QueryArgs)
|
|
655
|
+
# TODO: deal with tools that have weakref (e.g. db_tools); for now those cannot be serialized.
|
|
586
656
|
tool_dict = {
|
|
587
657
|
"tool_type": tool.metadata.tool_type.value,
|
|
588
658
|
"name": tool.metadata.name,
|
|
589
659
|
"description": tool.metadata.description,
|
|
590
|
-
"fn":
|
|
591
|
-
"async_fn":
|
|
660
|
+
"fn": pickle.dumps(tool.fn).decode("latin-1") if tool.fn else None, # Serialize fn
|
|
661
|
+
"async_fn": pickle.dumps(tool.async_fn).decode("latin-1")
|
|
592
662
|
if tool.async_fn
|
|
593
663
|
else None, # Serialize async_fn
|
|
594
664
|
"fn_schema": tool.metadata.fn_schema.model_json_schema()
|
|
@@ -599,7 +669,7 @@ class Agent:
|
|
|
599
669
|
|
|
600
670
|
return {
|
|
601
671
|
"agent_type": self.agent_type.value,
|
|
602
|
-
"memory":
|
|
672
|
+
"memory": pickle.dumps(self.agent.memory).decode("latin-1"),
|
|
603
673
|
"tools": tool_info,
|
|
604
674
|
"topic": self._topic,
|
|
605
675
|
"custom_instructions": self._custom_instructions,
|
|
@@ -613,39 +683,30 @@ class Agent:
|
|
|
613
683
|
agent_config = AgentConfig.from_dict(data["agent_config"])
|
|
614
684
|
tools = []
|
|
615
685
|
|
|
616
|
-
json_type_to_python = {
|
|
617
|
-
"string": str,
|
|
618
|
-
"integer": int,
|
|
619
|
-
"boolean": bool,
|
|
620
|
-
"array": list,
|
|
621
|
-
"object": dict,
|
|
622
|
-
"number": float,
|
|
623
|
-
}
|
|
624
|
-
|
|
625
686
|
for tool_data in data["tools"]:
|
|
626
687
|
# Recreate the dynamic model using the schema info
|
|
627
688
|
if tool_data.get("fn_schema"):
|
|
628
689
|
field_definitions = {}
|
|
629
690
|
for field, values in tool_data["fn_schema"]["properties"].items():
|
|
691
|
+
# Instead of checking for 'type', use the helper:
|
|
692
|
+
field_type = get_field_type(values)
|
|
693
|
+
# If there's a default value, include it.
|
|
630
694
|
if "default" in values:
|
|
631
695
|
field_definitions[field] = (
|
|
632
|
-
|
|
633
|
-
Field(
|
|
634
|
-
|
|
635
|
-
default=values["default"],
|
|
636
|
-
),
|
|
637
|
-
) # type: ignore
|
|
696
|
+
field_type,
|
|
697
|
+
Field(description=values.get("description", ""), default=values["default"]),
|
|
698
|
+
)
|
|
638
699
|
else:
|
|
639
700
|
field_definitions[field] = (
|
|
640
|
-
|
|
641
|
-
Field(description=values
|
|
642
|
-
)
|
|
701
|
+
field_type,
|
|
702
|
+
Field(description=values.get("description", "")),
|
|
703
|
+
)
|
|
643
704
|
query_args_model = create_model("QueryArgs", **field_definitions) # type: ignore
|
|
644
705
|
else:
|
|
645
706
|
query_args_model = create_model("QueryArgs")
|
|
646
707
|
|
|
647
|
-
fn =
|
|
648
|
-
async_fn =
|
|
708
|
+
fn = pickle.loads(tool_data["fn"].encode("latin-1")) if tool_data["fn"] else None
|
|
709
|
+
async_fn = pickle.loads(tool_data["async_fn"].encode("latin-1")) if tool_data["async_fn"] else None
|
|
649
710
|
|
|
650
711
|
tool = VectaraTool.from_defaults(
|
|
651
712
|
name=tool_data["name"],
|
|
@@ -664,7 +725,7 @@ class Agent:
|
|
|
664
725
|
custom_instructions=data["custom_instructions"],
|
|
665
726
|
verbose=data["verbose"],
|
|
666
727
|
)
|
|
667
|
-
memory =
|
|
728
|
+
memory = pickle.loads(data["memory"].encode("latin-1")) if data.get("memory") else None
|
|
668
729
|
if memory:
|
|
669
730
|
agent.agent.memory = memory
|
|
670
731
|
return agent
|
vectara_agentic/agent_config.py
CHANGED
|
@@ -44,6 +44,15 @@ class AgentConfig:
|
|
|
44
44
|
default_factory=lambda: os.getenv("VECTARA_AGENTIC_TOOL_MODEL_NAME", "")
|
|
45
45
|
)
|
|
46
46
|
|
|
47
|
+
# Params for Private LLM endpoint if used
|
|
48
|
+
private_llm_api_base: str = field(
|
|
49
|
+
default_factory=lambda: os.getenv("VECTARA_AGENTIC_PRIVATE_LLM_API_BASE",
|
|
50
|
+
"http://private-endpoint.company.com:5000/v1")
|
|
51
|
+
)
|
|
52
|
+
private_llm_api_key: str = field(
|
|
53
|
+
default_factory=lambda: os.getenv("VECTARA_AGENTIC_PRIVATE_LLM_API_KEY", "<private-api-key>")
|
|
54
|
+
)
|
|
55
|
+
|
|
47
56
|
# Observer
|
|
48
57
|
observer: ObserverType = field(
|
|
49
58
|
default_factory=lambda: ObserverType(
|
vectara_agentic/tools.py
CHANGED
|
@@ -110,6 +110,9 @@ class VectaraTool(FunctionTool):
|
|
|
110
110
|
if self.metadata.tool_type != other.metadata.tool_type:
|
|
111
111
|
return False
|
|
112
112
|
|
|
113
|
+
if self.metadata.name != other.metadata.name or self.metadata.description != other.metadata.description:
|
|
114
|
+
return False
|
|
115
|
+
|
|
113
116
|
# Check if fn_schema is an instance of a BaseModel or a class itself (metaclass)
|
|
114
117
|
self_schema_dict = self.metadata.fn_schema.model_fields
|
|
115
118
|
other_schema_dict = other.metadata.fn_schema.model_fields
|
|
@@ -252,7 +255,10 @@ def _build_filter_string(kwargs: Dict[str, Any], tool_args_type: Dict[str, dict]
|
|
|
252
255
|
filter_parts.append(f"{prefix}.{key}='{val_str}'")
|
|
253
256
|
|
|
254
257
|
filter_str = " AND ".join(filter_parts)
|
|
255
|
-
|
|
258
|
+
if fixed_filter and filter_str:
|
|
259
|
+
return f"({fixed_filter}) AND ({filter_str})"
|
|
260
|
+
else:
|
|
261
|
+
return fixed_filter or filter_str
|
|
256
262
|
|
|
257
263
|
class VectaraToolFactory:
|
|
258
264
|
"""
|
|
@@ -294,7 +300,7 @@ class VectaraToolFactory:
|
|
|
294
300
|
mmr_diversity_bias: float = 0.2,
|
|
295
301
|
udf_expression: str = None,
|
|
296
302
|
rerank_chain: List[Dict] = None,
|
|
297
|
-
save_history: bool =
|
|
303
|
+
save_history: bool = True,
|
|
298
304
|
verbose: bool = False,
|
|
299
305
|
) -> VectaraTool:
|
|
300
306
|
"""
|
|
@@ -426,7 +432,7 @@ class VectaraToolFactory:
|
|
|
426
432
|
|
|
427
433
|
# Create the tool function signature string
|
|
428
434
|
fields = []
|
|
429
|
-
for name, field in tool_args_schema.
|
|
435
|
+
for name, field in tool_args_schema.model_fields.items():
|
|
430
436
|
annotation = field.annotation
|
|
431
437
|
type_name = annotation.__name__ if hasattr(annotation, '__name__') else str(annotation)
|
|
432
438
|
fields.append(f"{name}: {type_name}")
|
|
@@ -677,7 +683,7 @@ class VectaraToolFactory:
|
|
|
677
683
|
|
|
678
684
|
# Create the tool function signature string
|
|
679
685
|
fields = []
|
|
680
|
-
for name, field in tool_args_schema.
|
|
686
|
+
for name, field in tool_args_schema.model_fields.items():
|
|
681
687
|
annotation = field.annotation
|
|
682
688
|
type_name = annotation.__name__ if hasattr(annotation, '__name__') else str(annotation)
|
|
683
689
|
fields.append(f"{name}: {type_name}")
|
vectara_agentic/tools_catalog.py
CHANGED
vectara_agentic/types.py
CHANGED
vectara_agentic/utils.py
CHANGED
|
@@ -112,6 +112,10 @@ def get_llm(
|
|
|
112
112
|
elif model_provider == ModelProvider.COHERE:
|
|
113
113
|
from llama_index.llms.cohere import Cohere
|
|
114
114
|
llm = Cohere(model=model_name, temperature=0)
|
|
115
|
+
elif model_provider == ModelProvider.PRIVATE:
|
|
116
|
+
from llama_index.llms.openai_like import OpenAILike
|
|
117
|
+
llm = OpenAILike(model=model_name, temperature=0, is_function_calling_model=True,is_chat_model=True,
|
|
118
|
+
api_base=config.private_llm_api_base, api_key=config.private_llm_api_key)
|
|
115
119
|
else:
|
|
116
120
|
raise ValueError(f"Unknown LLM provider: {model_provider}")
|
|
117
121
|
return llm
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.2
|
|
2
2
|
Name: vectara_agentic
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.1
|
|
4
4
|
Summary: A Python package for creating AI Assistants and AI Agents with Vectara
|
|
5
5
|
Home-page: https://github.com/vectara/py-vectara-agentic
|
|
6
6
|
Author: Ofer Mendelevitch
|
|
@@ -51,7 +51,7 @@ Requires-Dist: pydantic==2.10.3
|
|
|
51
51
|
Requires-Dist: retrying==1.3.4
|
|
52
52
|
Requires-Dist: python-dotenv==1.0.1
|
|
53
53
|
Requires-Dist: tiktoken==0.8.0
|
|
54
|
-
Requires-Dist:
|
|
54
|
+
Requires-Dist: cloudpickle>=3.1.1
|
|
55
55
|
Requires-Dist: httpx==0.27.2
|
|
56
56
|
Dynamic: author
|
|
57
57
|
Dynamic: author-email
|
|
@@ -135,7 +135,7 @@ from vectara_agentic.tools import VectaraToolFactory
|
|
|
135
135
|
vec_factory = VectaraToolFactory(
|
|
136
136
|
vectara_api_key=os.environ['VECTARA_API_KEY'],
|
|
137
137
|
vectara_customer_id=os.environ['VECTARA_CUSTOMER_ID'],
|
|
138
|
-
|
|
138
|
+
vectara_corpus_key=os.environ['VECTARA_CORPUS_KEY']
|
|
139
139
|
)
|
|
140
140
|
```
|
|
141
141
|
|
|
@@ -315,6 +315,10 @@ def mult_func(x, y):
|
|
|
315
315
|
mult_tool = ToolsFactory().create_tool(mult_func)
|
|
316
316
|
```
|
|
317
317
|
|
|
318
|
+
Note: When you define your own Python functions as tools, implement them at the top module level,
|
|
319
|
+
and not as nested functions. Nested functions are not supported if you use serialization
|
|
320
|
+
(dumps/loads or from_dict/to_dict).
|
|
321
|
+
|
|
318
322
|
## 🛠️ Configuration
|
|
319
323
|
|
|
320
324
|
## Configuring Vectara-agentic
|
|
@@ -352,10 +356,31 @@ If any of these are not provided, `AgentConfig` first tries to read the values f
|
|
|
352
356
|
|
|
353
357
|
When creating a `VectaraToolFactory`, you can pass in a `vectara_api_key`, `vectara_customer_id`, and `vectara_corpus_id` to the factory.
|
|
354
358
|
|
|
355
|
-
If not passed in, it will be taken from the environment variables (`VECTARA_API_KEY
|
|
359
|
+
If not passed in, it will be taken from the environment variables (`VECTARA_API_KEY` and `VECTARA_CORPUS_KEY`). Note that `VECTARA_CORPUS_KEY` can be a single KEY or a comma-separated list of KEYs (if you want to query multiple corpora).
|
|
356
360
|
|
|
357
361
|
These values will be used as credentials when creating Vectara tools - in `create_rag_tool()` and `create_search_tool()`.
|
|
358
362
|
|
|
363
|
+
## Setting up a privately hosted LLM
|
|
364
|
+
|
|
365
|
+
If you want to setup vectara-agentic to use your own self-hosted LLM endpoint, follow the example below
|
|
366
|
+
|
|
367
|
+
```python
|
|
368
|
+
config = AgentConfig(
|
|
369
|
+
agent_type=AgentType.REACT,
|
|
370
|
+
main_llm_provider=ModelProvider.PRIVATE,
|
|
371
|
+
main_llm_model_name="meta-llama/Meta-Llama-3.1-8B-Instruct",
|
|
372
|
+
private_llm_api_base="http://vllm-server.company.com/v1",
|
|
373
|
+
private_llm_api_key="TEST_API_KEY",
|
|
374
|
+
)
|
|
375
|
+
agent = Agent(agent_config=config, tools=tools, topic=topic,
|
|
376
|
+
custom_instructions=custom_instructions)
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
In this case we specify the Main LLM provider to be privately hosted with Llama-3.1-8B as the model.
|
|
380
|
+
- The `ModelProvider.PRIVATE` specifies a privately hosted LLM.
|
|
381
|
+
- The `private_llm_api_base` specifies the api endpoint to use, and the `private_llm_api_key`
|
|
382
|
+
specifies the private API key requires to use this service.
|
|
383
|
+
|
|
359
384
|
## ℹ️ Additional Information
|
|
360
385
|
|
|
361
386
|
### About Custom Instructions for your Agent
|
|
@@ -376,6 +401,8 @@ The `Agent` class defines a few helpful methods to help you understand the inter
|
|
|
376
401
|
|
|
377
402
|
The `Agent` class supports serialization. Use the `dumps()` to serialize and `loads()` to read back from a serialized stream.
|
|
378
403
|
|
|
404
|
+
Note: due to cloudpickle limitations, if a tool contains Python `weakref` objects, serialization won't work and an exception will be raised.
|
|
405
|
+
|
|
379
406
|
### Observability
|
|
380
407
|
|
|
381
408
|
vectara-agentic supports observability via the existing integration of LlamaIndex and Arize Phoenix.
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
+
tests/endpoint.py,sha256=rnHyXEZhjipyR2Stj2Mum331REChWuhmn5WPpyDryV0,1291
|
|
3
|
+
tests/test_agent.py,sha256=SzSE1T_9PyIs-LUjj-fJjfGDlpJBzHUigyVX-KEhmJ4,4967
|
|
4
|
+
tests/test_private_llm.py,sha256=b7RrOHHsTQKARHskCbh2I4f_LmjZmD5bdk1oEWGhP7s,2150
|
|
5
|
+
tests/test_tools.py,sha256=lPihJ5mRdK66exWqDFRPEYIM2kUDeGtoaiG78UH5WMs,3499
|
|
6
|
+
vectara_agentic/__init__.py,sha256=ADH4fPKLbpGNYYYszv3c3QDOjPToPE_qh3LpkH_seCU,430
|
|
7
|
+
vectara_agentic/_callback.py,sha256=jpzHqnl297k2qajYc-6nkPtIPtgVLpVWYEISHS7ySlM,9186
|
|
8
|
+
vectara_agentic/_observability.py,sha256=HeQYJIkqPLW3EWHiXHatkaJzo08IQGESKujdeWTuRgk,3805
|
|
9
|
+
vectara_agentic/_prompts.py,sha256=7xOcRf9XNtpfFpDIUzgb-yMQ516K8X7bAzayAp406FU,6595
|
|
10
|
+
vectara_agentic/_version.py,sha256=r1kR7ilT4Tx4v5m7GtBI1hwUMe-C5VRwnozb3l382_0,65
|
|
11
|
+
vectara_agentic/agent.py,sha256=LMnJJfw2udDrsZAVlYpji0_rkmasHOsCuv206UJpH7Q,32007
|
|
12
|
+
vectara_agentic/agent_config.py,sha256=yof3zU8OgYE5441EAwcoDBpTHDM5lNN-CyeO0LrT4-c,3350
|
|
13
|
+
vectara_agentic/agent_endpoint.py,sha256=QIMejCLlpW2qzXxeDAxv3anF46XMDdVMdKGWhJh3azY,1996
|
|
14
|
+
vectara_agentic/db_tools.py,sha256=3_hPrutNIGEeU2kH503GjcYbtAMsK6BidQLIm6DT6C8,3591
|
|
15
|
+
vectara_agentic/tools.py,sha256=gI6zsVNh2SfAOqeQ5ZzAmf8NxkmtV6NUPJsTN7Cnb7o,39440
|
|
16
|
+
vectara_agentic/tools_catalog.py,sha256=0gfF-JzTEnFS8e_x2uA6mvcvjVkY9ygRskxJetKGtrs,5231
|
|
17
|
+
vectara_agentic/types.py,sha256=Qy7c7gSXJbvzddzhSRx2Flaf6a3go8u2LW17IKNxkKI,1603
|
|
18
|
+
vectara_agentic/utils.py,sha256=ZhS82hA9yconr9yqDJ0GCBUjRxc06ZTlFaBzF67Yb3Y,5008
|
|
19
|
+
vectara_agentic-0.2.1.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
|
20
|
+
vectara_agentic-0.2.1.dist-info/METADATA,sha256=ZZpRSVHVy9gI9QsNHFKKtdYyl3DWbR_h_LyD6oX9bJw,21857
|
|
21
|
+
vectara_agentic-0.2.1.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
|
|
22
|
+
vectara_agentic-0.2.1.dist-info/top_level.txt,sha256=Y7TQTFdOYGYodQRltUGRieZKIYuzeZj2kHqAUpfCUfg,22
|
|
23
|
+
vectara_agentic-0.2.1.dist-info/RECORD,,
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
-
tests/test_agent.py,sha256=bhKwmWsYTwTCrI-D4hXGKZPPDzgOJF3XtjhYo-bV2xc,3963
|
|
3
|
-
tests/test_tools.py,sha256=Nn8iTabsCspABdt3eW13lAy8GJL1xRS42XQxBhwN__I,2762
|
|
4
|
-
vectara_agentic/__init__.py,sha256=ADH4fPKLbpGNYYYszv3c3QDOjPToPE_qh3LpkH_seCU,430
|
|
5
|
-
vectara_agentic/_callback.py,sha256=jpzHqnl297k2qajYc-6nkPtIPtgVLpVWYEISHS7ySlM,9186
|
|
6
|
-
vectara_agentic/_observability.py,sha256=HeQYJIkqPLW3EWHiXHatkaJzo08IQGESKujdeWTuRgk,3805
|
|
7
|
-
vectara_agentic/_prompts.py,sha256=mgGx3zUJPtpS1epBLtl0BnoLeE7wb6AX1SX6dFNaTTQ,6368
|
|
8
|
-
vectara_agentic/_version.py,sha256=BLvF264tVnfoZW3j2NXD3gU6WcA1j4dfS2TtMqZ6oJs,65
|
|
9
|
-
vectara_agentic/agent.py,sha256=57VftipZRZXAwf6LXdyY0zsyCInLcR0ZGvYr1SwTakI,29354
|
|
10
|
-
vectara_agentic/agent_config.py,sha256=9P9lyFAAXLX1ft2dBQ6tYN7dKzp7SC7B-h-DnhUHsSg,2941
|
|
11
|
-
vectara_agentic/agent_endpoint.py,sha256=QIMejCLlpW2qzXxeDAxv3anF46XMDdVMdKGWhJh3azY,1996
|
|
12
|
-
vectara_agentic/db_tools.py,sha256=3_hPrutNIGEeU2kH503GjcYbtAMsK6BidQLIm6DT6C8,3591
|
|
13
|
-
vectara_agentic/tools.py,sha256=xhi8lvs351_8WQPQu6oOMdwond9MqqqVt9RHR4NjazI,39238
|
|
14
|
-
vectara_agentic/tools_catalog.py,sha256=O3vYFpZ5EQeh37X70TiHepXFGN5yGk4_d7CkrkLeUhk,5205
|
|
15
|
-
vectara_agentic/types.py,sha256=8yeRHr1iYV9lmsWUwtfreHUvYaw1u9CSiQIhBkjcWxc,1579
|
|
16
|
-
vectara_agentic/utils.py,sha256=eDEOUpCkb0r216LxSLkamll2NmJhu76Z1TgQFbAVq7A,4690
|
|
17
|
-
vectara_agentic-0.2.0.dist-info/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
|
18
|
-
vectara_agentic-0.2.0.dist-info/METADATA,sha256=iHGfo8TF7CfneXOKNTXdWoYDuiOILc77uru_HzPX1H0,20564
|
|
19
|
-
vectara_agentic-0.2.0.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
|
|
20
|
-
vectara_agentic-0.2.0.dist-info/top_level.txt,sha256=Y7TQTFdOYGYodQRltUGRieZKIYuzeZj2kHqAUpfCUfg,22
|
|
21
|
-
vectara_agentic-0.2.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|