ag2 0.4.1__py3-none-any.whl → 0.5.0b2__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 ag2 might be problematic. Click here for more details.

Files changed (160) hide show
  1. {ag2-0.4.1.dist-info → ag2-0.5.0b2.dist-info}/METADATA +5 -146
  2. ag2-0.5.0b2.dist-info/RECORD +6 -0
  3. ag2-0.5.0b2.dist-info/top_level.txt +1 -0
  4. ag2-0.4.1.dist-info/RECORD +0 -158
  5. ag2-0.4.1.dist-info/top_level.txt +0 -1
  6. autogen/__init__.py +0 -17
  7. autogen/_pydantic.py +0 -116
  8. autogen/agentchat/__init__.py +0 -42
  9. autogen/agentchat/agent.py +0 -142
  10. autogen/agentchat/assistant_agent.py +0 -85
  11. autogen/agentchat/chat.py +0 -306
  12. autogen/agentchat/contrib/__init__.py +0 -0
  13. autogen/agentchat/contrib/agent_builder.py +0 -788
  14. autogen/agentchat/contrib/agent_eval/agent_eval.py +0 -107
  15. autogen/agentchat/contrib/agent_eval/criterion.py +0 -47
  16. autogen/agentchat/contrib/agent_eval/critic_agent.py +0 -47
  17. autogen/agentchat/contrib/agent_eval/quantifier_agent.py +0 -42
  18. autogen/agentchat/contrib/agent_eval/subcritic_agent.py +0 -48
  19. autogen/agentchat/contrib/agent_eval/task.py +0 -43
  20. autogen/agentchat/contrib/agent_optimizer.py +0 -450
  21. autogen/agentchat/contrib/capabilities/__init__.py +0 -0
  22. autogen/agentchat/contrib/capabilities/agent_capability.py +0 -21
  23. autogen/agentchat/contrib/capabilities/generate_images.py +0 -297
  24. autogen/agentchat/contrib/capabilities/teachability.py +0 -406
  25. autogen/agentchat/contrib/capabilities/text_compressors.py +0 -72
  26. autogen/agentchat/contrib/capabilities/transform_messages.py +0 -92
  27. autogen/agentchat/contrib/capabilities/transforms.py +0 -565
  28. autogen/agentchat/contrib/capabilities/transforms_util.py +0 -120
  29. autogen/agentchat/contrib/capabilities/vision_capability.py +0 -217
  30. autogen/agentchat/contrib/captainagent/tools/__init__.py +0 -0
  31. autogen/agentchat/contrib/captainagent/tools/data_analysis/calculate_correlation.py +0 -41
  32. autogen/agentchat/contrib/captainagent/tools/data_analysis/calculate_skewness_and_kurtosis.py +0 -29
  33. autogen/agentchat/contrib/captainagent/tools/data_analysis/detect_outlier_iqr.py +0 -29
  34. autogen/agentchat/contrib/captainagent/tools/data_analysis/detect_outlier_zscore.py +0 -29
  35. autogen/agentchat/contrib/captainagent/tools/data_analysis/explore_csv.py +0 -22
  36. autogen/agentchat/contrib/captainagent/tools/data_analysis/shapiro_wilk_test.py +0 -31
  37. autogen/agentchat/contrib/captainagent/tools/information_retrieval/arxiv_download.py +0 -26
  38. autogen/agentchat/contrib/captainagent/tools/information_retrieval/arxiv_search.py +0 -55
  39. autogen/agentchat/contrib/captainagent/tools/information_retrieval/extract_pdf_image.py +0 -54
  40. autogen/agentchat/contrib/captainagent/tools/information_retrieval/extract_pdf_text.py +0 -39
  41. autogen/agentchat/contrib/captainagent/tools/information_retrieval/get_wikipedia_text.py +0 -22
  42. autogen/agentchat/contrib/captainagent/tools/information_retrieval/get_youtube_caption.py +0 -35
  43. autogen/agentchat/contrib/captainagent/tools/information_retrieval/image_qa.py +0 -61
  44. autogen/agentchat/contrib/captainagent/tools/information_retrieval/optical_character_recognition.py +0 -62
  45. autogen/agentchat/contrib/captainagent/tools/information_retrieval/perform_web_search.py +0 -48
  46. autogen/agentchat/contrib/captainagent/tools/information_retrieval/scrape_wikipedia_tables.py +0 -34
  47. autogen/agentchat/contrib/captainagent/tools/information_retrieval/transcribe_audio_file.py +0 -22
  48. autogen/agentchat/contrib/captainagent/tools/information_retrieval/youtube_download.py +0 -36
  49. autogen/agentchat/contrib/captainagent/tools/math/calculate_circle_area_from_diameter.py +0 -22
  50. autogen/agentchat/contrib/captainagent/tools/math/calculate_day_of_the_week.py +0 -19
  51. autogen/agentchat/contrib/captainagent/tools/math/calculate_fraction_sum.py +0 -29
  52. autogen/agentchat/contrib/captainagent/tools/math/calculate_matrix_power.py +0 -32
  53. autogen/agentchat/contrib/captainagent/tools/math/calculate_reflected_point.py +0 -17
  54. autogen/agentchat/contrib/captainagent/tools/math/complex_numbers_product.py +0 -26
  55. autogen/agentchat/contrib/captainagent/tools/math/compute_currency_conversion.py +0 -24
  56. autogen/agentchat/contrib/captainagent/tools/math/count_distinct_permutations.py +0 -28
  57. autogen/agentchat/contrib/captainagent/tools/math/evaluate_expression.py +0 -29
  58. autogen/agentchat/contrib/captainagent/tools/math/find_continuity_point.py +0 -35
  59. autogen/agentchat/contrib/captainagent/tools/math/fraction_to_mixed_numbers.py +0 -40
  60. autogen/agentchat/contrib/captainagent/tools/math/modular_inverse_sum.py +0 -23
  61. autogen/agentchat/contrib/captainagent/tools/math/simplify_mixed_numbers.py +0 -37
  62. autogen/agentchat/contrib/captainagent/tools/math/sum_of_digit_factorials.py +0 -16
  63. autogen/agentchat/contrib/captainagent/tools/math/sum_of_primes_below.py +0 -16
  64. autogen/agentchat/contrib/captainagent/tools/requirements.txt +0 -10
  65. autogen/agentchat/contrib/captainagent/tools/tool_description.tsv +0 -34
  66. autogen/agentchat/contrib/captainagent.py +0 -490
  67. autogen/agentchat/contrib/gpt_assistant_agent.py +0 -545
  68. autogen/agentchat/contrib/graph_rag/__init__.py +0 -0
  69. autogen/agentchat/contrib/graph_rag/document.py +0 -30
  70. autogen/agentchat/contrib/graph_rag/falkor_graph_query_engine.py +0 -111
  71. autogen/agentchat/contrib/graph_rag/falkor_graph_rag_capability.py +0 -81
  72. autogen/agentchat/contrib/graph_rag/graph_query_engine.py +0 -56
  73. autogen/agentchat/contrib/graph_rag/graph_rag_capability.py +0 -64
  74. autogen/agentchat/contrib/img_utils.py +0 -390
  75. autogen/agentchat/contrib/llamaindex_conversable_agent.py +0 -123
  76. autogen/agentchat/contrib/llava_agent.py +0 -176
  77. autogen/agentchat/contrib/math_user_proxy_agent.py +0 -471
  78. autogen/agentchat/contrib/multimodal_conversable_agent.py +0 -128
  79. autogen/agentchat/contrib/qdrant_retrieve_user_proxy_agent.py +0 -325
  80. autogen/agentchat/contrib/retrieve_assistant_agent.py +0 -56
  81. autogen/agentchat/contrib/retrieve_user_proxy_agent.py +0 -705
  82. autogen/agentchat/contrib/society_of_mind_agent.py +0 -203
  83. autogen/agentchat/contrib/swarm_agent.py +0 -463
  84. autogen/agentchat/contrib/text_analyzer_agent.py +0 -76
  85. autogen/agentchat/contrib/tool_retriever.py +0 -120
  86. autogen/agentchat/contrib/vectordb/__init__.py +0 -0
  87. autogen/agentchat/contrib/vectordb/base.py +0 -243
  88. autogen/agentchat/contrib/vectordb/chromadb.py +0 -326
  89. autogen/agentchat/contrib/vectordb/mongodb.py +0 -559
  90. autogen/agentchat/contrib/vectordb/pgvectordb.py +0 -958
  91. autogen/agentchat/contrib/vectordb/qdrant.py +0 -334
  92. autogen/agentchat/contrib/vectordb/utils.py +0 -126
  93. autogen/agentchat/contrib/web_surfer.py +0 -305
  94. autogen/agentchat/conversable_agent.py +0 -2908
  95. autogen/agentchat/groupchat.py +0 -1668
  96. autogen/agentchat/user_proxy_agent.py +0 -109
  97. autogen/agentchat/utils.py +0 -207
  98. autogen/browser_utils.py +0 -291
  99. autogen/cache/__init__.py +0 -10
  100. autogen/cache/abstract_cache_base.py +0 -78
  101. autogen/cache/cache.py +0 -182
  102. autogen/cache/cache_factory.py +0 -85
  103. autogen/cache/cosmos_db_cache.py +0 -150
  104. autogen/cache/disk_cache.py +0 -109
  105. autogen/cache/in_memory_cache.py +0 -61
  106. autogen/cache/redis_cache.py +0 -128
  107. autogen/code_utils.py +0 -745
  108. autogen/coding/__init__.py +0 -22
  109. autogen/coding/base.py +0 -113
  110. autogen/coding/docker_commandline_code_executor.py +0 -262
  111. autogen/coding/factory.py +0 -45
  112. autogen/coding/func_with_reqs.py +0 -203
  113. autogen/coding/jupyter/__init__.py +0 -22
  114. autogen/coding/jupyter/base.py +0 -32
  115. autogen/coding/jupyter/docker_jupyter_server.py +0 -164
  116. autogen/coding/jupyter/embedded_ipython_code_executor.py +0 -182
  117. autogen/coding/jupyter/jupyter_client.py +0 -224
  118. autogen/coding/jupyter/jupyter_code_executor.py +0 -161
  119. autogen/coding/jupyter/local_jupyter_server.py +0 -168
  120. autogen/coding/local_commandline_code_executor.py +0 -410
  121. autogen/coding/markdown_code_extractor.py +0 -44
  122. autogen/coding/utils.py +0 -57
  123. autogen/exception_utils.py +0 -46
  124. autogen/extensions/__init__.py +0 -0
  125. autogen/formatting_utils.py +0 -76
  126. autogen/function_utils.py +0 -362
  127. autogen/graph_utils.py +0 -148
  128. autogen/io/__init__.py +0 -15
  129. autogen/io/base.py +0 -105
  130. autogen/io/console.py +0 -43
  131. autogen/io/websockets.py +0 -213
  132. autogen/logger/__init__.py +0 -11
  133. autogen/logger/base_logger.py +0 -140
  134. autogen/logger/file_logger.py +0 -287
  135. autogen/logger/logger_factory.py +0 -29
  136. autogen/logger/logger_utils.py +0 -42
  137. autogen/logger/sqlite_logger.py +0 -459
  138. autogen/math_utils.py +0 -356
  139. autogen/oai/__init__.py +0 -33
  140. autogen/oai/anthropic.py +0 -428
  141. autogen/oai/bedrock.py +0 -606
  142. autogen/oai/cerebras.py +0 -270
  143. autogen/oai/client.py +0 -1148
  144. autogen/oai/client_utils.py +0 -167
  145. autogen/oai/cohere.py +0 -453
  146. autogen/oai/completion.py +0 -1216
  147. autogen/oai/gemini.py +0 -469
  148. autogen/oai/groq.py +0 -281
  149. autogen/oai/mistral.py +0 -279
  150. autogen/oai/ollama.py +0 -582
  151. autogen/oai/openai_utils.py +0 -811
  152. autogen/oai/together.py +0 -343
  153. autogen/retrieve_utils.py +0 -487
  154. autogen/runtime_logging.py +0 -163
  155. autogen/token_count_utils.py +0 -259
  156. autogen/types.py +0 -20
  157. autogen/version.py +0 -7
  158. {ag2-0.4.1.dist-info → ag2-0.5.0b2.dist-info}/LICENSE +0 -0
  159. {ag2-0.4.1.dist-info → ag2-0.5.0b2.dist-info}/NOTICE.md +0 -0
  160. {ag2-0.4.1.dist-info → ag2-0.5.0b2.dist-info}/WHEEL +0 -0
