ToolAgents 0.0.1__tar.gz
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.
- toolagents-0.0.1/LICENSE +21 -0
- toolagents-0.0.1/PKG-INFO +199 -0
- toolagents-0.0.1/ReadMe.md +173 -0
- toolagents-0.0.1/pyproject.toml +39 -0
- toolagents-0.0.1/setup.cfg +4 -0
- toolagents-0.0.1/src/ToolAgents/__init__.py +2 -0
- toolagents-0.0.1/src/ToolAgents/agents/__init__.py +3 -0
- toolagents-0.0.1/src/ToolAgents/agents/chat_api_agent.py +180 -0
- toolagents-0.0.1/src/ToolAgents/agents/mistral_agent.py +214 -0
- toolagents-0.0.1/src/ToolAgents/agents/ollama_agent.py +162 -0
- toolagents-0.0.1/src/ToolAgents/function_tool.py +486 -0
- toolagents-0.0.1/src/ToolAgents/provider/__init__.py +4 -0
- toolagents-0.0.1/src/ToolAgents/provider/chat_api_with_tools.py +682 -0
- toolagents-0.0.1/src/ToolAgents/provider/llama_cpp_server.py +157 -0
- toolagents-0.0.1/src/ToolAgents/provider/tgi_server.py +144 -0
- toolagents-0.0.1/src/ToolAgents/provider/vllm_server.py +123 -0
- toolagents-0.0.1/src/ToolAgents/utilities/__init__.py +2 -0
- toolagents-0.0.1/src/ToolAgents/utilities/chat_history.py +107 -0
- toolagents-0.0.1/src/ToolAgents/utilities/documentation_generation.py +490 -0
- toolagents-0.0.1/src/ToolAgents.egg-info/PKG-INFO +199 -0
- toolagents-0.0.1/src/ToolAgents.egg-info/SOURCES.txt +26 -0
- toolagents-0.0.1/src/ToolAgents.egg-info/dependency_links.txt +1 -0
- toolagents-0.0.1/src/ToolAgents.egg-info/requires.txt +12 -0
- toolagents-0.0.1/src/ToolAgents.egg-info/top_level.txt +1 -0
- toolagents-0.0.1/tests/test_chat_api_agent.py +34 -0
- toolagents-0.0.1/tests/test_mistral_agent.py +31 -0
- toolagents-0.0.1/tests/test_ollama_agent.py +29 -0
- toolagents-0.0.1/tests/test_tools.py +216 -0
toolagents-0.0.1/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Maximilian Winter
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: ToolAgents
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: ToolAgents is a lightweight and flexible framework for creating function-calling agents with various language models and APIs.
|
|
5
|
+
Author-email: Maximilian Winter <maximilian.winter.91@gmail.com>
|
|
6
|
+
Project-URL: Homepage, https://github.com/Maximilian-Winter/ToolAgents
|
|
7
|
+
Project-URL: Bug Tracker, https://github.com/Maximilian-Winter/ToolAgents/issues
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Requires-Python: >=3.10
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
License-File: LICENSE
|
|
14
|
+
Requires-Dist: pydantic>=2.5.3
|
|
15
|
+
Requires-Dist: requests>=2.31.0
|
|
16
|
+
Requires-Dist: docstring_parser
|
|
17
|
+
Requires-Dist: aiohttp
|
|
18
|
+
Requires-Dist: mistral-common
|
|
19
|
+
Requires-Dist: openai
|
|
20
|
+
Requires-Dist: transformers
|
|
21
|
+
Requires-Dist: sentencepiece
|
|
22
|
+
Requires-Dist: protobuf
|
|
23
|
+
Requires-Dist: anthropic
|
|
24
|
+
Requires-Dist: ollama
|
|
25
|
+
Requires-Dist: groq
|
|
26
|
+
|
|
27
|
+
# ToolAgents
|
|
28
|
+
|
|
29
|
+
ToolAgents is a lightweight and flexible framework for creating function-calling agents with various language models and APIs. It provides a unified interface for integrating different LLM providers and executing function calls seamlessly.
|
|
30
|
+
|
|
31
|
+
## Features
|
|
32
|
+
|
|
33
|
+
- Support for multiple LLM providers:
|
|
34
|
+
- llama.cpp servers
|
|
35
|
+
- Hugging Face's Text Generation Interface (TGI) servers
|
|
36
|
+
- vLLM servers
|
|
37
|
+
- OpenAI API
|
|
38
|
+
- Anthropic API
|
|
39
|
+
- Ollama (with Tool calling support)
|
|
40
|
+
- Easy-to-use interface for passing functions, Pydantic models, and tools to LLMs
|
|
41
|
+
- Streamlined process for function calling and result handling
|
|
42
|
+
- Flexible agent types:
|
|
43
|
+
- MistralAgent for llama.cpp, TGI, and vLLM servers
|
|
44
|
+
- ChatAPIAgent for OpenAI and Anthropic APIs
|
|
45
|
+
- OllamaAgent for Ollama integration
|
|
46
|
+
|
|
47
|
+
## Installation
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
pip install toolagents
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Usage
|
|
55
|
+
|
|
56
|
+
### MistralAgent with llama.cpp Server
|
|
57
|
+
|
|
58
|
+
```python
|
|
59
|
+
from ToolAgents.agents import MistralAgent
|
|
60
|
+
from ToolAgents.provider import LlamaCppServerProvider, LlamaCppSamplingSettings
|
|
61
|
+
from ToolAgents.tests.test_tools import calculator_function_tool, current_datetime_function_tool, get_weather_function_tool
|
|
62
|
+
|
|
63
|
+
# Initialize the provider and agent
|
|
64
|
+
provider = LlamaCppServerProvider("http://127.0.0.1:8080/")
|
|
65
|
+
agent = MistralAgent(llm_provider=provider, debug_output=False,
|
|
66
|
+
system_prompt="You are a helpful assistant.")
|
|
67
|
+
|
|
68
|
+
# Configure settings
|
|
69
|
+
settings = LlamaCppSamplingSettings()
|
|
70
|
+
settings.temperature = 0.3
|
|
71
|
+
settings.top_p = 1.0
|
|
72
|
+
settings.max_tokens = 4096
|
|
73
|
+
|
|
74
|
+
# Define tools
|
|
75
|
+
tools = [calculator_function_tool, current_datetime_function_tool, get_weather_function_tool]
|
|
76
|
+
|
|
77
|
+
# Get a response
|
|
78
|
+
result = agent.get_streaming_response(
|
|
79
|
+
"Perform the following tasks: Get the current weather in Celsius in London, New York, and at the North Pole. "
|
|
80
|
+
"Solve these calculations: 42 * 42, 74 + 26, 7 * 26, 4 + 6, and 96/8.",
|
|
81
|
+
sampling_settings=settings,
|
|
82
|
+
tools=tools
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
for token in result:
|
|
86
|
+
print(token, end="", flush=True)
|
|
87
|
+
print()
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### ChatAPIAgent with Anthropic API
|
|
91
|
+
|
|
92
|
+
```python
|
|
93
|
+
import os
|
|
94
|
+
from dotenv import load_dotenv
|
|
95
|
+
from ToolAgents.agents import ChatAPIAgent
|
|
96
|
+
from ToolAgents.provider import AnthropicChatAPI, AnthropicSettings
|
|
97
|
+
from ToolAgents.tests.test_tools import calculator_function_tool, current_datetime_function_tool, get_weather_function_tool
|
|
98
|
+
|
|
99
|
+
load_dotenv()
|
|
100
|
+
|
|
101
|
+
# Initialize the API and agent
|
|
102
|
+
api = AnthropicChatAPI(api_key=os.getenv("ANTHROPIC_API_KEY"), model="claude-3-sonnet-20240229")
|
|
103
|
+
agent = ChatAPIAgent(chat_api=api, system_prompt="You are a helpful assistant.")
|
|
104
|
+
|
|
105
|
+
# Configure settings
|
|
106
|
+
settings = AnthropicSettings()
|
|
107
|
+
settings.temperature = 0.45
|
|
108
|
+
settings.top_p = 0.85
|
|
109
|
+
|
|
110
|
+
# Define tools
|
|
111
|
+
tools = [calculator_function_tool, current_datetime_function_tool, get_weather_function_tool]
|
|
112
|
+
|
|
113
|
+
# Get a response
|
|
114
|
+
result = agent.get_response(
|
|
115
|
+
"Perform the following tasks: Get the current weather in Celsius in London, New York, and at the North Pole. "
|
|
116
|
+
"Solve these calculations: 42 * 42, 74 + 26, 7 * 26, 4 + 6, and 96/8.",
|
|
117
|
+
tools=tools,
|
|
118
|
+
settings=settings
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
print(result)
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### OllamaAgent
|
|
125
|
+
|
|
126
|
+
```python
|
|
127
|
+
from ToolAgents.agents import OllamaAgent
|
|
128
|
+
from ToolAgents.tests.test_tools import get_flight_times_tool
|
|
129
|
+
|
|
130
|
+
def run():
|
|
131
|
+
agent = OllamaAgent(model='mistral-nemo', system_prompt="You are a helpful assistant.", debug_output=False)
|
|
132
|
+
|
|
133
|
+
tools = [get_flight_times_tool]
|
|
134
|
+
|
|
135
|
+
response = agent.get_response(
|
|
136
|
+
message="What is the flight time from New York (NYC) to Los Angeles (LAX)?",
|
|
137
|
+
tools=tools,
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
print(response)
|
|
141
|
+
|
|
142
|
+
print("\nStreaming response:")
|
|
143
|
+
for chunk in agent.get_streaming_response(
|
|
144
|
+
message="What is the flight time from London (LHR) to New York (JFK)?",
|
|
145
|
+
tools=tools,
|
|
146
|
+
):
|
|
147
|
+
print(chunk, end='', flush=True)
|
|
148
|
+
|
|
149
|
+
if __name__ == "__main__":
|
|
150
|
+
run()
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
## Custom Tools
|
|
154
|
+
|
|
155
|
+
You can create custom tools using Pydantic models or function definitions. Here's an example of a custom calculator tool:
|
|
156
|
+
|
|
157
|
+
```python
|
|
158
|
+
from enum import Enum
|
|
159
|
+
from typing import Union
|
|
160
|
+
|
|
161
|
+
from pydantic import BaseModel, Field
|
|
162
|
+
from ToolAgents import FunctionTool
|
|
163
|
+
|
|
164
|
+
class MathOperation(Enum):
|
|
165
|
+
ADD = "add"
|
|
166
|
+
SUBTRACT = "subtract"
|
|
167
|
+
MULTIPLY = "multiply"
|
|
168
|
+
DIVIDE = "divide"
|
|
169
|
+
|
|
170
|
+
class Calculator(BaseModel):
|
|
171
|
+
"""
|
|
172
|
+
Perform a math operation on two numbers.
|
|
173
|
+
"""
|
|
174
|
+
number_one: Union[int, float] = Field(..., description="First number.")
|
|
175
|
+
operation: MathOperation = Field(..., description="Math operation to perform.")
|
|
176
|
+
number_two: Union[int, float] = Field(..., description="Second number.")
|
|
177
|
+
|
|
178
|
+
def run(self):
|
|
179
|
+
if self.operation == MathOperation.ADD:
|
|
180
|
+
return self.number_one + self.number_two
|
|
181
|
+
elif self.operation == MathOperation.SUBTRACT:
|
|
182
|
+
return self.number_one - self.number_two
|
|
183
|
+
elif self.operation == MathOperation.MULTIPLY:
|
|
184
|
+
return self.number_one * self.number_two
|
|
185
|
+
elif self.operation == MathOperation.DIVIDE:
|
|
186
|
+
return self.number_one / self.number_two
|
|
187
|
+
else:
|
|
188
|
+
raise ValueError("Unknown operation.")
|
|
189
|
+
|
|
190
|
+
calculator_tool = FunctionTool(Calculator)
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
## Contributing
|
|
194
|
+
|
|
195
|
+
Contributions to ToolAgents are welcome! Please feel free to submit pull requests, create issues, or suggest improvements.
|
|
196
|
+
|
|
197
|
+
## License
|
|
198
|
+
|
|
199
|
+
ToolAgents is released under the MIT License. See the [LICENSE](LICENSE) file for details.
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
# ToolAgents
|
|
2
|
+
|
|
3
|
+
ToolAgents is a lightweight and flexible framework for creating function-calling agents with various language models and APIs. It provides a unified interface for integrating different LLM providers and executing function calls seamlessly.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- Support for multiple LLM providers:
|
|
8
|
+
- llama.cpp servers
|
|
9
|
+
- Hugging Face's Text Generation Interface (TGI) servers
|
|
10
|
+
- vLLM servers
|
|
11
|
+
- OpenAI API
|
|
12
|
+
- Anthropic API
|
|
13
|
+
- Ollama (with Tool calling support)
|
|
14
|
+
- Easy-to-use interface for passing functions, Pydantic models, and tools to LLMs
|
|
15
|
+
- Streamlined process for function calling and result handling
|
|
16
|
+
- Flexible agent types:
|
|
17
|
+
- MistralAgent for llama.cpp, TGI, and vLLM servers
|
|
18
|
+
- ChatAPIAgent for OpenAI and Anthropic APIs
|
|
19
|
+
- OllamaAgent for Ollama integration
|
|
20
|
+
|
|
21
|
+
## Installation
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
pip install toolagents
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Usage
|
|
29
|
+
|
|
30
|
+
### MistralAgent with llama.cpp Server
|
|
31
|
+
|
|
32
|
+
```python
|
|
33
|
+
from ToolAgents.agents import MistralAgent
|
|
34
|
+
from ToolAgents.provider import LlamaCppServerProvider, LlamaCppSamplingSettings
|
|
35
|
+
from ToolAgents.tests.test_tools import calculator_function_tool, current_datetime_function_tool, get_weather_function_tool
|
|
36
|
+
|
|
37
|
+
# Initialize the provider and agent
|
|
38
|
+
provider = LlamaCppServerProvider("http://127.0.0.1:8080/")
|
|
39
|
+
agent = MistralAgent(llm_provider=provider, debug_output=False,
|
|
40
|
+
system_prompt="You are a helpful assistant.")
|
|
41
|
+
|
|
42
|
+
# Configure settings
|
|
43
|
+
settings = LlamaCppSamplingSettings()
|
|
44
|
+
settings.temperature = 0.3
|
|
45
|
+
settings.top_p = 1.0
|
|
46
|
+
settings.max_tokens = 4096
|
|
47
|
+
|
|
48
|
+
# Define tools
|
|
49
|
+
tools = [calculator_function_tool, current_datetime_function_tool, get_weather_function_tool]
|
|
50
|
+
|
|
51
|
+
# Get a response
|
|
52
|
+
result = agent.get_streaming_response(
|
|
53
|
+
"Perform the following tasks: Get the current weather in Celsius in London, New York, and at the North Pole. "
|
|
54
|
+
"Solve these calculations: 42 * 42, 74 + 26, 7 * 26, 4 + 6, and 96/8.",
|
|
55
|
+
sampling_settings=settings,
|
|
56
|
+
tools=tools
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
for token in result:
|
|
60
|
+
print(token, end="", flush=True)
|
|
61
|
+
print()
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### ChatAPIAgent with Anthropic API
|
|
65
|
+
|
|
66
|
+
```python
|
|
67
|
+
import os
|
|
68
|
+
from dotenv import load_dotenv
|
|
69
|
+
from ToolAgents.agents import ChatAPIAgent
|
|
70
|
+
from ToolAgents.provider import AnthropicChatAPI, AnthropicSettings
|
|
71
|
+
from ToolAgents.tests.test_tools import calculator_function_tool, current_datetime_function_tool, get_weather_function_tool
|
|
72
|
+
|
|
73
|
+
load_dotenv()
|
|
74
|
+
|
|
75
|
+
# Initialize the API and agent
|
|
76
|
+
api = AnthropicChatAPI(api_key=os.getenv("ANTHROPIC_API_KEY"), model="claude-3-sonnet-20240229")
|
|
77
|
+
agent = ChatAPIAgent(chat_api=api, system_prompt="You are a helpful assistant.")
|
|
78
|
+
|
|
79
|
+
# Configure settings
|
|
80
|
+
settings = AnthropicSettings()
|
|
81
|
+
settings.temperature = 0.45
|
|
82
|
+
settings.top_p = 0.85
|
|
83
|
+
|
|
84
|
+
# Define tools
|
|
85
|
+
tools = [calculator_function_tool, current_datetime_function_tool, get_weather_function_tool]
|
|
86
|
+
|
|
87
|
+
# Get a response
|
|
88
|
+
result = agent.get_response(
|
|
89
|
+
"Perform the following tasks: Get the current weather in Celsius in London, New York, and at the North Pole. "
|
|
90
|
+
"Solve these calculations: 42 * 42, 74 + 26, 7 * 26, 4 + 6, and 96/8.",
|
|
91
|
+
tools=tools,
|
|
92
|
+
settings=settings
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
print(result)
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### OllamaAgent
|
|
99
|
+
|
|
100
|
+
```python
|
|
101
|
+
from ToolAgents.agents import OllamaAgent
|
|
102
|
+
from ToolAgents.tests.test_tools import get_flight_times_tool
|
|
103
|
+
|
|
104
|
+
def run():
|
|
105
|
+
agent = OllamaAgent(model='mistral-nemo', system_prompt="You are a helpful assistant.", debug_output=False)
|
|
106
|
+
|
|
107
|
+
tools = [get_flight_times_tool]
|
|
108
|
+
|
|
109
|
+
response = agent.get_response(
|
|
110
|
+
message="What is the flight time from New York (NYC) to Los Angeles (LAX)?",
|
|
111
|
+
tools=tools,
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
print(response)
|
|
115
|
+
|
|
116
|
+
print("\nStreaming response:")
|
|
117
|
+
for chunk in agent.get_streaming_response(
|
|
118
|
+
message="What is the flight time from London (LHR) to New York (JFK)?",
|
|
119
|
+
tools=tools,
|
|
120
|
+
):
|
|
121
|
+
print(chunk, end='', flush=True)
|
|
122
|
+
|
|
123
|
+
if __name__ == "__main__":
|
|
124
|
+
run()
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
## Custom Tools
|
|
128
|
+
|
|
129
|
+
You can create custom tools using Pydantic models or function definitions. Here's an example of a custom calculator tool:
|
|
130
|
+
|
|
131
|
+
```python
|
|
132
|
+
from enum import Enum
|
|
133
|
+
from typing import Union
|
|
134
|
+
|
|
135
|
+
from pydantic import BaseModel, Field
|
|
136
|
+
from ToolAgents import FunctionTool
|
|
137
|
+
|
|
138
|
+
class MathOperation(Enum):
|
|
139
|
+
ADD = "add"
|
|
140
|
+
SUBTRACT = "subtract"
|
|
141
|
+
MULTIPLY = "multiply"
|
|
142
|
+
DIVIDE = "divide"
|
|
143
|
+
|
|
144
|
+
class Calculator(BaseModel):
|
|
145
|
+
"""
|
|
146
|
+
Perform a math operation on two numbers.
|
|
147
|
+
"""
|
|
148
|
+
number_one: Union[int, float] = Field(..., description="First number.")
|
|
149
|
+
operation: MathOperation = Field(..., description="Math operation to perform.")
|
|
150
|
+
number_two: Union[int, float] = Field(..., description="Second number.")
|
|
151
|
+
|
|
152
|
+
def run(self):
|
|
153
|
+
if self.operation == MathOperation.ADD:
|
|
154
|
+
return self.number_one + self.number_two
|
|
155
|
+
elif self.operation == MathOperation.SUBTRACT:
|
|
156
|
+
return self.number_one - self.number_two
|
|
157
|
+
elif self.operation == MathOperation.MULTIPLY:
|
|
158
|
+
return self.number_one * self.number_two
|
|
159
|
+
elif self.operation == MathOperation.DIVIDE:
|
|
160
|
+
return self.number_one / self.number_two
|
|
161
|
+
else:
|
|
162
|
+
raise ValueError("Unknown operation.")
|
|
163
|
+
|
|
164
|
+
calculator_tool = FunctionTool(Calculator)
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
## Contributing
|
|
168
|
+
|
|
169
|
+
Contributions to ToolAgents are welcome! Please feel free to submit pull requests, create issues, or suggest improvements.
|
|
170
|
+
|
|
171
|
+
## License
|
|
172
|
+
|
|
173
|
+
ToolAgents is released under the MIT License. See the [LICENSE](LICENSE) file for details.
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = [ "setuptools>=42"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "ToolAgents"
|
|
7
|
+
version = "0.0.1"
|
|
8
|
+
description = "ToolAgents is a lightweight and flexible framework for creating function-calling agents with various language models and APIs."
|
|
9
|
+
|
|
10
|
+
readme = "ReadMe.md"
|
|
11
|
+
dependencies = [
|
|
12
|
+
"pydantic>=2.5.3",
|
|
13
|
+
"requests>=2.31.0",
|
|
14
|
+
"docstring_parser",
|
|
15
|
+
"aiohttp",
|
|
16
|
+
"mistral-common",
|
|
17
|
+
"openai",
|
|
18
|
+
"transformers",
|
|
19
|
+
"sentencepiece",
|
|
20
|
+
"protobuf",
|
|
21
|
+
"anthropic",
|
|
22
|
+
"ollama",
|
|
23
|
+
"groq"
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
requires-python = ">=3.10"
|
|
27
|
+
classifiers = [ "Programming Language :: Python :: 3", "License :: OSI Approved :: MIT License", "Operating System :: OS Independent" ]
|
|
28
|
+
[[project.authors]]
|
|
29
|
+
name = "Maximilian Winter"
|
|
30
|
+
email = "maximilian.winter.91@gmail.com"
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
[project.urls]
|
|
34
|
+
Homepage = "https://github.com/Maximilian-Winter/ToolAgents"
|
|
35
|
+
"Bug Tracker" = "https://github.com/Maximilian-Winter/ToolAgents/issues"
|
|
36
|
+
|
|
37
|
+
[tool.setuptools.packages.find]
|
|
38
|
+
where = ["src"]
|
|
39
|
+
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import random
|
|
3
|
+
import string
|
|
4
|
+
from typing import Optional, Dict, List, Any
|
|
5
|
+
|
|
6
|
+
from ToolAgents import FunctionTool
|
|
7
|
+
from ToolAgents.provider.chat_api_with_tools import ChatAPI
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def generate_id(length=8):
|
|
11
|
+
# Characters to use in the ID
|
|
12
|
+
characters = string.ascii_letters + string.digits
|
|
13
|
+
# Random choice of characters
|
|
14
|
+
return "".join(random.choice(characters) for _ in range(length))
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class ChatAPIAgent:
|
|
18
|
+
def __init__(
|
|
19
|
+
self,
|
|
20
|
+
chat_api: ChatAPI,
|
|
21
|
+
system_prompt: Optional[str] = None,
|
|
22
|
+
debug_output: bool = False,
|
|
23
|
+
):
|
|
24
|
+
self.chat_api = chat_api
|
|
25
|
+
self.messages: List[Dict[str, Any]] = []
|
|
26
|
+
self.debug_output = debug_output
|
|
27
|
+
self.system_prompt = system_prompt
|
|
28
|
+
|
|
29
|
+
if system_prompt is not None:
|
|
30
|
+
self.messages.append({"role": "system", "content": system_prompt})
|
|
31
|
+
|
|
32
|
+
def get_response(
|
|
33
|
+
self,
|
|
34
|
+
message: Optional[str] = None,
|
|
35
|
+
tools: Optional[List[FunctionTool]] = None,
|
|
36
|
+
settings: Optional[Any] = None,
|
|
37
|
+
messages: Optional[List[Dict[str, Any]]] = None,
|
|
38
|
+
override_system_prompt: Optional[str] = None,
|
|
39
|
+
) -> str:
|
|
40
|
+
if tools is None:
|
|
41
|
+
tools = []
|
|
42
|
+
|
|
43
|
+
# Use provided messages if available, otherwise use internal messages
|
|
44
|
+
current_messages = messages if messages is not None else self.messages.copy()
|
|
45
|
+
|
|
46
|
+
# Override system prompt if provided
|
|
47
|
+
if override_system_prompt is not None:
|
|
48
|
+
current_messages = [msg for msg in current_messages if msg["role"] != "system"]
|
|
49
|
+
current_messages.insert(0, {"role": "system", "content": override_system_prompt})
|
|
50
|
+
elif self.system_prompt is not None and not any(msg["role"] == "system" for msg in current_messages):
|
|
51
|
+
current_messages.insert(0, {"role": "system", "content": self.system_prompt})
|
|
52
|
+
|
|
53
|
+
if message is not None:
|
|
54
|
+
current_messages.append({"role": "user", "content": message})
|
|
55
|
+
|
|
56
|
+
if self.debug_output:
|
|
57
|
+
print("Input messages:", json.dumps(current_messages, indent=2))
|
|
58
|
+
|
|
59
|
+
result = self.chat_api.get_response(current_messages, settings=settings, tools=tools)
|
|
60
|
+
|
|
61
|
+
try:
|
|
62
|
+
parsed_result = json.loads(result)
|
|
63
|
+
if "tool_calls" in parsed_result:
|
|
64
|
+
tool_calls = parsed_result["tool_calls"]
|
|
65
|
+
content = parsed_result.get("content", "")
|
|
66
|
+
|
|
67
|
+
tool_calls_prepared = []
|
|
68
|
+
tool_messages = []
|
|
69
|
+
for tool_call in tool_calls:
|
|
70
|
+
tool = next((t for t in tools if t.model.__name__ == tool_call["function"]["name"]), None)
|
|
71
|
+
if tool:
|
|
72
|
+
call_parameters = tool_call["function"]["arguments"]
|
|
73
|
+
if isinstance(call_parameters, str):
|
|
74
|
+
call_parameters = json.loads(call_parameters)
|
|
75
|
+
call = tool.model(**call_parameters)
|
|
76
|
+
output = call.run(**tool.additional_parameters)
|
|
77
|
+
tool_call_id = tool_call["function"].get("id", tool_call.get("id", generate_id(length=9)))
|
|
78
|
+
tool_calls_prepared.append(
|
|
79
|
+
self.chat_api.generate_tool_use_message(content=parsed_result["content"],
|
|
80
|
+
tool_call_id=tool_call_id,
|
|
81
|
+
tool_name=tool_call["function"]["name"],
|
|
82
|
+
tool_args=call_parameters))
|
|
83
|
+
tool_messages.append(
|
|
84
|
+
self.chat_api.generate_tool_response_message(
|
|
85
|
+
tool_call_id=tool_call_id,
|
|
86
|
+
tool_name=tool_call["function"]["name"],
|
|
87
|
+
tool_response=str(output)
|
|
88
|
+
)
|
|
89
|
+
)
|
|
90
|
+
if "role" in tool_calls_prepared[0]:
|
|
91
|
+
current_messages.extend(tool_calls_prepared)
|
|
92
|
+
else:
|
|
93
|
+
current_messages.append(
|
|
94
|
+
{"role": "assistant", "content": parsed_result["content"], "tool_calls": tool_calls_prepared})
|
|
95
|
+
current_messages.extend(tool_messages)
|
|
96
|
+
return self.get_response(settings=settings, tools=tools, messages=current_messages)
|
|
97
|
+
else:
|
|
98
|
+
current_messages.append({"role": "assistant", "content": result})
|
|
99
|
+
return result
|
|
100
|
+
except json.JSONDecodeError:
|
|
101
|
+
current_messages.append({"role": "assistant", "content": result})
|
|
102
|
+
return result
|
|
103
|
+
|
|
104
|
+
def get_streaming_response(
|
|
105
|
+
self,
|
|
106
|
+
message: Optional[str] = None,
|
|
107
|
+
tools: Optional[List[FunctionTool]] = None,
|
|
108
|
+
settings: Optional[Any] = None,
|
|
109
|
+
messages: Optional[List[Dict[str, Any]]] = None,
|
|
110
|
+
override_system_prompt: Optional[str] = None,
|
|
111
|
+
):
|
|
112
|
+
if tools is None:
|
|
113
|
+
tools = []
|
|
114
|
+
|
|
115
|
+
# Use provided messages if available, otherwise use internal messages
|
|
116
|
+
current_messages = messages if messages is not None else self.messages.copy()
|
|
117
|
+
|
|
118
|
+
# Override system prompt if provided
|
|
119
|
+
if override_system_prompt is not None:
|
|
120
|
+
current_messages = [msg for msg in current_messages if msg["role"] != "system"]
|
|
121
|
+
current_messages.insert(0, {"role": "system", "content": override_system_prompt})
|
|
122
|
+
elif self.system_prompt is not None and not any(msg["role"] == "system" for msg in current_messages):
|
|
123
|
+
current_messages.insert(0, {"role": "system", "content": self.system_prompt})
|
|
124
|
+
|
|
125
|
+
if message is not None:
|
|
126
|
+
current_messages.append({"role": "user", "content": message})
|
|
127
|
+
|
|
128
|
+
if self.debug_output:
|
|
129
|
+
print("Input messages:", json.dumps(current_messages, indent=2))
|
|
130
|
+
|
|
131
|
+
for chunk in self.chat_api.get_streaming_response(current_messages, settings=settings, tools=tools):
|
|
132
|
+
yield chunk
|
|
133
|
+
|
|
134
|
+
# After streaming is complete, update the message history
|
|
135
|
+
last_message = {"role": "assistant", "content": ""}
|
|
136
|
+
for chunk in self.chat_api.get_streaming_response(current_messages, settings=settings, tools=tools):
|
|
137
|
+
try:
|
|
138
|
+
parsed_chunk = json.loads(chunk)
|
|
139
|
+
if "tool_calls" in parsed_chunk:
|
|
140
|
+
last_message["tool_calls"] = parsed_chunk["tool_calls"]
|
|
141
|
+
if parsed_chunk.get("content"):
|
|
142
|
+
last_message["content"] += parsed_chunk["content"]
|
|
143
|
+
else:
|
|
144
|
+
last_message["content"] += chunk
|
|
145
|
+
except json.JSONDecodeError:
|
|
146
|
+
last_message["content"] += chunk
|
|
147
|
+
|
|
148
|
+
if "tool_calls" in last_message:
|
|
149
|
+
tool_messages = []
|
|
150
|
+
tool_calls_prepared = []
|
|
151
|
+
for tool_call in last_message["tool_calls"]:
|
|
152
|
+
tool = next((t for t in tools if t.model.__name__ == tool_call["function"]["name"]), None)
|
|
153
|
+
if tool:
|
|
154
|
+
call_parameters = tool_call["function"]["arguments"]
|
|
155
|
+
if isinstance(call_parameters, str):
|
|
156
|
+
call_parameters = json.loads(call_parameters)
|
|
157
|
+
call = tool.model(**call_parameters)
|
|
158
|
+
output = call.run(**tool.additional_parameters)
|
|
159
|
+
tool_call_id = tool_call["function"].get("id", generate_id(length=9))
|
|
160
|
+
tool_calls_prepared.append(
|
|
161
|
+
self.chat_api.generate_tool_use_message(content=last_message["content"],
|
|
162
|
+
tool_call_id=tool_call_id,
|
|
163
|
+
tool_name=tool_call["function"]["name"],
|
|
164
|
+
tool_args=call_parameters))
|
|
165
|
+
tool_messages.append(
|
|
166
|
+
self.chat_api.generate_tool_response_message(
|
|
167
|
+
tool_call_id=tool_call_id,
|
|
168
|
+
tool_name=tool_call["function"]["name"],
|
|
169
|
+
tool_response=str(output)
|
|
170
|
+
)
|
|
171
|
+
)
|
|
172
|
+
if "role" in tool_calls_prepared[0]:
|
|
173
|
+
current_messages.extend(tool_calls_prepared)
|
|
174
|
+
else:
|
|
175
|
+
current_messages.append(
|
|
176
|
+
{"role": "assistant", "content": last_message["content"], "tool_calls": tool_calls_prepared})
|
|
177
|
+
|
|
178
|
+
current_messages.extend(tool_messages)
|
|
179
|
+
yield "\n"
|
|
180
|
+
yield from self.get_streaming_response(settings=settings, tools=tools, messages=current_messages)
|