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.
Files changed (28) hide show
  1. toolagents-0.0.1/LICENSE +21 -0
  2. toolagents-0.0.1/PKG-INFO +199 -0
  3. toolagents-0.0.1/ReadMe.md +173 -0
  4. toolagents-0.0.1/pyproject.toml +39 -0
  5. toolagents-0.0.1/setup.cfg +4 -0
  6. toolagents-0.0.1/src/ToolAgents/__init__.py +2 -0
  7. toolagents-0.0.1/src/ToolAgents/agents/__init__.py +3 -0
  8. toolagents-0.0.1/src/ToolAgents/agents/chat_api_agent.py +180 -0
  9. toolagents-0.0.1/src/ToolAgents/agents/mistral_agent.py +214 -0
  10. toolagents-0.0.1/src/ToolAgents/agents/ollama_agent.py +162 -0
  11. toolagents-0.0.1/src/ToolAgents/function_tool.py +486 -0
  12. toolagents-0.0.1/src/ToolAgents/provider/__init__.py +4 -0
  13. toolagents-0.0.1/src/ToolAgents/provider/chat_api_with_tools.py +682 -0
  14. toolagents-0.0.1/src/ToolAgents/provider/llama_cpp_server.py +157 -0
  15. toolagents-0.0.1/src/ToolAgents/provider/tgi_server.py +144 -0
  16. toolagents-0.0.1/src/ToolAgents/provider/vllm_server.py +123 -0
  17. toolagents-0.0.1/src/ToolAgents/utilities/__init__.py +2 -0
  18. toolagents-0.0.1/src/ToolAgents/utilities/chat_history.py +107 -0
  19. toolagents-0.0.1/src/ToolAgents/utilities/documentation_generation.py +490 -0
  20. toolagents-0.0.1/src/ToolAgents.egg-info/PKG-INFO +199 -0
  21. toolagents-0.0.1/src/ToolAgents.egg-info/SOURCES.txt +26 -0
  22. toolagents-0.0.1/src/ToolAgents.egg-info/dependency_links.txt +1 -0
  23. toolagents-0.0.1/src/ToolAgents.egg-info/requires.txt +12 -0
  24. toolagents-0.0.1/src/ToolAgents.egg-info/top_level.txt +1 -0
  25. toolagents-0.0.1/tests/test_chat_api_agent.py +34 -0
  26. toolagents-0.0.1/tests/test_mistral_agent.py +31 -0
  27. toolagents-0.0.1/tests/test_ollama_agent.py +29 -0
  28. toolagents-0.0.1/tests/test_tools.py +216 -0
@@ -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,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,2 @@
1
+ from .function_tool import FunctionTool
2
+
@@ -0,0 +1,3 @@
1
+ from ToolAgents.agents.chat_api_agent import ChatAPIAgent
2
+ from ToolAgents.agents.mistral_agent import MistralAgent
3
+ from ToolAgents.agents.ollama_agent import OllamaAgent
@@ -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)