autogen/oai/ollama.py DELETED
@@ -1,582 +0,0 @@
1
- # Copyright (c) 2023 - 2024, Owners of https://github.com/ag2ai
2
- #
3
- # SPDX-License-Identifier: Apache-2.0
4
- #
5
- # Portions derived from https://github.com/microsoft/autogen are under the MIT License.
6
- # SPDX-License-Identifier: MIT
7
- """Create an OpenAI-compatible client using Ollama's API.
8
-
9
- Example:
10
- llm_config={
11
- "config_list": [{
12
- "api_type": "ollama",
13
- "model": "mistral:7b-instruct-v0.3-q6_K"
14
- }
15
- ]}
16
-
17
- agent = autogen.AssistantAgent("my_agent", llm_config=llm_config)
18
-
19
- Install Ollama's python library using: pip install --upgrade ollama
20
- Install fix-busted-json library: pip install --upgrade fix-busted-json
21
-
22
- Resources:
23
- - https://github.com/ollama/ollama-python
24
- """
25
-
26
- from __future__ import annotations
27
-
28
- import copy
29
- import json
30
- import random
31
- import re
32
- import time
33
- import warnings
34
- from typing import Any, Dict, List, Tuple
35
-
36
- import ollama
37
- from fix_busted_json import repair_json
38
- from ollama import Client
39
- from openai.types.chat import ChatCompletion, ChatCompletionMessageToolCall
40
- from openai.types.chat.chat_completion import ChatCompletionMessage, Choice
41
- from openai.types.completion_usage import CompletionUsage
42
-
43
- from autogen.oai.client_utils import should_hide_tools, validate_parameter
44
-
45
-
46
- class OllamaClient:
47
- """Client for Ollama's API."""
48
-
49
- # Defaults for manual tool calling
50
- # Instruction is added to the first system message and provides directions to follow a two step
51
- # process
52
- # 1. (before tools have been called) Return JSON with the functions to call
53
- # 2. (directly after tools have been called) Return Text describing the results of the function calls in text format
54
-
55
- # Override using "manual_tool_call_instruction" config parameter
56
- TOOL_CALL_MANUAL_INSTRUCTION = (
57
- "You are to follow a strict two step process that will occur over "
58
- "a number of interactions, so pay attention to what step you are in based on the full "
59
- "conversation. We will be taking turns so only do one step at a time so don't perform step "
60
- "2 until step 1 is complete and I've told you the result. The first step is to choose one "
61
- "or more functions based on the request given and return only JSON with the functions and "
62
- "arguments to use. The second step is to analyse the given output of the function and summarise "
63
- "it returning only TEXT and not Python or JSON. "
64
- "For argument values, be sure numbers aren't strings, they should not have double quotes around them. "
65
- "In terms of your response format, for step 1 return only JSON and NO OTHER text, "
66
- "for step 2 return only text and NO JSON/Python/Markdown. "
67
- 'The format for running a function is [{"name": "function_name1", "arguments":{"argument_name": "argument_value"}},{"name": "function_name2", "arguments":{"argument_name": "argument_value"}}] '
68
- 'Make sure the keys "name" and "arguments" are as described. '
69
- "If you don't get the format correct, try again. "
70
- "The following functions are available to you:[FUNCTIONS_LIST]"
71
- )
72
-
73
- # Appended to the last user message if no tools have been called
74
- # Override using "manual_tool_call_step1" config parameter
75
- TOOL_CALL_MANUAL_STEP1 = " (proceed with step 1)"
76
-
77
- # Appended to the user message after tools have been executed. Will create a 'user' message if one doesn't exist.
78
- # Override using "manual_tool_call_step2" config parameter
79
- TOOL_CALL_MANUAL_STEP2 = " (proceed with step 2)"
80
-
81
- def __init__(self, **kwargs):
82
- """Note that no api_key or environment variable is required for Ollama.
83
-
84
- Args:
85
- None
86
- """
87
-
88
- def message_retrieval(self, response) -> List:
89
- """
90
- Retrieve and return a list of strings or a list of Choice.Message from the response.
91
-
92
- NOTE: if a list of Choice.Message is returned, it currently needs to contain the fields of OpenAI's ChatCompletion Message object,
93
- since that is expected for function or tool calling in the rest of the codebase at the moment, unless a custom agent is being used.
94
- """
95
- return [choice.message for choice in response.choices]
96
-
97
- def cost(self, response) -> float:
98
- return response.cost
99
-
100
- @staticmethod
101
- def get_usage(response) -> Dict:
102
- """Return usage summary of the response using RESPONSE_USAGE_KEYS."""
103
- # ... # pragma: no cover
104
- return {
105
- "prompt_tokens": response.usage.prompt_tokens,
106
- "completion_tokens": response.usage.completion_tokens,
107
- "total_tokens": response.usage.total_tokens,
108
- "cost": response.cost,
109
- "model": response.model,
110
- }
111
-
112
- def parse_params(self, params: Dict[str, Any]) -> Dict[str, Any]:
113
- """Loads the parameters for Ollama API from the passed in parameters and returns a validated set. Checks types, ranges, and sets defaults"""
114
- ollama_params = {}
115
-
116
- # Check that we have what we need to use Ollama's API
117
- # https://github.com/ollama/ollama/blob/main/docs/api.md#generate-a-completion
118
-
119
- # The main parameters are model, prompt, stream, and options
120
- # Options is a dictionary of parameters for the model
121
- # There are other, advanced, parameters such as format, system (to override system message), template, raw, etc. - not used
122
-
123
- # We won't enforce the available models
124
- ollama_params["model"] = params.get("model", None)
125
- assert ollama_params[
126
- "model"
127
- ], "Please specify the 'model' in your config list entry to nominate the Ollama model to use."
128
-
129
- ollama_params["stream"] = validate_parameter(params, "stream", bool, True, False, None, None)
130
-
131
- # Build up the options dictionary
132
- # https://github.com/ollama/ollama/blob/main/docs/modelfile.md#valid-parameters-and-values
133
- options_dict = {}
134
-
135
- if "num_predict" in params:
136
- # Maximum number of tokens to predict, note: -1 is infinite, -2 is fill context, 128 is default
137
- options_dict["num_predict"] = validate_parameter(params, "num_predict", int, False, 128, None, None)
138
-
139
- if "repeat_penalty" in params:
140
- options_dict["repeat_penalty"] = validate_parameter(
141
- params, "repeat_penalty", (int, float), False, 1.1, None, None
142
- )
143
-
144
- if "seed" in params:
145
- options_dict["seed"] = validate_parameter(params, "seed", int, False, 42, None, None)
146
-
147
- if "temperature" in params:
148
- options_dict["temperature"] = validate_parameter(
149
- params, "temperature", (int, float), False, 0.8, None, None
150
- )
151
-
152
- if "top_k" in params:
153
- options_dict["top_k"] = validate_parameter(params, "top_k", int, False, 40, None, None)
154
-
155
- if "top_p" in params:
156
- options_dict["top_p"] = validate_parameter(params, "top_p", (int, float), False, 0.9, None, None)
157
-
158
- if self._native_tool_calls and self._tools_in_conversation and not self._should_hide_tools:
159
- ollama_params["tools"] = params["tools"]
160
-
161
- # Ollama doesn't support streaming with tools natively
162
- if ollama_params["stream"] and self._native_tool_calls:
163
- warnings.warn(
164
- "Streaming is not supported when using tools and 'Native' tool calling, streaming will be disabled.",
165
- UserWarning,
166
- )
167
-
168
- ollama_params["stream"] = False
169
-
170
- if not self._native_tool_calls and self._tools_in_conversation:
171
- # For manual tool calling we have injected the available tools into the prompt
172
- # and we don't want to force JSON mode
173
- ollama_params["format"] = "" # Don't force JSON for manual tool calling mode
174
-
175
- if len(options_dict) != 0:
176
- ollama_params["options"] = options_dict
177
-
178
- return ollama_params
179
-
180
- def create(self, params: Dict) -> ChatCompletion:
181
-
182
- messages = params.get("messages", [])
183
-
184
- # Are tools involved in this conversation?
185
- self._tools_in_conversation = "tools" in params
186
-
187
- # We provide second-level filtering out of tools to avoid LLMs re-calling tools continuously
188
- if self._tools_in_conversation:
189
- hide_tools = validate_parameter(
190
- params, "hide_tools", str, False, "never", None, ["if_all_run", "if_any_run", "never"]
191
- )
192
- self._should_hide_tools = should_hide_tools(messages, params["tools"], hide_tools)
193
- else:
194
- self._should_hide_tools = False
195
-
196
- # Are we using native Ollama tool calling, otherwise we're doing manual tool calling
197
- # We allow the user to decide if they want to use Ollama's tool calling
198
- # or for tool calling to be handled manually through text messages
199
- # Default is True = Ollama's tool calling
200
- self._native_tool_calls = validate_parameter(params, "native_tool_calls", bool, False, True, None, None)
201
-
202
- if not self._native_tool_calls:
203
- # Load defaults
204
- self._manual_tool_call_instruction = validate_parameter(
205
- params, "manual_tool_call_instruction", str, False, self.TOOL_CALL_MANUAL_INSTRUCTION, None, None
206
- )
207
- self._manual_tool_call_step1 = validate_parameter(
208
- params, "manual_tool_call_step1", str, False, self.TOOL_CALL_MANUAL_STEP1, None, None
209
- )
210
- self._manual_tool_call_step2 = validate_parameter(
211
- params, "manual_tool_call_step2", str, False, self.TOOL_CALL_MANUAL_STEP2, None, None
212
- )
213
-
214
- # Convert AutoGen messages to Ollama messages
215
- ollama_messages = self.oai_messages_to_ollama_messages(
216
- messages,
217
- (
218
- params["tools"]
219
- if (not self._native_tool_calls and self._tools_in_conversation) and not self._should_hide_tools
220
- else None
221
- ),
222
- )
223
-
224
- # Parse parameters to the Ollama API's parameters
225
- ollama_params = self.parse_params(params)
226
-
227
- ollama_params["messages"] = ollama_messages
228
-
229
- # Token counts will be returned
230
- prompt_tokens = 0
231
- completion_tokens = 0
232
- total_tokens = 0
233
-
234
- ans = None
235
- if "client_host" in params:
236
- client = Client(host=params["client_host"])
237
- response = client.chat(**ollama_params)
238
- else:
239
- response = ollama.chat(**ollama_params)
240
-
241
- if ollama_params["stream"]:
242
- # Read in the chunks as they stream, taking in tool_calls which may be across
243
- # multiple chunks if more than one suggested
244
- ans = ""
245
- for chunk in response:
246
- ans = ans + (chunk["message"]["content"] or "")
247
-
248
- if "done_reason" in chunk:
249
- prompt_tokens = chunk["prompt_eval_count"] if "prompt_eval_count" in chunk else 0
250
- completion_tokens = chunk["eval_count"] if "eval_count" in chunk else 0
251
- total_tokens = prompt_tokens + completion_tokens
252
- else:
253
- # Non-streaming finished
254
- ans: str = response["message"]["content"]
255
-
256
- prompt_tokens = response["prompt_eval_count"] if "prompt_eval_count" in response else 0
257
- completion_tokens = response["eval_count"] if "eval_count" in response else 0
258
- total_tokens = prompt_tokens + completion_tokens
259
-
260
- if response is not None:
261
-
262
- # Defaults
263
- ollama_finish = "stop"
264
- tool_calls = None
265
-
266
- # Id and streaming text into response
267
- if ollama_params["stream"]:
268
- response_content = ans
269
- response_id = chunk["created_at"]
270
- else:
271
- response_content = response["message"]["content"]
272
- response_id = response["created_at"]
273
-
274
- # Process tools in the response
275
- if self._tools_in_conversation:
276
-
277
- if self._native_tool_calls:
278
-
279
- if not ollama_params["stream"]:
280
- response_content = response["message"]["content"]
281
-
282
- # Native tool calling
283
- if "tool_calls" in response["message"]:
284
- ollama_finish = "tool_calls"
285
- tool_calls = []
286
- random_id = random.randint(0, 10000)
287
- for tool_call in response["message"]["tool_calls"]:
288
- tool_calls.append(
289
- ChatCompletionMessageToolCall(
290
- id="ollama_func_{}".format(random_id),
291
- function={
292
- "name": tool_call["function"]["name"],
293
- "arguments": json.dumps(tool_call["function"]["arguments"]),
294
- },
295
- type="function",
296
- )
297
- )
298
-
299
- random_id += 1
300
-
301
- elif not self._native_tool_calls:
302
-
303
- # Try to convert the response to a tool call object
304
- response_toolcalls = response_to_tool_call(ans)
305
-
306
- # If we can, then we've got tool call(s)
307
- if response_toolcalls is not None:
308
- ollama_finish = "tool_calls"
309
- tool_calls = []
310
- random_id = random.randint(0, 10000)
311
-
312
- for json_function in response_toolcalls:
313
- tool_calls.append(
314
- ChatCompletionMessageToolCall(
315
- id="ollama_manual_func_{}".format(random_id),
316
- function={
317
- "name": json_function["name"],
318
- "arguments": (
319
- json.dumps(json_function["arguments"])
320
- if "arguments" in json_function
321
- else "{}"
322
- ),
323
- },
324
- type="function",
325
- )
326
- )
327
-
328
- random_id += 1
329
-
330
- # Blank the message content
331
- response_content = ""
332
-
333
- else:
334
- raise RuntimeError("Failed to get response from Ollama.")
335
-
336
- # Convert response to AutoGen response
337
- message = ChatCompletionMessage(
338
- role="assistant",
339
- content=response_content,
340
- function_call=None,
341
- tool_calls=tool_calls,
342
- )
343
- choices = [Choice(finish_reason=ollama_finish, index=0, message=message)]
344
-
345
- response_oai = ChatCompletion(
346
- id=response_id,
347
- model=ollama_params["model"],
348
- created=int(time.time()),
349
- object="chat.completion",
350
- choices=choices,
351
- usage=CompletionUsage(
352
- prompt_tokens=prompt_tokens,
353
- completion_tokens=completion_tokens,
354
- total_tokens=total_tokens,
355
- ),
356
- cost=0, # Local models, FREE!
357
- )
358
-
359
- return response_oai
360
-
361
- def oai_messages_to_ollama_messages(self, messages: list[Dict[str, Any]], tools: list) -> list[dict[str, Any]]:
362
- """Convert messages from OAI format to Ollama's format.
363
- We correct for any specific role orders and types, and convert tools to messages (as Ollama can't use tool messages)
364
- """
365
-
366
- ollama_messages = copy.deepcopy(messages)
367
-
368
- # Remove the name field
369
- for message in ollama_messages:
370
- if "name" in message:
371
- message.pop("name", None)
372
-
373
- # Having a 'system' message on the end does not work well with Ollama, so we change it to 'user'
374
- # 'system' messages on the end are typical of the summarisation message: summary_method="reflection_with_llm"
375
- if len(ollama_messages) > 1 and ollama_messages[-1]["role"] == "system":
376
- ollama_messages[-1]["role"] = "user"
377
-
378
- # Process messages for tool calling manually
379
- if tools is not None and not self._native_tool_calls:
380
- # 1. We need to append instructions to the starting system message on function calling
381
- # 2. If we have not yet called tools we append "step 1 instruction" to the latest user message
382
- # 3. If we have already called tools we append "step 2 instruction" to the latest user message
383
-
384
- have_tool_calls = False
385
- have_tool_results = False
386
- last_tool_result_index = -1
387
-
388
- for i, message in enumerate(ollama_messages):
389
- if "tool_calls" in message:
390
- have_tool_calls = True
391
- if "tool_call_id" in message:
392
- have_tool_results = True
393
- last_tool_result_index = i
394
-
395
- tool_result_is_last_msg = have_tool_results and last_tool_result_index == len(ollama_messages) - 1
396
-
397
- if ollama_messages[0]["role"] == "system":
398
- manual_instruction = self._manual_tool_call_instruction
399
-
400
- # Build a string of the functions available
401
- functions_string = ""
402
- for function in tools:
403
- functions_string += f"""\n{function}\n"""
404
-
405
- # Replace single quotes with double questions - Not sure why this helps the LLM perform
406
- # better, but it seems to. Monitor and remove if not necessary.
407
- functions_string = functions_string.replace("'", '"')
408
-
409
- manual_instruction = manual_instruction.replace("[FUNCTIONS_LIST]", functions_string)
410
-
411
- # Update the system message with the instructions and functions
412
- ollama_messages[0]["content"] = ollama_messages[0]["content"] + manual_instruction.rstrip()
413
-
414
- # If we are still in the function calling or evaluating process, append the steps instruction
415
- if not have_tool_calls or tool_result_is_last_msg:
416
- if ollama_messages[0]["role"] == "system":
417
- # NOTE: we require a system message to exist for the manual steps texts
418
- # Append the manual step instructions
419
- content_to_append = (
420
- self._manual_tool_call_step1 if not have_tool_results else self._manual_tool_call_step2
421
- )
422
-
423
- if content_to_append != "":
424
- # Append the relevant tool call instruction to the latest user message
425
- if ollama_messages[-1]["role"] == "user":
426
- ollama_messages[-1]["content"] = ollama_messages[-1]["content"] + content_to_append
427
- else:
428
- ollama_messages.append({"role": "user", "content": content_to_append})
429
-
430
- # Convert tool call and tool result messages to normal text messages for Ollama
431
- for i, message in enumerate(ollama_messages):
432
- if "tool_calls" in message:
433
- # Recommended tool calls
434
- content = "Run the following function(s):"
435
- for tool_call in message["tool_calls"]:
436
- content = content + "\n" + str(tool_call)
437
- ollama_messages[i] = {"role": "assistant", "content": content}
438
- if "tool_call_id" in message:
439
- # Executed tool results
440
- message["result"] = message["content"]
441
- del message["content"]
442
- del message["role"]
443
- content = "The following function was run: " + str(message)
444
- ollama_messages[i] = {"role": "user", "content": content}
445
-
446
- # As we are changing messages, let's merge if they have two user messages on the end and the last one is tool call step instructions
447
- if (
448
- len(ollama_messages) >= 2
449
- and not self._native_tool_calls
450
- and ollama_messages[-2]["role"] == "user"
451
- and ollama_messages[-1]["role"] == "user"
452
- and (
453
- ollama_messages[-1]["content"] == self._manual_tool_call_step1
454
- or ollama_messages[-1]["content"] == self._manual_tool_call_step2
455
- )
456
- ):
457
- ollama_messages[-2]["content"] = ollama_messages[-2]["content"] + ollama_messages[-1]["content"]
458
- del ollama_messages[-1]
459
-
460
- # Ensure the last message is a user / system message, if not, add a user message
461
- if ollama_messages[-1]["role"] != "user" and ollama_messages[-1]["role"] != "system":
462
- ollama_messages.append({"role": "user", "content": "Please continue."})
463
-
464
- return ollama_messages
465
-
466
-
467
- def response_to_tool_call(response_string: str) -> Any:
468
- """Attempts to convert the response to an object, aimed to align with function format [{},{}]"""
469
-
470
- # We try and detect the list[dict] format:
471
- # Pattern 1 is [{},{}]
472
- # Pattern 2 is {} (without the [], so could be a single function call)
473
- patterns = [r"\[[\s\S]*?\]", r"\{[\s\S]*\}"]
474
-
475
- for i, pattern in enumerate(patterns):
476
- # Search for the pattern in the input string
477
- matches = re.findall(pattern, response_string.strip())
478
-
479
- for match in matches:
480
-
481
- # It has matched, extract it and load it
482
- json_str = match.strip()
483
- data_object = None
484
-
485
- try:
486
- # Attempt to convert it as is
487
- data_object = json.loads(json_str)
488
- except Exception:
489
- try:
490
- # If that fails, attempt to repair it
491
-
492
- if i == 0:
493
- # Enclose to a JSON object for repairing, which is restored upon fix
494
- fixed_json = repair_json("{'temp':" + json_str + "}")
495
- data_object = json.loads(fixed_json)
496
- data_object = data_object["temp"]
497
- else:
498
- fixed_json = repair_json(json_str)
499
- data_object = json.loads(fixed_json)
500
- except json.JSONDecodeError as e:
501
- if e.msg == "Invalid \\escape":
502
- # Handle Mistral/Mixtral trying to escape underlines with \\
503
- try:
504
- json_str = json_str.replace("\\_", "_")
505
- if i == 0:
506
- fixed_json = repair_json("{'temp':" + json_str + "}")
507
- data_object = json.loads(fixed_json)
508
- data_object = data_object["temp"]
509
- else:
510
- fixed_json = repair_json("{'temp':" + json_str + "}")
511
- data_object = json.loads(fixed_json)
512
- except Exception:
513
- pass
514
- except Exception:
515
- pass
516
-
517
- if data_object is not None:
518
- data_object = _object_to_tool_call(data_object)
519
-
520
- if data_object is not None:
521
- return data_object
522
-
523
- # There's no tool call in the response
524
- return None
525
-
526
-
527
- def _object_to_tool_call(data_object: Any) -> List[Dict]:
528
- """Attempts to convert an object to a valid tool call object List[Dict] and returns it, if it can, otherwise None"""
529
-
530
- # If it's a dictionary and not a list then wrap in a list
531
- if isinstance(data_object, dict):
532
- data_object = [data_object]
533
-
534
- # Validate that the data is a list of dictionaries
535
- if isinstance(data_object, list) and all(isinstance(item, dict) for item in data_object):
536
- # Perfect format, a list of dictionaries
537
-
538
- # Check that each dictionary has at least 'name', optionally 'arguments' and no other keys
539
- is_invalid = False
540
- for item in data_object:
541
- if not is_valid_tool_call_item(item):
542
- is_invalid = True
543
- break
544
-
545
- # All passed, name and (optionally) arguments exist for all entries.
546
- if not is_invalid:
547
- return data_object
548
- elif isinstance(data_object, list):
549
- # If it's a list but the items are not dictionaries, check if they are strings that can be converted to dictionaries
550
- data_copy = data_object.copy()
551
- is_invalid = False
552
- for i, item in enumerate(data_copy):
553
- try:
554
- new_item = eval(item)
555
- if isinstance(new_item, dict):
556
- if is_valid_tool_call_item(new_item):
557
- data_object[i] = new_item
558
- else:
559
- is_invalid = True
560
- break
561
- else:
562
- is_invalid = True
563
- break
564
- except Exception:
565
- is_invalid = True
566
- break
567
-
568
- if not is_invalid:
569
- return data_object
570
-
571
- return None
572
-
573
-
574
- def is_valid_tool_call_item(call_item: dict) -> bool:
575
- """Check that a dictionary item has at least 'name', optionally 'arguments' and no other keys to match a tool call JSON"""
576
- if "name" not in call_item or not isinstance(call_item["name"], str):
577
- return False
578
-
579
- if set(call_item.keys()) - {"name", "arguments"}:
580
- return False
581
-
582
- return True