# AutoGen
+
[📚 Cite paper](#related-papers).
+:fire: June 6, 2024: WIRED publishes a new article on AutoGen: [Chatbot Teamwork Makes the AI Dream Work](https://www.wired.com/story/chatbot-teamwork-makes-the-ai-dream-work/) based on interview with [Adam Fourney](https://github.com/afourney).
+
+:fire: June 4th, 2024: Microsoft Research Forum publishes new update and video on [AutoGen and Complex Tasks](https://www.microsoft.com/en-us/research/video/autogen-update-complex-tasks-and-agents/) presented by [Adam Fourney](https://github.com/afourney).
+
:fire: May 29, 2024: DeepLearning.ai launched a new short course [AI Agentic Design Patterns with AutoGen](https://www.deeplearning.ai/short-courses/ai-agentic-design-patterns-with-autogen), made in collaboration with Microsoft and Penn State University, and taught by AutoGen creators [Chi Wang](https://github.com/sonichi) and [Qingyun Wu](https://github.com/qingyun-wu).
:fire: May 24, 2024: Foundation Capital published an article on [Forbes: The Promise of Multi-Agent AI](https://www.forbes.com/sites/joannechen/2024/05/24/the-promise-of-multi-agent-ai/?sh=2c1e4f454d97) and a video [AI in the Real World Episode 2: Exploring Multi-Agent AI and AutoGen with Chi Wang](https://www.youtube.com/watch?v=RLwyXRVvlNk).
@@ -68,10 +83,7 @@
AutoGen is an open-source programming framework for building AI agents and facilitating cooperation among multiple agents to solve tasks. AutoGen aims to streamline the development and research of agentic AI, much like PyTorch does for Deep Learning. It offers features such as agents capable of interacting with each other, facilitates the use of various large language models (LLMs) and tool use support, autonomous and human-in-the-loop workflows, and multi-agent conversation patterns.
-**Open Source Statement**: The project welcomes contributions from developers and organizations worldwide. Our goal is to foster a collaborative and inclusive community where diverse perspectives and expertise can drive innovation and enhance the project's capabilities. Whether you are an individual contributor or represent an organization, we invite you to join us in shaping the future of this project. Together, we can build something truly remarkable.
-
-The project is currently maintained by a [dynamic group of volunteers](https://butternut-swordtail-8a5.notion.site/410675be605442d3ada9a42eb4dfef30?v=fa5d0a79fd3d4c0f9c112951b2831cbb&pvs=4) from several different organizations. Contact project administrators Chi Wang and Qingyun Wu via auto-gen@outlook.com if you are interested in becoming a maintainer.
-
+We welcome contributions from developers and organizations worldwide. Our goal is to foster a collaborative and inclusive community where diverse perspectives and expertise can drive innovation and enhance the project's capabilities. We acknowledge the invaluable contributions from our existing contributors, as listed in [contributors.md](./CONTRIBUTORS.md). Whether you are an individual contributor or represent an organization, we invite you to join us in shaping the future of this project. For further information please also see [Microsoft open-source contributing guidelines](https://github.com/microsoft/autogen?tab=readme-ov-file#contributing).
![AutoGen Overview](https://github.com/microsoft/autogen/blob/main/website/static/img/autogen_agentchat.png)
@@ -247,16 +259,25 @@ In addition, you can find:
## Related Papers
-[AutoGen](https://arxiv.org/abs/2308.08155)
+[AutoGen Studio](https://www.microsoft.com/en-us/research/publication/autogen-studio-a-no-code-developer-tool-for-building-and-debugging-multi-agent-systems/)
+
+```
+@inproceedings{dibia2024studio,
+ title={AutoGen Studio: A No-Code Developer Tool for Building and Debugging Multi-Agent Systems},
+ author={Victor Dibia and Jingya Chen and Gagan Bansal and Suff Syed and Adam Fourney and Erkang (Eric) Zhu and Chi Wang and Saleema Amershi},
+ year={2024},
+ booktitle={Pre-Print}
+}
+```
+
+[AutoGen](https://aka.ms/autogen-pdf)
```
@inproceedings{wu2023autogen,
title={AutoGen: Enabling Next-Gen LLM Applications via Multi-Agent Conversation Framework},
author={Qingyun Wu and Gagan Bansal and Jieyu Zhang and Yiran Wu and Beibin Li and Erkang Zhu and Li Jiang and Xiaoyun Zhang and Shaokun Zhang and Jiale Liu and Ahmed Hassan Awadallah and Ryen W White and Doug Burger and Chi Wang},
- year={2023},
- eprint={2308.08155},
- archivePrefix={arXiv},
- primaryClass={cs.AI}
+ year={2024},
+ booktitle={COLM},
}
```
@@ -354,7 +375,7 @@ may be either trademarks or registered trademarks of Microsoft in the United Sta
The licenses for this project do not grant you rights to use any Microsoft names, logos, or trademarks.
Microsoft's general trademark guidelines can be found at http://go.microsoft.com/fwlink/?LinkID=254653.
-Privacy information can be found at https://privacy.microsoft.com/en-us/
+Privacy information can be found at https://go.microsoft.com/fwlink/?LinkId=521839
Microsoft and any contributors reserve all other rights, whether under their respective copyrights, patents,
or trademarks, whether by implication, estoppel, or otherwise.
diff --git a/TRANSPARENCY_FAQS.md b/TRANSPARENCY_FAQS.md
index 206af084748..addf29d8b8d 100644
--- a/TRANSPARENCY_FAQS.md
+++ b/TRANSPARENCY_FAQS.md
@@ -31,6 +31,8 @@ While AutoGen automates LLM workflows, decisions about how to use specific LLM o
- Current version of AutoGen was evaluated on six applications to illustrate its potential in simplifying the development of high-performance multi-agent applications. These applications are selected based on their real-world relevance, problem difficulty and problem solving capabilities enabled by AutoGen, and innovative potential.
- These applications involve using AutoGen to solve math problems, question answering, decision making in text world environments, supply chain optimization, etc. For each of these domains AutoGen was evaluated on various success based metrics (i.e., how often the AutoGen based implementation solved the task). And, in some cases, AutoGen based approach was also evaluated on implementation efficiency (e.g., to track reductions in developer effort to build). More details can be found at: https://aka.ms/AutoGen/TechReport
- The team has conducted tests where a “red” agent attempts to get the default AutoGen assistant to break from its alignment and guardrails. The team has observed that out of 70 attempts to break guardrails, only 1 was successful in producing text that would have been flagged as problematic by Azure OpenAI filters. The team has not observed any evidence that AutoGen (or GPT models as hosted by OpenAI or Azure) can produce novel code exploits or jailbreak prompts, since direct prompts to “be a hacker”, “write exploits”, or “produce a phishing email” are refused by existing filters.
+- We also evaluated [a team of AutoGen agents](https://github.com/microsoft/autogen/tree/gaia_multiagent_v01_march_1st/samples/tools/autogenbench/scenarios/GAIA/Templates/Orchestrator) on the [GAIA benchmarks](https://arxiv.org/abs/2311.12983), and got [SOTA results](https://huggingface.co/spaces/gaia-benchmark/leaderboard) as of
+ March 1, 2024.
## What are the limitations of AutoGen? How can users minimize the impact of AutoGen’s limitations when using the system?
AutoGen relies on existing LLMs. Experimenting with AutoGen would retain common limitations of large language models; including:
diff --git a/autogen/agentchat/contrib/capabilities/context_handling.py b/autogen/agentchat/contrib/capabilities/context_handling.py
deleted file mode 100644
index 44b10259f1b..00000000000
--- a/autogen/agentchat/contrib/capabilities/context_handling.py
+++ /dev/null
@@ -1,138 +0,0 @@
-import sys
-from typing import Dict, List, Optional
-from warnings import warn
-
-import tiktoken
-from termcolor import colored
-
-from autogen import ConversableAgent, token_count_utils
-
-warn(
- "Context handling with TransformChatHistory is deprecated and will be removed in `0.2.30`. "
- "Please use `TransformMessages`, documentation can be found at https://microsoft.github.io/autogen/docs/topics/handling_long_contexts/intro_to_transform_messages",
- DeprecationWarning,
- stacklevel=2,
-)
-
-
-class TransformChatHistory:
- """
- An agent's chat history with other agents is a common context that it uses to generate a reply.
- This capability allows the agent to transform its chat history prior to using it to generate a reply.
- It does not permanently modify the chat history, but rather processes it on every invocation.
-
- This capability class enables various strategies to transform chat history, such as:
- - Truncate messages: Truncate each message to first maximum number of tokens.
- - Limit number of messages: Truncate the chat history to a maximum number of (recent) messages.
- - Limit number of tokens: Truncate the chat history to number of recent N messages that fit in
- maximum number of tokens.
- Note that the system message, because of its special significance, is always kept as is.
-
- The three strategies can be combined. For example, when each of these parameters are specified
- they are used in the following order:
- 1. First truncate messages to a maximum number of tokens
- 2. Second, it limits the number of message to keep
- 3. Third, it limits the total number of tokens in the chat history
-
- When adding this capability to an agent, the following are modified:
- - A hook is added to the hookable method `process_all_messages_before_reply` to transform the
- received messages for possible truncation.
- Not modifying the stored message history.
- """
-
- def __init__(
- self,
- *,
- max_tokens_per_message: Optional[int] = None,
- max_messages: Optional[int] = None,
- max_tokens: Optional[int] = None,
- ):
- """
- Args:
- max_tokens_per_message (Optional[int]): Maximum number of tokens to keep in each message.
- max_messages (Optional[int]): Maximum number of messages to keep in the context.
- max_tokens (Optional[int]): Maximum number of tokens to keep in the context.
- """
- self.max_tokens_per_message = max_tokens_per_message if max_tokens_per_message else sys.maxsize
- self.max_messages = max_messages if max_messages else sys.maxsize
- self.max_tokens = max_tokens if max_tokens else sys.maxsize
-
- def add_to_agent(self, agent: ConversableAgent):
- """
- Adds TransformChatHistory capability to the given agent.
- """
- agent.register_hook(hookable_method="process_all_messages_before_reply", hook=self._transform_messages)
-
- def _transform_messages(self, messages: List[Dict]) -> List[Dict]:
- """
- Args:
- messages: List of messages to process.
-
- Returns:
- List of messages with the first system message and the last max_messages messages,
- ensuring each message does not exceed max_tokens_per_message.
- """
- temp_messages = messages.copy()
- processed_messages = []
- system_message = None
- processed_messages_tokens = 0
-
- if messages[0]["role"] == "system":
- system_message = messages[0].copy()
- temp_messages.pop(0)
-
- total_tokens = sum(
- token_count_utils.count_token(msg["content"]) for msg in temp_messages
- ) # Calculate tokens for all messages
-
- # Truncate each message's content to a maximum token limit of each message
-
- # Process recent messages first
- for msg in reversed(temp_messages[-self.max_messages :]):
- msg["content"] = truncate_str_to_tokens(msg["content"], self.max_tokens_per_message)
- msg_tokens = token_count_utils.count_token(msg["content"])
- if processed_messages_tokens + msg_tokens > self.max_tokens:
- break
- # append the message to the beginning of the list to preserve order
- processed_messages = [msg] + processed_messages
- processed_messages_tokens += msg_tokens
- if system_message:
- processed_messages.insert(0, system_message)
- # Optionally, log the number of truncated messages and tokens if needed
- num_truncated = len(messages) - len(processed_messages)
-
- if num_truncated > 0 or total_tokens > processed_messages_tokens:
- print(
- colored(
- f"Truncated {num_truncated} messages. Reduced from {len(messages)} to {len(processed_messages)}.",
- "yellow",
- )
- )
- print(
- colored(
- f"Truncated {total_tokens - processed_messages_tokens} tokens. Tokens reduced from {total_tokens} to {processed_messages_tokens}",
- "yellow",
- )
- )
- return processed_messages
-
-
-def truncate_str_to_tokens(text: str, max_tokens: int, model: str = "gpt-3.5-turbo-0613") -> str:
- """Truncate a string so that the number of tokens is less than or equal to max_tokens using tiktoken.
-
- Args:
- text: The string to truncate.
- max_tokens: The maximum number of tokens to keep.
- model: The target OpenAI model for tokenization alignment.
-
- Returns:
- The truncated string.
- """
-
- encoding = tiktoken.encoding_for_model(model) # Get the appropriate tokenizer
-
- encoded_tokens = encoding.encode(text)
- truncated_tokens = encoded_tokens[:max_tokens]
- truncated_text = encoding.decode(truncated_tokens) # Decode back to text
-
- return truncated_text
diff --git a/autogen/agentchat/contrib/capabilities/transform_messages.py b/autogen/agentchat/contrib/capabilities/transform_messages.py
index e96dc39fa7b..1ce219bdadf 100644
--- a/autogen/agentchat/contrib/capabilities/transform_messages.py
+++ b/autogen/agentchat/contrib/capabilities/transform_messages.py
@@ -1,9 +1,8 @@
import copy
from typing import Dict, List
-from autogen import ConversableAgent
-
from ....formatting_utils import colored
+from ...conversable_agent import ConversableAgent
from .transforms import MessageTransform
diff --git a/autogen/agentchat/contrib/capabilities/transforms.py b/autogen/agentchat/contrib/capabilities/transforms.py
index dad3fc335ed..d9ad365b91b 100644
--- a/autogen/agentchat/contrib/capabilities/transforms.py
+++ b/autogen/agentchat/contrib/capabilities/transforms.py
@@ -53,13 +53,16 @@ class MessageHistoryLimiter:
It trims the conversation history by removing older messages, retaining only the most recent messages.
"""
- def __init__(self, max_messages: Optional[int] = None):
+ def __init__(self, max_messages: Optional[int] = None, keep_first_message: bool = False):
"""
Args:
max_messages Optional[int]: Maximum number of messages to keep in the context. Must be greater than 0 if not None.
+ keep_first_message bool: Whether to keep the original first message in the conversation history.
+ Defaults to False.
"""
self._validate_max_messages(max_messages)
self._max_messages = max_messages
+ self._keep_first_message = keep_first_message
def apply_transform(self, messages: List[Dict]) -> List[Dict]:
"""Truncates the conversation history to the specified maximum number of messages.
@@ -75,10 +78,31 @@ def apply_transform(self, messages: List[Dict]) -> List[Dict]:
List[Dict]: A new list containing the most recent messages up to the specified maximum.
"""
- if self._max_messages is None:
+ if self._max_messages is None or len(messages) <= self._max_messages:
return messages
- return messages[-self._max_messages :]
+ truncated_messages = []
+ remaining_count = self._max_messages
+
+ # Start with the first message if we need to keep it
+ if self._keep_first_message:
+ truncated_messages = [messages[0]]
+ remaining_count -= 1
+
+ # Loop through messages in reverse
+ for i in range(len(messages) - 1, 0, -1):
+ if remaining_count > 1:
+ truncated_messages.insert(1 if self._keep_first_message else 0, messages[i])
+ if remaining_count == 1:
+ # If there's only 1 slot left and it's a 'tools' message, ignore it.
+ if messages[i].get("role") != "tool":
+ truncated_messages.insert(1, messages[i])
+
+ remaining_count -= 1
+ if remaining_count == 0:
+ break
+
+ return truncated_messages
def get_logs(self, pre_transform_messages: List[Dict], post_transform_messages: List[Dict]) -> Tuple[str, bool]:
pre_transform_messages_len = len(pre_transform_messages)
@@ -421,3 +445,95 @@ def _compress_text(self, text: str) -> Tuple[str, int]:
def _validate_min_tokens(self, min_tokens: Optional[int]):
if min_tokens is not None and min_tokens <= 0:
raise ValueError("min_tokens must be greater than 0 or None")
+
+
+class TextMessageContentName:
+ """A transform for including the agent's name in the content of a message."""
+
+ def __init__(
+ self,
+ position: str = "start",
+ format_string: str = "{name}:\n",
+ deduplicate: bool = True,
+ filter_dict: Optional[Dict] = None,
+ exclude_filter: bool = True,
+ ):
+ """
+ Args:
+ position (str): The position to add the name to the content. The possible options are 'start' or 'end'. Defaults to 'start'.
+ format_string (str): The f-string to format the message name with. Use '{name}' as a placeholder for the agent's name. Defaults to '{name}:\n' and must contain '{name}'.
+ deduplicate (bool): Whether to deduplicate the formatted string so it doesn't appear twice (sometimes the LLM will add it to new messages itself). Defaults to True.
+ filter_dict (None or dict): A dictionary to filter out messages that you want/don't want to compress.
+ If None, no filters will be applied.
+ exclude_filter (bool): If exclude filter is True (the default value), messages that match the filter will be
+ excluded from compression. If False, messages that match the filter will be compressed.
+ """
+
+ assert isinstance(position, str) and position is not None
+ assert position in ["start", "end"]
+ assert isinstance(format_string, str) and format_string is not None
+ assert "{name}" in format_string
+ assert isinstance(deduplicate, bool) and deduplicate is not None
+
+ self._position = position
+ self._format_string = format_string
+ self._deduplicate = deduplicate
+ self._filter_dict = filter_dict
+ self._exclude_filter = exclude_filter
+
+ # Track the number of messages changed for logging
+ self._messages_changed = 0
+
+ def apply_transform(self, messages: List[Dict]) -> List[Dict]:
+ """Applies the name change to the message based on the position and format string.
+
+ Args:
+ messages (List[Dict]): A list of message dictionaries.
+
+ Returns:
+ List[Dict]: A list of dictionaries with the message content updated with names.
+ """
+ # Make sure there is at least one message
+ if not messages:
+ return messages
+
+ messages_changed = 0
+ processed_messages = copy.deepcopy(messages)
+ for message in processed_messages:
+ # Some messages may not have content.
+ if not transforms_util.is_content_right_type(
+ message.get("content")
+ ) or not transforms_util.is_content_right_type(message.get("name")):
+ continue
+
+ if not transforms_util.should_transform_message(message, self._filter_dict, self._exclude_filter):
+ continue
+
+ if transforms_util.is_content_text_empty(message["content"]) or transforms_util.is_content_text_empty(
+ message["name"]
+ ):
+ continue
+
+ # Get and format the name in the content
+ content = message["content"]
+ formatted_name = self._format_string.format(name=message["name"])
+
+ if self._position == "start":
+ if not self._deduplicate or not content.startswith(formatted_name):
+ message["content"] = f"{formatted_name}{content}"
+
+ messages_changed += 1
+ else:
+ if not self._deduplicate or not content.endswith(formatted_name):
+ message["content"] = f"{content}{formatted_name}"
+
+ messages_changed += 1
+
+ self._messages_changed = messages_changed
+ return processed_messages
+
+ def get_logs(self, pre_transform_messages: List[Dict], post_transform_messages: List[Dict]) -> Tuple[str, bool]:
+ if self._messages_changed > 0:
+ return f"{self._messages_changed} message(s) changed to incorporate name.", True
+ else:
+ return "No messages changed to incorporate name.", False
diff --git a/autogen/agentchat/contrib/compressible_agent.py b/autogen/agentchat/contrib/compressible_agent.py
deleted file mode 100644
index bea4058b94a..00000000000
--- a/autogen/agentchat/contrib/compressible_agent.py
+++ /dev/null
@@ -1,436 +0,0 @@
-import copy
-import inspect
-import logging
-from typing import Any, Callable, Dict, List, Literal, Optional, Tuple, Union
-from warnings import warn
-
-from autogen import Agent, ConversableAgent, OpenAIWrapper
-from autogen.token_count_utils import count_token, get_max_token_limit, num_tokens_from_functions
-
-from ...formatting_utils import colored
-
-logger = logging.getLogger(__name__)
-
-warn(
- "Context handling with CompressibleAgent is deprecated and will be removed in `0.2.30`. "
- "Please use `TransformMessages`, documentation can be found at https://microsoft.github.io/autogen/docs/topics/handling_long_contexts/intro_to_transform_messages",
- DeprecationWarning,
- stacklevel=2,
-)
-
-
-class CompressibleAgent(ConversableAgent):
- """CompressibleAgent agent. While this agent retains all the default functionalities of the `AssistantAgent`,
- it also provides the added feature of compression when activated through the `compress_config` setting.
-
- `compress_config` is set to False by default, making this agent equivalent to the `AssistantAgent`.
- This agent does not work well in a GroupChat: The compressed messages will not be sent to all the agents in the group.
- The default system message is the same as AssistantAgent.
- `human_input_mode` is default to "NEVER"
- and `code_execution_config` is default to False.
- This agent doesn't execute code or function call by default.
- """
-
- DEFAULT_SYSTEM_MESSAGE = """You are a helpful AI assistant.
-Solve tasks using your coding and language skills.
-In the following cases, suggest python code (in a python coding block) or shell script (in a sh coding block) for the user to execute.
- 1. When you need to collect info, use the code to output the info you need, for example, browse or search the web, download/read a file, print the content of a webpage or a file, get the current date/time, check the operating system. After sufficient info is printed and the task is ready to be solved based on your language skill, you can solve the task by yourself.
- 2. When you need to perform some task with code, use the code to perform the task and output the result. Finish the task smartly.
-Solve the task step by step if you need to. If a plan is not provided, explain your plan first. Be clear which step uses code, and which step uses your language skill.
-When using code, you must indicate the script type in the code block. The user cannot provide any other feedback or perform any other action beyond executing the code you suggest. The user can't modify your code. So do not suggest incomplete code which requires users to modify. Don't use a code block if it's not intended to be executed by the user.
-If you want the user to save the code in a file before executing it, put # filename: inside the code block as the first line. Don't include multiple code blocks in one response. Do not ask users to copy and paste the result. Instead, use 'print' function for the output when relevant. Check the execution result returned by the user.
-If the result indicates there is an error, fix the error and output the code again. Suggest the full code instead of partial code or code changes. If the error can't be fixed or if the task is not solved even after the code is executed successfully, analyze the problem, revisit your assumption, collect additional info you need, and think of a different approach to try.
-When you find an answer, verify the answer carefully. Include verifiable evidence in your response if possible.
-Reply "TERMINATE" in the end when everything is done.
- """
- DEFAULT_COMPRESS_CONFIG = {
- "mode": "TERMINATE",
- "compress_function": None,
- "trigger_count": 0.7,
- "async": False,
- "broadcast": True,
- "verbose": False,
- "leave_last_n": 2,
- }
-
- def __init__(
- self,
- name: str,
- system_message: Optional[str] = DEFAULT_SYSTEM_MESSAGE,
- is_termination_msg: Optional[Callable[[Dict], bool]] = None,
- max_consecutive_auto_reply: Optional[int] = None,
- human_input_mode: Literal["ALWAYS", "NEVER", "TERMINATE"] = "NEVER",
- function_map: Optional[Dict[str, Callable]] = None,
- code_execution_config: Optional[Union[Dict, bool]] = False,
- llm_config: Optional[Union[Dict, bool]] = None,
- default_auto_reply: Optional[Union[str, Dict, None]] = "",
- compress_config: Optional[Dict] = False,
- description: Optional[str] = None,
- **kwargs,
- ):
- """
- Args:
- name (str): agent name.
- system_message (str): system message for the ChatCompletion inference.
- Please override this attribute if you want to reprogram the agent.
- llm_config (dict): llm inference configuration.
- Note: you must set `model` in llm_config. It will be used to compute the token count.
- Please refer to [OpenAIWrapper.create](/docs/reference/oai/client#create)
- for available options.
- is_termination_msg (function): a function that takes a message in the form of a dictionary
- and returns a boolean value indicating if this received message is a termination message.
- The dict can contain the following keys: "content", "role", "name", "function_call".
- max_consecutive_auto_reply (int): the maximum number of consecutive auto replies.
- default to None (no limit provided, class attribute MAX_CONSECUTIVE_AUTO_REPLY will be used as the limit in this case).
- The limit only plays a role when human_input_mode is not "ALWAYS".
- compress_config (dict or True/False): config for compression before oai_reply. Default to False.
- You should contain the following keys:
- - "mode" (Optional, str, default to "TERMINATE"): Choose from ["COMPRESS", "TERMINATE", "CUSTOMIZED"].
- 1. `TERMINATE`: terminate the conversation ONLY when token count exceeds the max limit of current model. `trigger_count` is NOT used in this mode.
- 2. `COMPRESS`: compress the messages when the token count exceeds the limit.
- 3. `CUSTOMIZED`: pass in a customized function to compress the messages.
- - "compress_function" (Optional, callable, default to None): Must be provided when mode is "CUSTOMIZED".
- The function should takes a list of messages and returns a tuple of (is_compress_success: bool, compressed_messages: List[Dict]).
- - "trigger_count" (Optional, float, int, default to 0.7): the threshold to trigger compression.
- If a float between (0, 1], it is the percentage of token used. if a int, it is the number of tokens used.
- - "async" (Optional, bool, default to False): whether to compress asynchronously.
- - "broadcast" (Optional, bool, default to True): whether to update the compressed message history to sender.
- - "verbose" (Optional, bool, default to False): Whether to print the content before and after compression. Used when mode="COMPRESS".
- - "leave_last_n" (Optional, int, default to 0): If provided, the last n messages will not be compressed. Used when mode="COMPRESS".
- description (str): a short description of the agent. This description is used by other agents
- (e.g. the GroupChatManager) to decide when to call upon this agent. (Default: system_message)
- **kwargs (dict): Please refer to other kwargs in
- [ConversableAgent](../conversable_agent#__init__).
- """
- super().__init__(
- name=name,
- system_message=system_message,
- is_termination_msg=is_termination_msg,
- max_consecutive_auto_reply=max_consecutive_auto_reply,
- human_input_mode=human_input_mode,
- function_map=function_map,
- code_execution_config=code_execution_config,
- llm_config=llm_config,
- default_auto_reply=default_auto_reply,
- description=description,
- **kwargs,
- )
-
- self._set_compress_config(compress_config)
-
- # create a separate client for compression.
- if llm_config is False:
- self.llm_compress_config = False
- self.compress_client = None
- else:
- if "model" not in llm_config:
- raise ValueError("llm_config must contain the 'model' field.")
- self.llm_compress_config = self.llm_config.copy()
- # remove functions
- if "functions" in self.llm_compress_config:
- del self.llm_compress_config["functions"]
- self.compress_client = OpenAIWrapper(**self.llm_compress_config)
-
- self._reply_func_list.clear()
- self.register_reply([Agent, None], ConversableAgent.generate_oai_reply)
- self.register_reply([Agent], CompressibleAgent.on_oai_token_limit) # check token limit
- self.register_reply([Agent, None], ConversableAgent.generate_code_execution_reply)
- self.register_reply([Agent, None], ConversableAgent.generate_function_call_reply)
- self.register_reply([Agent, None], ConversableAgent.check_termination_and_human_reply)
-
- def _set_compress_config(self, compress_config: Optional[Dict] = False):
- if compress_config:
- if compress_config is True:
- compress_config = {}
- if not isinstance(compress_config, dict):
- raise ValueError("compress_config must be a dict or True/False.")
-
- allowed_modes = ["COMPRESS", "TERMINATE", "CUSTOMIZED"]
- if compress_config.get("mode", "TERMINATE") not in allowed_modes:
- raise ValueError(f"Invalid compression mode. Allowed values are: {', '.join(allowed_modes)}")
-
- self.compress_config = self.DEFAULT_COMPRESS_CONFIG.copy()
- self.compress_config.update(compress_config)
-
- if not isinstance(self.compress_config["leave_last_n"], int) or self.compress_config["leave_last_n"] < 0:
- raise ValueError("leave_last_n must be a non-negative integer.")
-
- # convert trigger_count to int, default to 0.7
- trigger_count = self.compress_config["trigger_count"]
- if not (isinstance(trigger_count, int) or isinstance(trigger_count, float)) or trigger_count <= 0:
- raise ValueError("trigger_count must be a positive number.")
- if isinstance(trigger_count, float) and 0 < trigger_count <= 1:
- self.compress_config["trigger_count"] = int(
- trigger_count * get_max_token_limit(self.llm_config["model"])
- )
- trigger_count = self.compress_config["trigger_count"]
- init_count = self._compute_init_token_count()
- if trigger_count < init_count:
- print(
- f"Warning: trigger_count {trigger_count} is less than the initial token count {init_count} (system message + function description if passed), compression will be disabled. Please increase trigger_count if you want to enable compression."
- )
- self.compress_config = False
-
- if self.compress_config["mode"] == "CUSTOMIZED" and self.compress_config["compress_function"] is None:
- raise ValueError("compress_function must be provided when mode is CUSTOMIZED.")
- if self.compress_config["mode"] != "CUSTOMIZED" and self.compress_config["compress_function"] is not None:
- print("Warning: compress_function is provided but mode is not 'CUSTOMIZED'.")
-
- else:
- self.compress_config = False
-
- def generate_reply(
- self,
- messages: Optional[List[Dict]] = None,
- sender: Optional[Agent] = None,
- exclude: Optional[List[Callable]] = None,
- ) -> Union[str, Dict, None]:
- """
-
- Adding to line 202:
- ```
- if messages is not None and messages != self._oai_messages[sender]:
- messages = self._oai_messages[sender]
- ```
- """
- if all((messages is None, sender is None)):
- error_msg = f"Either {messages=} or {sender=} must be provided."
- logger.error(error_msg)
- raise AssertionError(error_msg)
-
- if messages is None:
- messages = self._oai_messages[sender]
-
- for reply_func_tuple in self._reply_func_list:
- reply_func = reply_func_tuple["reply_func"]
- if exclude and reply_func in exclude:
- continue
- if inspect.iscoroutinefunction(reply_func):
- continue
- if self._match_trigger(reply_func_tuple["trigger"], sender):
- final, reply = reply_func(self, messages=messages, sender=sender, config=reply_func_tuple["config"])
- if messages is not None and sender is not None and messages != self._oai_messages[sender]:
- messages = self._oai_messages[sender]
- if final:
- return reply
- return self._default_auto_reply
-
- def _compute_init_token_count(self):
- """Check if the agent is LLM-based and compute the initial token count."""
- if self.llm_config is False:
- return 0
-
- func_count = 0
- if "functions" in self.llm_config:
- func_count = num_tokens_from_functions(self.llm_config["functions"], self.llm_config["model"])
-
- return func_count + count_token(self._oai_system_message, self.llm_config["model"])
-
- def _manage_history_on_token_limit(self, messages, token_used, max_token_allowed, model):
- """Manage the message history with different modes when token limit is reached.
- Return:
- final (bool): whether to terminate the agent.
- compressed_messages (List[Dict]): the compressed messages. None if no compression or compression failed.
- """
- # 1. mode = "TERMINATE", terminate the agent if no token left.
- if self.compress_config["mode"] == "TERMINATE":
- if max_token_allowed - token_used <= 0:
- # Terminate if no token left.
- print(
- colored(
- f'Warning: Terminate Agent "{self.name}" due to no token left for oai reply. max token for {model}: {max_token_allowed}, existing token count: {token_used}',
- "yellow",
- ),
- flush=True,
- )
- return True, None
- return False, None
-
- # if token_used is less than trigger_count, no compression will be used.
- if token_used < self.compress_config["trigger_count"]:
- return False, None
-
- # 2. mode = "COMPRESS" or mode = "CUSTOMIZED", compress the messages
- copied_messages = copy.deepcopy(messages)
- if self.compress_config["mode"] == "COMPRESS":
- _, compress_messages = self.compress_messages(copied_messages)
- elif self.compress_config["mode"] == "CUSTOMIZED":
- _, compress_messages = self.compress_config["compress_function"](copied_messages)
- else:
- raise ValueError(f"Unknown compression mode: {self.compress_config['mode']}")
-
- if compress_messages is not None:
- for i in range(len(compress_messages)):
- compress_messages[i] = self._get_valid_oai_message(compress_messages[i])
- return False, compress_messages
-
- def _get_valid_oai_message(self, message):
- """Convert a message into a valid OpenAI ChatCompletion message."""
- oai_message = {k: message[k] for k in ("content", "function_call", "name", "context", "role") if k in message}
- if "content" not in oai_message:
- if "function_call" in oai_message:
- oai_message["content"] = None # if only function_call is provided, content will be set to None.
- else:
- raise ValueError(
- "Message can't be converted into a valid ChatCompletion message. Either content or function_call must be provided."
- )
- if "function_call" in oai_message:
- oai_message["role"] = "assistant" # only messages with role 'assistant' can have a function call.
- oai_message["function_call"] = dict(oai_message["function_call"])
- return oai_message
-
- def _print_compress_info(self, init_token_count, token_used, token_after_compression):
- to_print = "Token Count (including {} tokens from system msg and function descriptions). Before compression : {} | After: {}".format(
- init_token_count,
- token_used,
- token_after_compression,
- )
- print(colored(to_print, "magenta"), flush=True)
- print("-" * 80, flush=True)
-
- def on_oai_token_limit(
- self,
- messages: Optional[List[Dict]] = None,
- sender: Optional[Agent] = None,
- config: Optional[Any] = None,
- ) -> Tuple[bool, Union[str, Dict, None]]:
- """(Experimental) Compress previous messages when a threshold of tokens is reached.
-
- TODO: async compress
- TODO: maintain a list for old oai messages (messages before compression)
- """
- llm_config = self.llm_config if config is None else config
- if self.compress_config is False:
- return False, None
- if messages is None:
- messages = self._oai_messages[sender]
-
- model = llm_config["model"]
- init_token_count = self._compute_init_token_count()
- token_used = init_token_count + count_token(messages, model)
- final, compressed_messages = self._manage_history_on_token_limit(
- messages, token_used, get_max_token_limit(model), model
- )
-
- # update message history with compressed messages
- if compressed_messages is not None:
- self._print_compress_info(
- init_token_count, token_used, count_token(compressed_messages, model) + init_token_count
- )
- self._oai_messages[sender] = compressed_messages
- if self.compress_config["broadcast"]:
- # update the compressed message history to sender
- sender._oai_messages[self] = copy.deepcopy(compressed_messages)
- # switching the role of the messages for the sender
- for i in range(len(sender._oai_messages[self])):
- cmsg = sender._oai_messages[self][i]
- if "function_call" in cmsg or cmsg["role"] == "user":
- cmsg["role"] = "assistant"
- elif cmsg["role"] == "assistant":
- cmsg["role"] = "user"
- sender._oai_messages[self][i] = cmsg
-
- # successfully compressed, return False, None for generate_oai_reply to be called with the updated messages
- return False, None
- return final, None
-
- def compress_messages(
- self,
- messages: Optional[List[Dict]] = None,
- config: Optional[Any] = None,
- ) -> Tuple[bool, Union[str, Dict, None, List]]:
- """Compress a list of messages into one message.
-
- The first message (the initial prompt) will not be compressed.
- The rest of the messages will be compressed into one message, the model is asked to distinguish the role of each message: USER, ASSISTANT, FUNCTION_CALL, FUNCTION_RETURN.
- Check out the compress_sys_msg.
-
- TODO: model used in compression agent is different from assistant agent: For example, if original model used by is gpt-4; we start compressing at 70% of usage, 70% of 8092 = 5664; and we use gpt 3.5 here max_toke = 4096, it will raise error. choosinng model automatically?
- """
- # 1. use the compression client
- client = self.compress_client if config is None else config
-
- # 2. stop if there is only one message in the list
- leave_last_n = self.compress_config.get("leave_last_n", 0)
- if leave_last_n + 1 >= len(messages):
- logger.warning(
- f"Warning: Compression skipped at trigger count threshold. The first msg and last {leave_last_n} msgs will not be compressed. current msg count: {len(messages)}. Consider raising trigger_count."
- )
- return False, None
-
- # 3. put all history into one, except the first one
- if self.compress_config["verbose"]:
- print(colored("*" * 30 + "Start compressing the following content:" + "*" * 30, "magenta"), flush=True)
-
- compressed_prompt = "Below is the compressed content from the previous conversation, evaluate the process and continue if necessary:\n"
- chat_to_compress = "To be compressed:\n"
-
- for m in messages[1 : len(messages) - leave_last_n]: # 0, 1, 2, 3, 4
- # Handle function role
- if m.get("role") == "function":
- chat_to_compress += f"##FUNCTION_RETURN## (from function \"{m['name']}\"): \n{m['content']}\n"
-
- # If name exists in the message
- elif "name" in m:
- chat_to_compress += f"##{m['name']}({m['role'].upper()})## {m['content']}\n"
-
- # Handle case where content is not None and name is absent
- elif m.get("content"): # This condition will also handle None and empty string
- if compressed_prompt in m["content"]:
- chat_to_compress += m["content"].replace(compressed_prompt, "") + "\n"
- else:
- chat_to_compress += f"##{m['role'].upper()}## {m['content']}\n"
-
- # Handle function_call in the message
- if "function_call" in m:
- function_name = m["function_call"].get("name")
- function_args = m["function_call"].get("arguments")
-
- if not function_name or not function_args:
- chat_to_compress += f"##FUNCTION_CALL## {m['function_call']}\n"
- else:
- chat_to_compress += f"##FUNCTION_CALL## \nName: {function_name}\nArgs: {function_args}\n"
-
- chat_to_compress = [{"role": "user", "content": chat_to_compress}]
-
- if self.compress_config["verbose"]:
- print(chat_to_compress[0]["content"])
-
- # 4. use LLM to compress
- compress_sys_msg = """You are a helpful assistant that will summarize and compress conversation history.
-Rules:
-1. Please summarize each of the message and reserve the exact titles: ##USER##, ##ASSISTANT##, ##FUNCTION_CALL##, ##FUNCTION_RETURN##, ##SYSTEM##, ##()## (e.g. ##Bob(ASSISTANT)##).
-2. Try to compress the content but reserve important information (a link, a specific number, etc.).
-3. Use words to summarize the code blocks or functions calls (##FUNCTION_CALL##) and their goals. For code blocks, please use ##CODE## to mark it.
-4. For returns from functions (##FUNCTION_RETURN##) or returns from code execution: summarize the content and indicate the status of the return (e.g. success, error, etc.).
-"""
- try:
- response = client.create(
- context=None,
- messages=[{"role": "system", "content": compress_sys_msg}] + chat_to_compress,
- )
- except Exception as e:
- print(colored(f"Failed to compress the content due to {e}", "red"), flush=True)
- return False, None
-
- compressed_message = self.client.extract_text_or_completion_object(response)[0]
- assert isinstance(compressed_message, str), f"compressed_message should be a string: {compressed_message}"
- if self.compress_config["verbose"]:
- print(
- colored("*" * 30 + "Content after compressing:" + "*" * 30, "magenta"),
- flush=True,
- )
- print(compressed_message, colored("\n" + "*" * 80, "magenta"))
-
- # 5. add compressed message to the first message and return
- return (
- True,
- [
- messages[0],
- {
- "content": compressed_prompt + compressed_message,
- "role": "system",
- },
- ]
- + messages[len(messages) - leave_last_n :],
- )
diff --git a/autogen/agentchat/contrib/qdrant_retrieve_user_proxy_agent.py b/autogen/agentchat/contrib/qdrant_retrieve_user_proxy_agent.py
index ea81de6dff1..f1cc6947d50 100644
--- a/autogen/agentchat/contrib/qdrant_retrieve_user_proxy_agent.py
+++ b/autogen/agentchat/contrib/qdrant_retrieve_user_proxy_agent.py
@@ -1,3 +1,4 @@
+import warnings
from typing import Callable, Dict, List, Literal, Optional
from autogen.agentchat.contrib.retrieve_user_proxy_agent import RetrieveUserProxyAgent
@@ -93,6 +94,11 @@ def __init__(
**kwargs (dict): other kwargs in [UserProxyAgent](../user_proxy_agent#__init__).
"""
+ warnings.warn(
+ "The QdrantRetrieveUserProxyAgent is deprecated. Please use the RetrieveUserProxyAgent instead, set `vector_db` to `qdrant`.",
+ DeprecationWarning,
+ stacklevel=2,
+ )
super().__init__(name, human_input_mode, is_termination_msg, retrieve_config, **kwargs)
self._client = self._retrieve_config.get("client", QdrantClient(":memory:"))
self._embedding_model = self._retrieve_config.get("embedding_model", "BAAI/bge-small-en-v1.5")
diff --git a/autogen/agentchat/contrib/retrieve_assistant_agent.py b/autogen/agentchat/contrib/retrieve_assistant_agent.py
index 9b5ace200dc..173bc4432e7 100644
--- a/autogen/agentchat/contrib/retrieve_assistant_agent.py
+++ b/autogen/agentchat/contrib/retrieve_assistant_agent.py
@@ -1,3 +1,4 @@
+import warnings
from typing import Any, Dict, List, Optional, Tuple, Union
from autogen.agentchat.agent import Agent
@@ -16,6 +17,11 @@ class RetrieveAssistantAgent(AssistantAgent):
"""
def __init__(self, *args, **kwargs):
+ warnings.warn(
+ "The RetrieveAssistantAgent is deprecated. Please use the AssistantAgent instead.",
+ DeprecationWarning,
+ stacklevel=2,
+ )
super().__init__(*args, **kwargs)
self.register_reply(Agent, RetrieveAssistantAgent._generate_retrieve_assistant_reply)
diff --git a/autogen/agentchat/contrib/retrieve_user_proxy_agent.py b/autogen/agentchat/contrib/retrieve_user_proxy_agent.py
index 90757af6fc3..10b70e0e972 100644
--- a/autogen/agentchat/contrib/retrieve_user_proxy_agent.py
+++ b/autogen/agentchat/contrib/retrieve_user_proxy_agent.py
@@ -189,7 +189,7 @@ def __init__(
interactive retrieval. Default is True.
- `collection_name` (Optional, str) - the name of the collection.
If key not provided, a default name `autogen-docs` will be used.
- - `get_or_create` (Optional, bool) - Whether to get the collection if it exists. Default is True.
+ - `get_or_create` (Optional, bool) - Whether to get the collection if it exists. Default is False.
- `overwrite` (Optional, bool) - Whether to overwrite the collection if it exists. Default is False.
Case 1. if the collection does not exist, create the collection.
Case 2. the collection exists, if overwrite is True, it will overwrite the collection.
@@ -306,6 +306,10 @@ def retrieve_docs(self, problem: str, n_results: int = 20, search_string: str =
self._db_config["embedding_function"] = self._embedding_function
self._vector_db = VectorDBFactory.create_vector_db(db_type=self._vector_db, **self._db_config)
self.register_reply(Agent, RetrieveUserProxyAgent._generate_retrieve_user_reply, position=2)
+ self.register_hook(
+ hookable_method="process_message_before_send",
+ hook=self._check_update_context_before_send,
+ )
def _init_db(self):
if not self._vector_db:
@@ -400,6 +404,34 @@ def _is_termination_msg_retrievechat(self, message):
update_context_case1, update_context_case2 = self._check_update_context(message)
return not (contain_code or update_context_case1 or update_context_case2)
+ def _check_update_context_before_send(self, sender, message, recipient, silent):
+ if not isinstance(message, (str, dict)):
+ return message
+ elif isinstance(message, dict):
+ msg_text = message.get("content", message)
+ else:
+ msg_text = message
+
+ if "UPDATE CONTEXT" == msg_text.strip().upper():
+ doc_contents = self._get_context(self._results)
+
+ # Always use self.problem as the query text to retrieve docs, but each time we replace the context with the
+ # next similar docs in the retrieved doc results.
+ if not doc_contents:
+ for _tmp_retrieve_count in range(1, 5):
+ self._reset(intermediate=True)
+ self.retrieve_docs(
+ self.problem, self.n_results * (2 * _tmp_retrieve_count + 1), self._search_string
+ )
+ doc_contents = self._get_context(self._results)
+ if doc_contents or self.n_results * (2 * _tmp_retrieve_count + 1) >= len(self._results[0]):
+ break
+ msg_text = self._generate_message(doc_contents, task=self._task)
+
+ if isinstance(message, dict):
+ message["content"] = msg_text
+ return message
+
@staticmethod
def get_max_tokens(model="gpt-3.5-turbo"):
if "32k" in model:
diff --git a/autogen/agentchat/contrib/vectordb/qdrant.py b/autogen/agentchat/contrib/vectordb/qdrant.py
index 398734eb033..d9c4ee1d2e5 100644
--- a/autogen/agentchat/contrib/vectordb/qdrant.py
+++ b/autogen/agentchat/contrib/vectordb/qdrant.py
@@ -93,7 +93,7 @@ def __init__(
kwargs: dict | Additional keyword arguments.
"""
self.client: QdrantClient = client or QdrantClient(location=":memory:")
- self.embedding_function = FastEmbedEmbeddingFunction() or embedding_function
+ self.embedding_function = embedding_function or FastEmbedEmbeddingFunction()
self.collection_options = collection_options
self.content_payload_key = content_payload_key
self.metadata_payload_key = metadata_payload_key
diff --git a/autogen/agentchat/conversable_agent.py b/autogen/agentchat/conversable_agent.py
index a088c491082..eabe6d6d460 100644
--- a/autogen/agentchat/conversable_agent.py
+++ b/autogen/agentchat/conversable_agent.py
@@ -377,9 +377,9 @@ def replace_reply_func(self, old_reply_func: Callable, new_reply_func: Callable)
f["reply_func"] = new_reply_func
@staticmethod
- def _summary_from_nested_chats(
+ def _get_chats_to_run(
chat_queue: List[Dict[str, Any]], recipient: Agent, messages: Union[str, Callable], sender: Agent, config: Any
- ) -> Tuple[bool, str]:
+ ) -> List[Dict[str, Any]]:
"""A simple chat reply function.
This function initiate one or a sequence of chats between the "recipient" and the agents in the
chat_queue.
@@ -406,22 +406,59 @@ def _summary_from_nested_chats(
if message:
current_c["message"] = message
chat_to_run.append(current_c)
+ return chat_to_run
+
+ @staticmethod
+ def _summary_from_nested_chats(
+ chat_queue: List[Dict[str, Any]], recipient: Agent, messages: Union[str, Callable], sender: Agent, config: Any
+ ) -> Tuple[bool, Union[str, None]]:
+ """A simple chat reply function.
+ This function initiate one or a sequence of chats between the "recipient" and the agents in the
+ chat_queue.
+
+ It extracts and returns a summary from the nested chat based on the "summary_method" in each chat in chat_queue.
+
+ Returns:
+ Tuple[bool, str]: A tuple where the first element indicates the completion of the chat, and the second element contains the summary of the last chat if any chats were initiated.
+ """
+ chat_to_run = ConversableAgent._get_chats_to_run(chat_queue, recipient, messages, sender, config)
if not chat_to_run:
return True, None
res = initiate_chats(chat_to_run)
return True, res[-1].summary
+ @staticmethod
+ async def _a_summary_from_nested_chats(
+ chat_queue: List[Dict[str, Any]], recipient: Agent, messages: Union[str, Callable], sender: Agent, config: Any
+ ) -> Tuple[bool, Union[str, None]]:
+ """A simple chat reply function.
+ This function initiate one or a sequence of chats between the "recipient" and the agents in the
+ chat_queue.
+
+ It extracts and returns a summary from the nested chat based on the "summary_method" in each chat in chat_queue.
+
+ Returns:
+ Tuple[bool, str]: A tuple where the first element indicates the completion of the chat, and the second element contains the summary of the last chat if any chats were initiated.
+ """
+ chat_to_run = ConversableAgent._get_chats_to_run(chat_queue, recipient, messages, sender, config)
+ if not chat_to_run:
+ return True, None
+ res = await a_initiate_chats(chat_to_run)
+ index_of_last_chat = chat_to_run[-1]["chat_id"]
+ return True, res[index_of_last_chat].summary
+
def register_nested_chats(
self,
chat_queue: List[Dict[str, Any]],
trigger: Union[Type[Agent], str, Agent, Callable[[Agent], bool], List],
reply_func_from_nested_chats: Union[str, Callable] = "summary_from_nested_chats",
position: int = 2,
+ use_async: Union[bool, None] = None,
**kwargs,
) -> None:
"""Register a nested chat reply function.
Args:
- chat_queue (list): a list of chat objects to be initiated.
+ chat_queue (list): a list of chat objects to be initiated. If use_async is used, then all messages in chat_queue must have a chat-id associated with them.
trigger (Agent class, str, Agent instance, callable, or list): refer to `register_reply` for details.
reply_func_from_nested_chats (Callable, str): the reply function for the nested chat.
The function takes a chat_queue for nested chat, recipient agent, a list of messages, a sender agent and a config as input and returns a reply message.
@@ -436,15 +473,33 @@ def reply_func_from_nested_chats(
) -> Tuple[bool, Union[str, Dict, None]]:
```
position (int): Ref to `register_reply` for details. Default to 2. It means we first check the termination and human reply, then check the registered nested chat reply.
+ use_async: Uses a_initiate_chats internally to start nested chats. If the original chat is initiated with a_initiate_chats, you may set this to true so nested chats do not run in sync.
kwargs: Ref to `register_reply` for details.
"""
- if reply_func_from_nested_chats == "summary_from_nested_chats":
- reply_func_from_nested_chats = self._summary_from_nested_chats
- if not callable(reply_func_from_nested_chats):
- raise ValueError("reply_func_from_nested_chats must be a callable")
+ if use_async:
+ for chat in chat_queue:
+ if chat.get("chat_id") is None:
+ raise ValueError("chat_id is required for async nested chats")
+
+ if use_async:
+ if reply_func_from_nested_chats == "summary_from_nested_chats":
+ reply_func_from_nested_chats = self._a_summary_from_nested_chats
+ if not callable(reply_func_from_nested_chats) or not inspect.iscoroutinefunction(
+ reply_func_from_nested_chats
+ ):
+ raise ValueError("reply_func_from_nested_chats must be a callable and a coroutine")
+
+ async def wrapped_reply_func(recipient, messages=None, sender=None, config=None):
+ return await reply_func_from_nested_chats(chat_queue, recipient, messages, sender, config)
+
+ else:
+ if reply_func_from_nested_chats == "summary_from_nested_chats":
+ reply_func_from_nested_chats = self._summary_from_nested_chats
+ if not callable(reply_func_from_nested_chats):
+ raise ValueError("reply_func_from_nested_chats must be a callable")
- def wrapped_reply_func(recipient, messages=None, sender=None, config=None):
- return reply_func_from_nested_chats(chat_queue, recipient, messages, sender, config)
+ def wrapped_reply_func(recipient, messages=None, sender=None, config=None):
+ return reply_func_from_nested_chats(chat_queue, recipient, messages, sender, config)
functools.update_wrapper(wrapped_reply_func, reply_func_from_nested_chats)
@@ -454,7 +509,9 @@ def wrapped_reply_func(recipient, messages=None, sender=None, config=None):
position,
kwargs.get("config"),
kwargs.get("reset_config"),
- ignore_async_in_sync_chat=kwargs.get("ignore_async_in_sync_chat"),
+ ignore_async_in_sync_chat=(
+ not use_async if use_async is not None else kwargs.get("ignore_async_in_sync_chat")
+ ),
)
@property
@@ -564,7 +621,7 @@ def _assert_valid_name(name):
raise ValueError(f"Invalid name: {name}. Name must be less than 64 characters.")
return name
- def _append_oai_message(self, message: Union[Dict, str], role, conversation_id: Agent) -> bool:
+ def _append_oai_message(self, message: Union[Dict, str], role, conversation_id: Agent, is_sending: bool) -> bool:
"""Append a message to the ChatCompletion conversation.
If the message received is a string, it will be put in the "content" field of the new dictionary.
@@ -576,6 +633,7 @@ def _append_oai_message(self, message: Union[Dict, str], role, conversation_id:
message (dict or str): message to be appended to the ChatCompletion conversation.
role (str): role of the message, can be "assistant" or "function".
conversation_id (Agent): id of the conversation, should be the recipient or sender.
+ is_sending (bool): If the agent (aka self) is sending to the conversation_id agent, otherwise receiving.
Returns:
bool: whether the message is appended to the ChatCompletion conversation.
@@ -605,7 +663,15 @@ def _append_oai_message(self, message: Union[Dict, str], role, conversation_id:
if oai_message.get("function_call", False) or oai_message.get("tool_calls", False):
oai_message["role"] = "assistant" # only messages with role 'assistant' can have a function call.
+ elif "name" not in oai_message:
+ # If we don't have a name field, append it
+ if is_sending:
+ oai_message["name"] = self.name
+ else:
+ oai_message["name"] = conversation_id.name
+
self._oai_messages[conversation_id].append(oai_message)
+
return True
def _process_message_before_send(
@@ -661,7 +727,7 @@ def send(
message = self._process_message_before_send(message, recipient, ConversableAgent._is_silent(self, silent))
# When the agent composes and sends the message, the role of the message is "assistant"
# unless it's "function".
- valid = self._append_oai_message(message, "assistant", recipient)
+ valid = self._append_oai_message(message, "assistant", recipient, is_sending=True)
if valid:
recipient.receive(message, self, request_reply, silent)
else:
@@ -711,7 +777,7 @@ async def a_send(
message = self._process_message_before_send(message, recipient, ConversableAgent._is_silent(self, silent))
# When the agent composes and sends the message, the role of the message is "assistant"
# unless it's "function".
- valid = self._append_oai_message(message, "assistant", recipient)
+ valid = self._append_oai_message(message, "assistant", recipient, is_sending=True)
if valid:
await recipient.a_receive(message, self, request_reply, silent)
else:
@@ -782,7 +848,7 @@ def _print_received_message(self, message: Union[Dict, str], sender: Agent):
def _process_received_message(self, message: Union[Dict, str], sender: Agent, silent: bool):
# When the agent receives a message, the role of the message is "user". (If 'role' exists and is 'function', it will remain unchanged.)
- valid = self._append_oai_message(message, "user", sender)
+ valid = self._append_oai_message(message, "user", sender, is_sending=False)
if logging_enabled():
log_event(self, "received_message", message=message, sender=sender.name, valid=valid)
diff --git a/autogen/agentchat/groupchat.py b/autogen/agentchat/groupchat.py
index 48f11d526cc..2ebdf95b7d3 100644
--- a/autogen/agentchat/groupchat.py
+++ b/autogen/agentchat/groupchat.py
@@ -5,7 +5,7 @@
import re
import sys
from dataclasses import dataclass, field
-from typing import Callable, Dict, List, Literal, Optional, Tuple, Union
+from typing import Any, Callable, Dict, List, Literal, Optional, Tuple, Union
from ..code_utils import content_str
from ..exception_utils import AgentNameConflict, NoEligibleSpeaker, UndefinedNextAgent
@@ -17,6 +17,12 @@
from .chat import ChatResult
from .conversable_agent import ConversableAgent
+try:
+ # Non-core module
+ from .contrib.capabilities import transform_messages
+except ImportError:
+ transform_messages = None
+
logger = logging.getLogger(__name__)
@@ -76,6 +82,8 @@ def custom_speaker_selection_func(
of times until a single agent is returned or it exhausts the maximum attempts.
Applies only to "auto" speaker selection method.
Default is 2.
+ - select_speaker_transform_messages: (optional) the message transformations to apply to the nested select speaker agent-to-agent chat messages.
+ Takes a TransformMessages object, defaults to None and is only utilised when the speaker selection method is "auto".
- select_speaker_auto_verbose: whether to output the select speaker responses and selections
If set to True, the outputs from the two agents in the nested select speaker chat will be output, along with
whether the responses were successful, or not, in selecting an agent
@@ -132,6 +140,7 @@ def custom_speaker_selection_func(
The names are case-sensitive and should not be abbreviated or changed.
The only names that are accepted are {agentlist}.
Respond with ONLY the name of the speaker and DO NOT provide a reason."""
+ select_speaker_transform_messages: Optional[Any] = None
select_speaker_auto_verbose: Optional[bool] = False
role_for_select_speaker_messages: Optional[str] = "system"
@@ -249,6 +258,20 @@ def __post_init__(self):
elif self.max_retries_for_selecting_speaker < 0:
raise ValueError("max_retries_for_selecting_speaker must be greater than or equal to zero")
+ # Load message transforms here (load once for the Group Chat so we don't have to re-initiate it and it maintains the cache across subsequent select speaker calls)
+ self._speaker_selection_transforms = None
+ if self.select_speaker_transform_messages is not None:
+ if transform_messages is not None:
+ if isinstance(self.select_speaker_transform_messages, transform_messages.TransformMessages):
+ self._speaker_selection_transforms = self.select_speaker_transform_messages
+ else:
+ raise ValueError("select_speaker_transform_messages must be None or MessageTransforms.")
+ else:
+ logger.warning(
+ "TransformMessages could not be loaded, the 'select_speaker_transform_messages' transform"
+ "will not apply."
+ )
+
# Validate select_speaker_auto_verbose
if self.select_speaker_auto_verbose is None or not isinstance(self.select_speaker_auto_verbose, bool):
raise ValueError("select_speaker_auto_verbose cannot be None or non-bool")
@@ -649,11 +672,16 @@ def validate_speaker_name(recipient, messages, sender, config) -> Tuple[bool, Un
if self.select_speaker_prompt_template is not None:
start_message = {
"content": self.select_speaker_prompt(agents),
+ "name": "checking_agent",
"override_role": self.role_for_select_speaker_messages,
}
else:
start_message = messages[-1]
+ # Add the message transforms, if any, to the speaker selection agent
+ if self._speaker_selection_transforms is not None:
+ self._speaker_selection_transforms.add_to_agent(speaker_selection_agent)
+
# Run the speaker selection chat
result = checking_agent.initiate_chat(
speaker_selection_agent,
@@ -748,6 +776,10 @@ def validate_speaker_name(recipient, messages, sender, config) -> Tuple[bool, Un
else:
start_message = messages[-1]
+ # Add the message transforms, if any, to the speaker selection agent
+ if self._speaker_selection_transforms is not None:
+ self._speaker_selection_transforms.add_to_agent(speaker_selection_agent)
+
# Run the speaker selection chat
result = await checking_agent.a_initiate_chat(
speaker_selection_agent,
@@ -813,6 +845,7 @@ def _validate_speaker_name(
return True, {
"content": self.select_speaker_auto_multiple_template.format(agentlist=agentlist),
+ "name": "checking_agent",
"override_role": self.role_for_select_speaker_messages,
}
else:
@@ -842,6 +875,7 @@ def _validate_speaker_name(
return True, {
"content": self.select_speaker_auto_none_template.format(agentlist=agentlist),
+ "name": "checking_agent",
"override_role": self.role_for_select_speaker_messages,
}
else:
@@ -965,6 +999,7 @@ def __init__(
# Store groupchat
self._groupchat = groupchat
+ self._last_speaker = None
self._silent = silent
# Order of register_reply is important.
@@ -1006,6 +1041,53 @@ def _prepare_chat(
if (recipient != agent or prepare_recipient) and isinstance(agent, ConversableAgent):
agent._prepare_chat(self, clear_history, False, reply_at_receive)
+ @property
+ def last_speaker(self) -> Agent:
+ """Return the agent who sent the last message to group chat manager.
+
+ In a group chat, an agent will always send a message to the group chat manager, and the group chat manager will
+ send the message to all other agents in the group chat. So, when an agent receives a message, it will always be
+ from the group chat manager. With this property, the agent receiving the message can know who actually sent the
+ message.
+
+ Example:
+ ```python
+ from autogen import ConversableAgent
+ from autogen import GroupChat, GroupChatManager
+
+
+ def print_messages(recipient, messages, sender, config):
+ # Print the message immediately
+ print(
+ f"Sender: {sender.name} | Recipient: {recipient.name} | Message: {messages[-1].get('content')}"
+ )
+ print(f"Real Sender: {sender.last_speaker.name}")
+ assert sender.last_speaker.name in messages[-1].get("content")
+ return False, None # Required to ensure the agent communication flow continues
+
+
+ agent_a = ConversableAgent("agent A", default_auto_reply="I'm agent A.")
+ agent_b = ConversableAgent("agent B", default_auto_reply="I'm agent B.")
+ agent_c = ConversableAgent("agent C", default_auto_reply="I'm agent C.")
+ for agent in [agent_a, agent_b, agent_c]:
+ agent.register_reply(
+ [ConversableAgent, None], reply_func=print_messages, config=None
+ )
+ group_chat = GroupChat(
+ [agent_a, agent_b, agent_c],
+ messages=[],
+ max_round=6,
+ speaker_selection_method="random",
+ allow_repeat_speaker=True,
+ )
+ chat_manager = GroupChatManager(group_chat)
+ groupchat_result = agent_a.initiate_chat(
+ chat_manager, message="Hi, there, I'm agent A."
+ )
+ ```
+ """
+ return self._last_speaker
+
def run_chat(
self,
messages: Optional[List[Dict]] = None,
@@ -1034,6 +1116,7 @@ def run_chat(
a.previous_cache = a.client_cache
a.client_cache = self.client_cache
for i in range(groupchat.max_round):
+ self._last_speaker = speaker
groupchat.append(message, speaker)
# broadcast the message to all agents except the speaker
for agent in groupchat.agents:
@@ -1212,11 +1295,10 @@ def resume(
if not message_speaker_agent and message["name"] == self.name:
message_speaker_agent = self
- # Add previous messages to each agent (except their own messages and the last message, as we'll kick off the conversation with it)
+ # Add previous messages to each agent (except the last message, as we'll kick off the conversation with it)
if i != len(messages) - 1:
for agent in self._groupchat.agents:
- if agent.name != message["name"]:
- self.send(message, self._groupchat.agent_by_name(agent.name), request_reply=False, silent=True)
+ self.send(message, self._groupchat.agent_by_name(agent.name), request_reply=False, silent=True)
# Add previous message to the new groupchat, if it's an admin message the name may not match so add the message directly
if message_speaker_agent:
@@ -1258,7 +1340,7 @@ def resume(
async def a_resume(
self,
messages: Union[List[Dict], str],
- remove_termination_string: Union[str, Callable[[str], str]],
+ remove_termination_string: Union[str, Callable[[str], str]] = None,
silent: Optional[bool] = False,
) -> Tuple[ConversableAgent, Dict]:
"""Resumes a group chat using the previous messages as a starting point, asynchronously. Requires the agents, group chat, and group chat manager to be established
diff --git a/autogen/coding/func_with_reqs.py b/autogen/coding/func_with_reqs.py
index 6f199573822..f255f1df017 100644
--- a/autogen/coding/func_with_reqs.py
+++ b/autogen/coding/func_with_reqs.py
@@ -6,7 +6,7 @@
from dataclasses import dataclass, field
from importlib.abc import SourceLoader
from textwrap import dedent, indent
-from typing import Any, Callable, Generic, List, TypeVar, Union
+from typing import Any, Callable, Generic, List, Set, TypeVar, Union
from typing_extensions import ParamSpec
@@ -159,12 +159,12 @@ def _build_python_functions_file(
funcs: List[Union[FunctionWithRequirements[Any, P], Callable[..., Any], FunctionWithRequirementsStr]]
) -> str:
# First collect all global imports
- global_imports = set()
+ global_imports: Set[str] = set()
for func in funcs:
if isinstance(func, (FunctionWithRequirements, FunctionWithRequirementsStr)):
- global_imports.update(func.global_imports)
+ global_imports.update(map(_import_to_str, func.global_imports))
- content = "\n".join(map(_import_to_str, global_imports)) + "\n\n"
+ content = "\n".join(global_imports) + "\n\n"
for func in funcs:
content += _to_code(func) + "\n\n"
diff --git a/autogen/logger/file_logger.py b/autogen/logger/file_logger.py
index 0e9e14f3333..20df88101d1 100644
--- a/autogen/logger/file_logger.py
+++ b/autogen/logger/file_logger.py
@@ -18,6 +18,7 @@
if TYPE_CHECKING:
from autogen import Agent, ConversableAgent, OpenAIWrapper
from autogen.oai.anthropic import AnthropicClient
+ from autogen.oai.bedrock import BedrockClient
from autogen.oai.cohere import CohereClient
from autogen.oai.gemini import GeminiClient
from autogen.oai.groq import GroqClient
@@ -217,6 +218,7 @@ def log_new_client(
| GroqClient
| CohereClient
| OllamaClient
+ | BedrockClient
),
wrapper: OpenAIWrapper,
init_args: Dict[str, Any],
diff --git a/autogen/logger/sqlite_logger.py b/autogen/logger/sqlite_logger.py
index 3bebffc126d..592af7c9389 100644
--- a/autogen/logger/sqlite_logger.py
+++ b/autogen/logger/sqlite_logger.py
@@ -19,6 +19,7 @@
if TYPE_CHECKING:
from autogen import Agent, ConversableAgent, OpenAIWrapper
from autogen.oai.anthropic import AnthropicClient
+ from autogen.oai.bedrock import BedrockClient
from autogen.oai.cohere import CohereClient
from autogen.oai.gemini import GeminiClient
from autogen.oai.groq import GroqClient
@@ -404,6 +405,7 @@ def log_new_client(
GroqClient,
CohereClient,
OllamaClient,
+ BedrockClient,
],
wrapper: OpenAIWrapper,
init_args: Dict[str, Any],
diff --git a/autogen/oai/bedrock.py b/autogen/oai/bedrock.py
new file mode 100644
index 00000000000..7894781e3ee
--- /dev/null
+++ b/autogen/oai/bedrock.py
@@ -0,0 +1,606 @@
+"""
+Create a compatible client for the Amazon Bedrock Converse API.
+
+Example usage:
+Install the `boto3` package by running `pip install --upgrade boto3`.
+- https://docs.aws.amazon.com/bedrock/latest/userguide/conversation-inference.html
+
+import autogen
+
+config_list = [
+ {
+ "api_type": "bedrock",
+ "model": "meta.llama3-1-8b-instruct-v1:0",
+ "aws_region": "us-west-2",
+ "aws_access_key": "",
+ "aws_secret_key": "",
+ "price" : [0.003, 0.015]
+ }
+]
+
+assistant = autogen.AssistantAgent("assistant", llm_config={"config_list": config_list})
+
+"""
+
+from __future__ import annotations
+
+import base64
+import json
+import os
+import re
+import time
+import warnings
+from typing import Any, Dict, List, Literal, Tuple
+
+import boto3
+import requests
+from botocore.config import Config
+from openai.types.chat import ChatCompletion, ChatCompletionMessageToolCall
+from openai.types.chat.chat_completion import ChatCompletionMessage, Choice
+from openai.types.completion_usage import CompletionUsage
+
+from autogen.oai.client_utils import validate_parameter
+
+
+class BedrockClient:
+ """Client for Amazon's Bedrock Converse API."""
+
+ _retries = 5
+
+ def __init__(self, **kwargs: Any):
+ """
+ Initialises BedrockClient for Amazon's Bedrock Converse API
+ """
+ self._aws_access_key = kwargs.get("aws_access_key", None)
+ self._aws_secret_key = kwargs.get("aws_secret_key", None)
+ self._aws_session_token = kwargs.get("aws_session_token", None)
+ self._aws_region = kwargs.get("aws_region", None)
+ self._aws_profile_name = kwargs.get("aws_profile_name", None)
+
+ if not self._aws_access_key:
+ self._aws_access_key = os.getenv("AWS_ACCESS_KEY")
+
+ if not self._aws_secret_key:
+ self._aws_secret_key = os.getenv("AWS_SECRET_KEY")
+
+ if not self._aws_session_token:
+ self._aws_session_token = os.getenv("AWS_SESSION_TOKEN")
+
+ if not self._aws_region:
+ self._aws_region = os.getenv("AWS_REGION")
+
+ if self._aws_region is None:
+ raise ValueError("Region is required to use the Amazon Bedrock API.")
+
+ # Initialize Bedrock client, session, and runtime
+ bedrock_config = Config(
+ region_name=self._aws_region,
+ signature_version="v4",
+ retries={"max_attempts": self._retries, "mode": "standard"},
+ )
+
+ session = boto3.Session(
+ aws_access_key_id=self._aws_access_key,
+ aws_secret_access_key=self._aws_secret_key,
+ aws_session_token=self._aws_session_token,
+ profile_name=self._aws_profile_name,
+ )
+
+ self.bedrock_runtime = session.client(service_name="bedrock-runtime", config=bedrock_config)
+
+ def message_retrieval(self, response):
+ """Retrieve the messages from the response."""
+ return [choice.message for choice in response.choices]
+
+ def parse_custom_params(self, params: Dict[str, Any]):
+ """
+ Parses custom parameters for logic in this client class
+ """
+
+ # Should we separate system messages into its own request parameter, default is True
+ # This is required because not all models support a system prompt (e.g. Mistral Instruct).
+ self._supports_system_prompts = params.get("supports_system_prompts", True)
+
+ def parse_params(self, params: Dict[str, Any]) -> tuple[Dict[str, Any], Dict[str, Any]]:
+ """
+ Loads the valid parameters required to invoke Bedrock Converse
+ Returns a tuple of (base_params, additional_params)
+ """
+
+ base_params = {}
+ additional_params = {}
+
+ # Amazon Bedrock base model IDs are here:
+ # https://docs.aws.amazon.com/bedrock/latest/userguide/model-ids.html
+ self._model_id = params.get("model", None)
+ assert self._model_id, "Please provide the 'model` in the config_list to use Amazon Bedrock"
+
+ # Parameters vary based on the model used.
+ # As we won't cater for all models and parameters, it's the developer's
+ # responsibility to implement the parameters and they will only be
+ # included if the developer has it in the config.
+ #
+ # Important:
+ # No defaults will be used (as they can vary per model)
+ # No ranges will be used (as they can vary)
+ # We will cover all the main parameters but there may be others
+ # that need to be added later
+ #
+ # Here are some pages that show the parameters available for different models
+ # https://docs.aws.amazon.com/bedrock/latest/userguide/model-parameters-titan-text.html
+ # https://docs.aws.amazon.com/bedrock/latest/userguide/model-parameters-anthropic-claude-text-completion.html
+ # https://docs.aws.amazon.com/bedrock/latest/userguide/model-parameters-cohere-command-r-plus.html
+ # https://docs.aws.amazon.com/bedrock/latest/userguide/model-parameters-meta.html
+ # https://docs.aws.amazon.com/bedrock/latest/userguide/model-parameters-mistral-chat-completion.html
+
+ # Here are the possible "base" parameters and their suitable types
+ base_parameters = [["temperature", (float, int)], ["topP", (float, int)], ["maxTokens", (int)]]
+
+ for param_name, suitable_types in base_parameters:
+ if param_name in params:
+ base_params[param_name] = validate_parameter(
+ params, param_name, suitable_types, False, None, None, None
+ )
+
+ # Here are the possible "model-specific" parameters and their suitable types, known as additional parameters
+ additional_parameters = [
+ ["top_p", (float, int)],
+ ["top_k", (int)],
+ ["k", (int)],
+ ["seed", (int)],
+ ]
+
+ for param_name, suitable_types in additional_parameters:
+ if param_name in params:
+ additional_params[param_name] = validate_parameter(
+ params, param_name, suitable_types, False, None, None, None
+ )
+
+ # Streaming
+ if "stream" in params:
+ self._streaming = params["stream"]
+ else:
+ self._streaming = False
+
+ # For this release we will not support streaming as many models do not support streaming with tool use
+ if self._streaming:
+ warnings.warn(
+ "Streaming is not currently supported, streaming will be disabled.",
+ UserWarning,
+ )
+ self._streaming = False
+
+ return base_params, additional_params
+
+ def create(self, params):
+ """Run Amazon Bedrock inference and return AutoGen response"""
+
+ # Set custom client class settings
+ self.parse_custom_params(params)
+
+ # Parse the inference parameters
+ base_params, additional_params = self.parse_params(params)
+
+ has_tools = "tools" in params
+ messages = oai_messages_to_bedrock_messages(params["messages"], has_tools, self._supports_system_prompts)
+
+ if self._supports_system_prompts:
+ system_messages = extract_system_messages(params["messages"])
+
+ tool_config = format_tools(params["tools"] if has_tools else [])
+
+ request_args = {"messages": messages, "modelId": self._model_id}
+
+ # Base and additional args
+ if len(base_params) > 0:
+ request_args["inferenceConfig"] = base_params
+
+ if len(additional_params) > 0:
+ request_args["additionalModelRequestFields"] = additional_params
+
+ if self._supports_system_prompts:
+ request_args["system"] = system_messages
+
+ if len(tool_config["tools"]) > 0:
+ request_args["toolConfig"] = tool_config
+
+ try:
+ response = self.bedrock_runtime.converse(
+ **request_args,
+ )
+ except Exception as e:
+ raise RuntimeError(f"Failed to get response from Bedrock: {e}")
+
+ if response is None:
+ raise RuntimeError(f"Failed to get response from Bedrock after retrying {self._retries} times.")
+
+ finish_reason = convert_stop_reason_to_finish_reason(response["stopReason"])
+ response_message = response["output"]["message"]
+
+ if finish_reason == "tool_calls":
+ tool_calls = format_tool_calls(response_message["content"])
+ # text = ""
+ else:
+ tool_calls = None
+
+ text = ""
+ for content in response_message["content"]:
+ if "text" in content:
+ text = content["text"]
+ # NOTE: other types of output may be dealt with here
+
+ message = ChatCompletionMessage(role="assistant", content=text, tool_calls=tool_calls)
+
+ response_usage = response["usage"]
+ usage = CompletionUsage(
+ prompt_tokens=response_usage["inputTokens"],
+ completion_tokens=response_usage["outputTokens"],
+ total_tokens=response_usage["totalTokens"],
+ )
+
+ return ChatCompletion(
+ id=response["ResponseMetadata"]["RequestId"],
+ choices=[Choice(finish_reason=finish_reason, index=0, message=message)],
+ created=int(time.time()),
+ model=self._model_id,
+ object="chat.completion",
+ usage=usage,
+ )
+
+ def cost(self, response: ChatCompletion) -> float:
+ """Calculate the cost of the response."""
+ return calculate_cost(response.usage.prompt_tokens, response.usage.completion_tokens, response.model)
+
+ @staticmethod
+ def get_usage(response) -> Dict:
+ """Get the usage of tokens and their cost information."""
+ return {
+ "prompt_tokens": response.usage.prompt_tokens,
+ "completion_tokens": response.usage.completion_tokens,
+ "total_tokens": response.usage.total_tokens,
+ "cost": response.cost,
+ "model": response.model,
+ }
+
+
+def extract_system_messages(messages: List[dict]) -> List:
+ """Extract the system messages from the list of messages.
+
+ Args:
+ messages (list[dict]): List of messages.
+
+ Returns:
+ List[SystemMessage]: List of System messages.
+ """
+
+ """
+ system_messages = [message.get("content")[0]["text"] for message in messages if message.get("role") == "system"]
+ return system_messages # ''.join(system_messages)
+ """
+
+ for message in messages:
+ if message.get("role") == "system":
+ if isinstance(message["content"], str):
+ return [{"text": message.get("content")}]
+ else:
+ return [{"text": message.get("content")[0]["text"]}]
+ return []
+
+
+def oai_messages_to_bedrock_messages(
+ messages: List[Dict[str, Any]], has_tools: bool, supports_system_prompts: bool
+) -> List[Dict]:
+ """
+ Convert messages from OAI format to Bedrock format.
+ We correct for any specific role orders and types, etc.
+ AWS Bedrock requires messages to alternate between user and assistant roles. This function ensures that the messages
+ are in the correct order and format for Bedrock by inserting "Please continue" messages as needed.
+ This is the same method as the one in the Autogen Anthropic client
+ """
+
+ # Track whether we have tools passed in. If not, tool use / result messages should be converted to text messages.
+ # Bedrock requires a tools parameter with the tools listed, if there are other messages with tool use or tool results.
+ # This can occur when we don't need tool calling, such as for group chat speaker selection
+
+ # Convert messages to Bedrock compliant format
+
+ # Take out system messages if the model supports it, otherwise leave them in.
+ if supports_system_prompts:
+ messages = [x for x in messages if not x["role"] == "system"]
+ else:
+ # Replace role="system" with role="user"
+ for msg in messages:
+ if msg["role"] == "system":
+ msg["role"] = "user"
+
+ processed_messages = []
+
+ # Used to interweave user messages to ensure user/assistant alternating
+ user_continue_message = {"content": [{"text": "Please continue."}], "role": "user"}
+ assistant_continue_message = {
+ "content": [{"text": "Please continue."}],
+ "role": "assistant",
+ }
+
+ tool_use_messages = 0
+ tool_result_messages = 0
+ last_tool_use_index = -1
+ last_tool_result_index = -1
+ # user_role_index = 0 if supports_system_prompts else 1 # If system prompts are supported, messages start with user, otherwise they'll be the second message
+ for message in messages:
+ # New messages will be added here, manage role alternations
+ expected_role = "user" if len(processed_messages) % 2 == 0 else "assistant"
+
+ if "tool_calls" in message:
+ # Map the tool call options to Bedrock's format
+ tool_uses = []
+ tool_names = []
+ for tool_call in message["tool_calls"]:
+ tool_uses.append(
+ {
+ "toolUse": {
+ "toolUseId": tool_call["id"],
+ "name": tool_call["function"]["name"],
+ "input": json.loads(tool_call["function"]["arguments"]),
+ }
+ }
+ )
+ if has_tools:
+ tool_use_messages += 1
+ tool_names.append(tool_call["function"]["name"])
+
+ if expected_role == "user":
+ # Insert an extra user message as we will append an assistant message
+ processed_messages.append(user_continue_message)
+
+ if has_tools:
+ processed_messages.append({"role": "assistant", "content": tool_uses})
+ last_tool_use_index = len(processed_messages) - 1
+ else:
+ # Not using tools, so put in a plain text message
+ processed_messages.append(
+ {
+ "role": "assistant",
+ "content": [
+ {"text": f"Some internal function(s) that could be used: [{', '.join(tool_names)}]"}
+ ],
+ }
+ )
+ elif "tool_call_id" in message:
+ if has_tools:
+ # Map the tool usage call to tool_result for Bedrock
+ tool_result = {
+ "toolResult": {
+ "toolUseId": message["tool_call_id"],
+ "content": [{"text": message["content"]}],
+ }
+ }
+
+ # If the previous message also had a tool_result, add it to that
+ # Otherwise append a new message
+ if last_tool_result_index == len(processed_messages) - 1:
+ processed_messages[-1]["content"].append(tool_result)
+ else:
+ if expected_role == "assistant":
+ # Insert an extra assistant message as we will append a user message
+ processed_messages.append(assistant_continue_message)
+
+ processed_messages.append({"role": "user", "content": [tool_result]})
+ last_tool_result_index = len(processed_messages) - 1
+
+ tool_result_messages += 1
+ else:
+ # Not using tools, so put in a plain text message
+ processed_messages.append(
+ {
+ "role": "user",
+ "content": [{"text": f"Running the function returned: {message['content']}"}],
+ }
+ )
+ elif message["content"] == "":
+ # Ignoring empty messages
+ pass
+ else:
+ if expected_role != message["role"] and not (len(processed_messages) == 0 and message["role"] == "system"):
+ # Inserting the alternating continue message (ignore if it's the first message and a system message)
+ processed_messages.append(
+ user_continue_message if expected_role == "user" else assistant_continue_message
+ )
+
+ processed_messages.append(
+ {
+ "role": message["role"],
+ "content": parse_content_parts(message=message),
+ }
+ )
+
+ # We'll replace the last tool_use if there's no tool_result (occurs if we finish the conversation before running the function)
+ if has_tools and tool_use_messages != tool_result_messages:
+ processed_messages[last_tool_use_index] = assistant_continue_message
+
+ # name is not a valid field on messages
+ for message in processed_messages:
+ if "name" in message:
+ message.pop("name", None)
+
+ # Note: When using reflection_with_llm we may end up with an "assistant" message as the last message and that may cause a blank response
+ # So, if the last role is not user, add a 'user' continue message at the end
+ if processed_messages[-1]["role"] != "user":
+ processed_messages.append(user_continue_message)
+
+ return processed_messages
+
+
+def parse_content_parts(
+ message: Dict[str, Any],
+) -> List[dict]:
+ content: str | List[Dict[str, Any]] = message.get("content")
+ if isinstance(content, str):
+ return [
+ {
+ "text": content,
+ }
+ ]
+ content_parts = []
+ for part in content:
+ # part_content: Dict = part.get("content")
+ if "text" in part: # part_content:
+ content_parts.append(
+ {
+ "text": part.get("text"),
+ }
+ )
+ elif "image_url" in part: # part_content:
+ image_data, content_type = parse_image(part.get("image_url").get("url"))
+ content_parts.append(
+ {
+ "image": {
+ "format": content_type[6:], # image/
+ "source": {"bytes": image_data},
+ },
+ }
+ )
+ else:
+ # Ignore..
+ continue
+ return content_parts
+
+
+def parse_image(image_url: str) -> Tuple[bytes, str]:
+ """Try to get the raw data from an image url.
+
+ Ref: https://docs.aws.amazon.com/bedrock/latest/APIReference/API_runtime_ImageSource.html
+ returns a tuple of (Image Data, Content Type)
+ """
+ pattern = r"^data:(image/[a-z]*);base64,\s*"
+ content_type = re.search(pattern, image_url)
+ # if already base64 encoded.
+ # Only supports 'image/jpeg', 'image/png', 'image/gif' or 'image/webp'
+ if content_type:
+ image_data = re.sub(pattern, "", image_url)
+ return base64.b64decode(image_data), content_type.group(1)
+
+ # Send a request to the image URL
+ response = requests.get(image_url)
+ # Check if the request was successful
+ if response.status_code == 200:
+
+ content_type = response.headers.get("Content-Type")
+ if not content_type.startswith("image"):
+ content_type = "image/jpeg"
+ # Get the image content
+ image_content = response.content
+ return image_content, content_type
+ else:
+ raise RuntimeError("Unable to access the image url")
+
+
+def format_tools(tools: List[Dict[str, Any]]) -> Dict[Literal["tools"], List[Dict[str, Any]]]:
+ converted_schema = {"tools": []}
+
+ for tool in tools:
+ if tool["type"] == "function":
+ function = tool["function"]
+ converted_tool = {
+ "toolSpec": {
+ "name": function["name"],
+ "description": function["description"],
+ "inputSchema": {"json": {"type": "object", "properties": {}, "required": []}},
+ }
+ }
+
+ for prop_name, prop_details in function["parameters"]["properties"].items():
+ converted_tool["toolSpec"]["inputSchema"]["json"]["properties"][prop_name] = {
+ "type": prop_details["type"],
+ "description": prop_details.get("description", ""),
+ }
+ if "enum" in prop_details:
+ converted_tool["toolSpec"]["inputSchema"]["json"]["properties"][prop_name]["enum"] = prop_details[
+ "enum"
+ ]
+ if "default" in prop_details:
+ converted_tool["toolSpec"]["inputSchema"]["json"]["properties"][prop_name]["default"] = (
+ prop_details["default"]
+ )
+
+ if "required" in function["parameters"]:
+ converted_tool["toolSpec"]["inputSchema"]["json"]["required"] = function["parameters"]["required"]
+
+ converted_schema["tools"].append(converted_tool)
+
+ return converted_schema
+
+
+def format_tool_calls(content):
+ """Converts Converse API response tool calls to AutoGen format"""
+ tool_calls = []
+ for tool_request in content:
+ if "toolUse" in tool_request:
+ tool = tool_request["toolUse"]
+
+ tool_calls.append(
+ ChatCompletionMessageToolCall(
+ id=tool["toolUseId"],
+ function={
+ "name": tool["name"],
+ "arguments": json.dumps(tool["input"]),
+ },
+ type="function",
+ )
+ )
+ return tool_calls
+
+
+def convert_stop_reason_to_finish_reason(
+ stop_reason: str,
+) -> Literal["stop", "length", "tool_calls", "content_filter"]:
+ """
+ Converts Bedrock finish reasons to our finish reasons, according to OpenAI:
+
+ - stop: if the model hit a natural stop point or a provided stop sequence,
+ - length: if the maximum number of tokens specified in the request was reached,
+ - content_filter: if content was omitted due to a flag from our content filters,
+ - tool_calls: if the model called a tool
+ """
+ if stop_reason:
+ finish_reason_mapping = {
+ "tool_use": "tool_calls",
+ "finished": "stop",
+ "end_turn": "stop",
+ "max_tokens": "length",
+ "stop_sequence": "stop",
+ "complete": "stop",
+ "content_filtered": "content_filter",
+ }
+ return finish_reason_mapping.get(stop_reason.lower(), stop_reason.lower())
+
+ warnings.warn(f"Unsupported stop reason: {stop_reason}", UserWarning)
+ return None
+
+
+# NOTE: As this will be quite dynamic, it's expected that the developer will use the "price" parameter in their config
+# These may be removed.
+PRICES_PER_K_TOKENS = {
+ "meta.llama3-8b-instruct-v1:0": (0.0003, 0.0006),
+ "meta.llama3-70b-instruct-v1:0": (0.00265, 0.0035),
+ "mistral.mistral-7b-instruct-v0:2": (0.00015, 0.0002),
+ "mistral.mixtral-8x7b-instruct-v0:1": (0.00045, 0.0007),
+ "mistral.mistral-large-2402-v1:0": (0.004, 0.012),
+ "mistral.mistral-small-2402-v1:0": (0.001, 0.003),
+}
+
+
+def calculate_cost(input_tokens: int, output_tokens: int, model_id: str) -> float:
+ """Calculate the cost of the completion using the Bedrock pricing."""
+
+ if model_id in PRICES_PER_K_TOKENS:
+ input_cost_per_k, output_cost_per_k = PRICES_PER_K_TOKENS[model_id]
+ input_cost = (input_tokens / 1000) * input_cost_per_k
+ output_cost = (output_tokens / 1000) * output_cost_per_k
+ return input_cost + output_cost
+ else:
+ warnings.warn(
+ f'Cannot get the costs for {model_id}. The cost will be 0. In your config_list, add field {{"price" : [prompt_price_per_1k, completion_token_price_per_1k]}} for customized pricing.',
+ UserWarning,
+ )
+ return 0
diff --git a/autogen/oai/client.py b/autogen/oai/client.py
index 0527ce2bd89..d678741c158 100644
--- a/autogen/oai/client.py
+++ b/autogen/oai/client.py
@@ -91,6 +91,13 @@
except ImportError as e:
ollama_import_exception = e
+try:
+ from autogen.oai.bedrock import BedrockClient
+
+ bedrock_import_exception: Optional[ImportError] = None
+except ImportError as e:
+ bedrock_import_exception = e
+
logger = logging.getLogger(__name__)
if not logger.handlers:
# Add the console handler.
@@ -464,10 +471,13 @@ def _configure_azure_openai(self, config: Dict[str, Any], openai_config: Dict[st
def _configure_openai_config_for_bedrock(self, config: Dict[str, Any], openai_config: Dict[str, Any]) -> None:
"""Update openai_config with AWS credentials from config."""
required_keys = ["aws_access_key", "aws_secret_key", "aws_region"]
-
+ optional_keys = ["aws_session_token", "aws_profile_name"]
for key in required_keys:
if key in config:
openai_config[key] = config[key]
+ for key in optional_keys:
+ if key in config:
+ openai_config[key] = config[key]
def _register_default_client(self, config: Dict[str, Any], openai_config: Dict[str, Any]) -> None:
"""Create a client with the given config to override openai_config,
@@ -523,7 +533,7 @@ def _register_default_client(self, config: Dict[str, Any], openai_config: Dict[s
self._clients.append(client)
elif api_type is not None and api_type.startswith("cohere"):
if cohere_import_exception:
- raise ImportError("Please install `cohere` to use the Groq API.")
+ raise ImportError("Please install `cohere` to use the Cohere API.")
client = CohereClient(**openai_config)
self._clients.append(client)
elif api_type is not None and api_type.startswith("ollama"):
@@ -531,6 +541,12 @@ def _register_default_client(self, config: Dict[str, Any], openai_config: Dict[s
raise ImportError("Please install `ollama` to use the Ollama API.")
client = OllamaClient(**openai_config)
self._clients.append(client)
+ elif api_type is not None and api_type.startswith("bedrock"):
+ self._configure_openai_config_for_bedrock(config, openai_config)
+ if bedrock_import_exception:
+ raise ImportError("Please install `boto3` to use the Amazon Bedrock API.")
+ client = BedrockClient(**openai_config)
+ self._clients.append(client)
else:
client = OpenAI(**openai_config)
self._clients.append(OpenAIClient(client))
diff --git a/autogen/oai/cohere.py b/autogen/oai/cohere.py
index 35b7ac97c4f..3d38d86425f 100644
--- a/autogen/oai/cohere.py
+++ b/autogen/oai/cohere.py
@@ -6,6 +6,7 @@
"api_type": "cohere",
"model": "command-r-plus",
"api_key": os.environ.get("COHERE_API_KEY")
+ "client_name": "autogen-cohere", # Optional parameter
}
]}
@@ -144,7 +145,7 @@ def parse_params(self, params: Dict[str, Any]) -> Dict[str, Any]:
def create(self, params: Dict) -> ChatCompletion:
messages = params.get("messages", [])
-
+ client_name = params.get("client_name") or "autogen-cohere"
# Parse parameters to the Cohere API's parameters
cohere_params = self.parse_params(params)
@@ -156,7 +157,7 @@ def create(self, params: Dict) -> ChatCompletion:
cohere_params["preamble"] = preamble
# We use chat model by default
- client = Cohere(api_key=self.api_key)
+ client = Cohere(api_key=self.api_key, client_name=client_name)
# Token counts will be returned
prompt_tokens = 0
@@ -285,6 +286,23 @@ def create(self, params: Dict) -> ChatCompletion:
return response_oai
+def extract_to_cohere_tool_results(tool_call_id: str, content_output: str, all_tool_calls) -> List[Dict[str, Any]]:
+ temp_tool_results = []
+
+ for tool_call in all_tool_calls:
+ if tool_call["id"] == tool_call_id:
+
+ call = {
+ "name": tool_call["function"]["name"],
+ "parameters": json.loads(
+ tool_call["function"]["arguments"] if not tool_call["function"]["arguments"] == "" else "{}"
+ ),
+ }
+ output = [{"value": content_output}]
+ temp_tool_results.append(ToolResult(call=call, outputs=output))
+ return temp_tool_results
+
+
def oai_messages_to_cohere_messages(
messages: list[Dict[str, Any]], params: Dict[str, Any], cohere_params: Dict[str, Any]
) -> tuple[list[dict[str, Any]], str, str]:
@@ -352,7 +370,8 @@ def oai_messages_to_cohere_messages(
# 'content' field renamed to 'message'
# tools go into tools parameter
# tool_results go into tool_results parameter
- for message in messages:
+ messages_length = len(messages)
+ for index, message in enumerate(messages):
if "role" in message and message["role"] == "system":
# System message
@@ -369,34 +388,34 @@ def oai_messages_to_cohere_messages(
new_message = {
"role": "CHATBOT",
"message": message["content"],
- # Not including tools in this message, may need to. Testing required.
+ "tool_calls": [
+ {
+ "name": tool_call_.get("function", {}).get("name"),
+ "parameters": json.loads(tool_call_.get("function", {}).get("arguments") or "null"),
+ }
+ for tool_call_ in message["tool_calls"]
+ ],
}
cohere_messages.append(new_message)
elif "role" in message and message["role"] == "tool":
- if "tool_call_id" in message:
- # Convert the tool call to a result
+ if not (tool_call_id := message.get("tool_call_id")):
+ continue
+
+ # Convert the tool call to a result
+ content_output = message["content"]
+ tool_results_chat_turn = extract_to_cohere_tool_results(tool_call_id, content_output, tool_calls)
+ if (index == messages_length - 1) or (messages[index + 1].get("role", "").lower() in ("user", "tool")):
+ # If the tool call is the last message or the next message is a user/tool message, this is a recent tool call.
+ # So, we pass it into tool_results.
+ tool_results.extend(tool_results_chat_turn)
+ continue
- tool_call_id = message["tool_call_id"]
- content_output = message["content"]
-
- # Find the original tool
- for tool_call in tool_calls:
- if tool_call["id"] == tool_call_id:
-
- call = {
- "name": tool_call["function"]["name"],
- "parameters": json.loads(
- tool_call["function"]["arguments"]
- if not tool_call["function"]["arguments"] == ""
- else "{}"
- ),
- }
- output = [{"value": content_output}]
-
- tool_results.append(ToolResult(call=call, outputs=output))
+ else:
+ # If its not the current tool call, we pass it as a tool message in the chat history.
+ new_message = {"role": "TOOL", "tool_results": tool_results_chat_turn}
+ cohere_messages.append(new_message)
- break
elif "content" in message and isinstance(message["content"], str):
# Standard text message
new_message = {
@@ -416,7 +435,7 @@ def oai_messages_to_cohere_messages(
# If we're adding tool_results, like we are, the last message can't be a USER message
# So, we add a CHATBOT 'continue' message, if so.
# Changed key from "content" to "message" (jaygdesai/autogen_Jay)
- if cohere_messages[-1]["role"] == "USER":
+ if cohere_messages[-1]["role"].lower() == "user":
cohere_messages.append({"role": "CHATBOT", "message": "Please continue."})
# We return a blank message when we have tool results
diff --git a/autogen/oai/mistral.py b/autogen/oai/mistral.py
index 8017e353632..10d0f926ffb 100644
--- a/autogen/oai/mistral.py
+++ b/autogen/oai/mistral.py
@@ -15,28 +15,32 @@
Resources:
- https://docs.mistral.ai/getting-started/quickstart/
-"""
-# Important notes when using the Mistral.AI API:
-# The first system message can greatly affect whether the model returns a tool call, including text that references the ability to use functions will help.
-# Changing the role on the first system message to 'user' improved the chances of the model recommending a tool call.
+NOTE: Requires mistralai package version >= 1.0.1
+"""
import inspect
import json
import os
import time
import warnings
-from typing import Any, Dict, List, Tuple, Union
+from typing import Any, Dict, List, Union
# Mistral libraries
# pip install mistralai
-from mistralai.client import MistralClient
-from mistralai.exceptions import MistralAPIException
-from mistralai.models.chat_completion import ChatCompletionResponse, ChatMessage, ToolCall
+from mistralai import (
+ AssistantMessage,
+ Function,
+ FunctionCall,
+ Mistral,
+ SystemMessage,
+ ToolCall,
+ ToolMessage,
+ UserMessage,
+)
from openai.types.chat import ChatCompletion, ChatCompletionMessageToolCall
from openai.types.chat.chat_completion import ChatCompletionMessage, Choice
from openai.types.completion_usage import CompletionUsage
-from typing_extensions import Annotated
from autogen.oai.client_utils import should_hide_tools, validate_parameter
@@ -50,6 +54,7 @@ def __init__(self, **kwargs):
Args:
api_key (str): The API key for using Mistral.AI (or environment variable MISTRAL_API_KEY needs to be set)
"""
+
# Ensure we have the api_key upon instantiation
self.api_key = kwargs.get("api_key", None)
if not self.api_key:
@@ -59,7 +64,9 @@ def __init__(self, **kwargs):
self.api_key
), "Please specify the 'api_key' in your config list entry for Mistral or set the MISTRAL_API_KEY env variable."
- def message_retrieval(self, response: ChatCompletionResponse) -> Union[List[str], List[ChatCompletionMessage]]:
+ self._client = Mistral(api_key=self.api_key)
+
+ def message_retrieval(self, response: ChatCompletion) -> Union[List[str], List[ChatCompletionMessage]]:
"""Retrieve the messages from the response."""
return [choice.message for choice in response.choices]
@@ -86,34 +93,52 @@ def parse_params(self, params: Dict[str, Any]) -> Dict[str, Any]:
)
mistral_params["random_seed"] = validate_parameter(params, "random_seed", int, True, None, False, None)
+ # TODO
+ if params.get("stream", False):
+ warnings.warn(
+ "Streaming is not currently supported, streaming will be disabled.",
+ UserWarning,
+ )
+
# 3. Convert messages to Mistral format
mistral_messages = []
tool_call_ids = {} # tool call ids to function name mapping
for message in params["messages"]:
if message["role"] == "assistant" and "tool_calls" in message and message["tool_calls"] is not None:
# Convert OAI ToolCall to Mistral ToolCall
- openai_toolcalls = message["tool_calls"]
- mistral_toolcalls = []
- for toolcall in openai_toolcalls:
- mistral_toolcall = ToolCall(id=toolcall["id"], function=toolcall["function"])
- mistral_toolcalls.append(mistral_toolcall)
- mistral_messages.append(
- ChatMessage(role=message["role"], content=message["content"], tool_calls=mistral_toolcalls)
- )
+ mistral_messages_tools = []
+ for toolcall in message["tool_calls"]:
+ mistral_messages_tools.append(
+ ToolCall(
+ id=toolcall["id"],
+ function=FunctionCall(
+ name=toolcall["function"]["name"],
+ arguments=json.loads(toolcall["function"]["arguments"]),
+ ),
+ )
+ )
+
+ mistral_messages.append(AssistantMessage(content="", tool_calls=mistral_messages_tools))
# Map tool call id to the function name
for tool_call in message["tool_calls"]:
tool_call_ids[tool_call["id"]] = tool_call["function"]["name"]
- elif message["role"] in ("system", "user", "assistant"):
- # Note this ChatMessage can take a 'name' but it is rejected by the Mistral API if not role=tool, so, no, the 'name' field is not used.
- mistral_messages.append(ChatMessage(role=message["role"], content=message["content"]))
+ elif message["role"] == "system":
+ if len(mistral_messages) > 0 and mistral_messages[-1].role == "assistant":
+ # System messages can't appear after an Assistant message, so use a UserMessage
+ mistral_messages.append(UserMessage(content=message["content"]))
+ else:
+ mistral_messages.append(SystemMessage(content=message["content"]))
+ elif message["role"] == "assistant":
+ mistral_messages.append(AssistantMessage(content=message["content"]))
+ elif message["role"] == "user":
+ mistral_messages.append(UserMessage(content=message["content"]))
elif message["role"] == "tool":
# Indicates the result of a tool call, the name is the function name called
mistral_messages.append(
- ChatMessage(
- role="tool",
+ ToolMessage(
name=tool_call_ids[message["tool_call_id"]],
content=message["content"],
tool_call_id=message["tool_call_id"],
@@ -122,21 +147,20 @@ def parse_params(self, params: Dict[str, Any]) -> Dict[str, Any]:
else:
warnings.warn(f"Unknown message role {message['role']}", UserWarning)
- # If a 'system' message follows an 'assistant' message, change it to 'user'
- # This can occur when using LLM summarisation
- for i in range(1, len(mistral_messages)):
- if mistral_messages[i - 1].role == "assistant" and mistral_messages[i].role == "system":
- mistral_messages[i].role = "user"
+ # 4. Last message needs to be user or tool, if not, add a "please continue" message
+ if not isinstance(mistral_messages[-1], UserMessage) and not isinstance(mistral_messages[-1], ToolMessage):
+ mistral_messages.append(UserMessage(content="Please continue."))
mistral_params["messages"] = mistral_messages
- # 4. Add tools to the call if we have them and aren't hiding them
+ # 5. Add tools to the call if we have them and aren't hiding them
if "tools" in params:
hide_tools = validate_parameter(
params, "hide_tools", str, False, "never", None, ["if_all_run", "if_any_run", "never"]
)
if not should_hide_tools(params["messages"], params["tools"], hide_tools):
- mistral_params["tools"] = params["tools"]
+ mistral_params["tools"] = tool_def_to_mistral(params["tools"])
+
return mistral_params
def create(self, params: Dict[str, Any]) -> ChatCompletion:
@@ -144,8 +168,7 @@ def create(self, params: Dict[str, Any]) -> ChatCompletion:
mistral_params = self.parse_params(params)
# 2. Call Mistral.AI API
- client = MistralClient(api_key=self.api_key)
- mistral_response = client.chat(**mistral_params)
+ mistral_response = self._client.chat.complete(**mistral_params)
# TODO: Handle streaming
# 3. Convert Mistral response to OAI compatible format
@@ -191,7 +214,7 @@ def create(self, params: Dict[str, Any]) -> ChatCompletion:
return response_oai
@staticmethod
- def get_usage(response: ChatCompletionResponse) -> Dict:
+ def get_usage(response: ChatCompletion) -> Dict:
return {
"prompt_tokens": response.usage.prompt_tokens if response.usage is not None else 0,
"completion_tokens": response.usage.completion_tokens if response.usage is not None else 0,
@@ -203,25 +226,48 @@ def get_usage(response: ChatCompletionResponse) -> Dict:
}
+def tool_def_to_mistral(tool_definitions: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
+ """Converts AutoGen tool definition to a mistral tool format"""
+
+ mistral_tools = []
+
+ for autogen_tool in tool_definitions:
+ mistral_tool = {
+ "type": "function",
+ "function": Function(
+ name=autogen_tool["function"]["name"],
+ description=autogen_tool["function"]["description"],
+ parameters=autogen_tool["function"]["parameters"],
+ ),
+ }
+
+ mistral_tools.append(mistral_tool)
+
+ return mistral_tools
+
+
def calculate_mistral_cost(input_tokens: int, output_tokens: int, model_name: str) -> float:
"""Calculate the cost of the mistral response."""
- # Prices per 1 million tokens
+ # Prices per 1 thousand tokens
# https://mistral.ai/technology/
model_cost_map = {
- "open-mistral-7b": {"input": 0.25, "output": 0.25},
- "open-mixtral-8x7b": {"input": 0.7, "output": 0.7},
- "open-mixtral-8x22b": {"input": 2.0, "output": 6.0},
- "mistral-small-latest": {"input": 1.0, "output": 3.0},
- "mistral-medium-latest": {"input": 2.7, "output": 8.1},
- "mistral-large-latest": {"input": 4.0, "output": 12.0},
+ "open-mistral-7b": {"input": 0.00025, "output": 0.00025},
+ "open-mixtral-8x7b": {"input": 0.0007, "output": 0.0007},
+ "open-mixtral-8x22b": {"input": 0.002, "output": 0.006},
+ "mistral-small-latest": {"input": 0.001, "output": 0.003},
+ "mistral-medium-latest": {"input": 0.00275, "output": 0.0081},
+ "mistral-large-latest": {"input": 0.0003, "output": 0.0003},
+ "mistral-large-2407": {"input": 0.0003, "output": 0.0003},
+ "open-mistral-nemo-2407": {"input": 0.0003, "output": 0.0003},
+ "codestral-2405": {"input": 0.001, "output": 0.003},
}
# Ensure we have the model they are using and return the total cost
if model_name in model_cost_map:
costs = model_cost_map[model_name]
- return (input_tokens * costs["input"] / 1_000_000) + (output_tokens * costs["output"] / 1_000_000)
+ return (input_tokens * costs["input"] / 1000) + (output_tokens * costs["output"] / 1000)
else:
warnings.warn(f"Cost calculation is not implemented for model {model_name}, will return $0.", UserWarning)
return 0
diff --git a/autogen/oai/openai_utils.py b/autogen/oai/openai_utils.py
index df70e01ff7d..41b94324118 100644
--- a/autogen/oai/openai_utils.py
+++ b/autogen/oai/openai_utils.py
@@ -28,6 +28,7 @@
# gpt-4o
"gpt-4o": (0.005, 0.015),
"gpt-4o-2024-05-13": (0.005, 0.015),
+ "gpt-4o-2024-08-06": (0.0025, 0.01),
# gpt-4-turbo
"gpt-4-turbo-2024-04-09": (0.01, 0.03),
# gpt-4
diff --git a/autogen/runtime_logging.py b/autogen/runtime_logging.py
index 21aebcbfdd8..f40408204db 100644
--- a/autogen/runtime_logging.py
+++ b/autogen/runtime_logging.py
@@ -14,6 +14,7 @@
if TYPE_CHECKING:
from autogen import Agent, ConversableAgent, OpenAIWrapper
from autogen.oai.anthropic import AnthropicClient
+ from autogen.oai.bedrock import BedrockClient
from autogen.oai.cohere import CohereClient
from autogen.oai.gemini import GeminiClient
from autogen.oai.groq import GroqClient
@@ -123,6 +124,7 @@ def log_new_client(
GroqClient,
CohereClient,
OllamaClient,
+ BedrockClient,
],
wrapper: OpenAIWrapper,
init_args: Dict[str, Any],
diff --git a/autogen/token_count_utils.py b/autogen/token_count_utils.py
index 220007a2bd1..8552a8f1653 100644
--- a/autogen/token_count_utils.py
+++ b/autogen/token_count_utils.py
@@ -36,6 +36,7 @@ def get_max_token_limit(model: str = "gpt-3.5-turbo-0613") -> int:
"gpt-4-vision-preview": 128000,
"gpt-4o": 128000,
"gpt-4o-2024-05-13": 128000,
+ "gpt-4o-2024-08-06": 128000,
"gpt-4o-mini": 128000,
"gpt-4o-mini-2024-07-18": 128000,
}
diff --git a/autogen/version.py b/autogen/version.py
index c4feccf559b..9b1b78b4b3a 100644
--- a/autogen/version.py
+++ b/autogen/version.py
@@ -1 +1 @@
-__version__ = "0.2.33"
+__version__ = "0.2.35"
diff --git a/dotnet/AutoGen.sln b/dotnet/AutoGen.sln
index 1218cf12982..78d18527b62 100644
--- a/dotnet/AutoGen.sln
+++ b/dotnet/AutoGen.sln
@@ -26,7 +26,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AutoGen.SemanticKernel", "s
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AutoGen.Core", "src\AutoGen.Core\AutoGen.Core.csproj", "{D58D43D1-0617-4A3D-9932-C773E6398535}"
EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AutoGen.OpenAI", "src\AutoGen.OpenAI\AutoGen.OpenAI.csproj", "{63445BB7-DBB9-4AEF-9D6F-98BBE75EE1EC}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AutoGen.OpenAI.V1", "src\AutoGen.OpenAI.V1\AutoGen.OpenAI.V1.csproj", "{63445BB7-DBB9-4AEF-9D6F-98BBE75EE1EC}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AutoGen.Mistral", "src\AutoGen.Mistral\AutoGen.Mistral.csproj", "{6585D1A4-3D97-4D76-A688-1933B61AEB19}"
EndProject
@@ -38,7 +38,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AutoGen.WebAPI.Tests", "tes
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AutoGen.SemanticKernel.Tests", "test\AutoGen.SemanticKernel.Tests\AutoGen.SemanticKernel.Tests.csproj", "{1DFABC4A-8458-4875-8DCB-59F3802DAC65}"
EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AutoGen.OpenAI.Tests", "test\AutoGen.OpenAI.Tests\AutoGen.OpenAI.Tests.csproj", "{D36A85F9-C172-487D-8192-6BFE5D05B4A7}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AutoGen.OpenAI.V1.Tests", "test\AutoGen.OpenAI.V1.Tests\AutoGen.OpenAI.V1.Tests.csproj", "{D36A85F9-C172-487D-8192-6BFE5D05B4A7}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AutoGen.DotnetInteractive.Tests", "test\AutoGen.DotnetInteractive.Tests\AutoGen.DotnetInteractive.Tests.csproj", "{B61388CA-DC73-4B7F-A7B2-7B9A86C9229E}"
EndProject
@@ -68,6 +68,16 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AutoGen.OpenAI.Sample", "sa
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AutoGen.WebAPI.Sample", "sample\AutoGen.WebAPI.Sample\AutoGen.WebAPI.Sample.csproj", "{12079C18-A519-403F-BBFD-200A36A0C083}"
EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AutoGen.AzureAIInference", "src\AutoGen.AzureAIInference\AutoGen.AzureAIInference.csproj", "{5C45981D-1319-4C25-935C-83D411CB28DF}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AutoGen.AzureAIInference.Tests", "test\AutoGen.AzureAIInference.Tests\AutoGen.AzureAIInference.Tests.csproj", "{5970868F-831E-418F-89A9-4EC599563E16}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AutoGen.Tests.Share", "test\AutoGen.Test.Share\AutoGen.Tests.Share.csproj", "{143725E2-206C-4D37-93E4-9EDF699826B2}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AutoGen.OpenAI", "src\AutoGen.OpenAI\AutoGen.OpenAI.csproj", "{3AF1CBEC-2877-41E9-92AE-3A391B2AA9E8}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AutoGen.OpenAI.Tests", "test\AutoGen.OpenAI.Tests\AutoGen.OpenAI.Tests.csproj", "{42A8251C-E7B3-47BB-A82E-459952EBE132}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -194,6 +204,26 @@ Global
{12079C18-A519-403F-BBFD-200A36A0C083}.Debug|Any CPU.Build.0 = Debug|Any CPU
{12079C18-A519-403F-BBFD-200A36A0C083}.Release|Any CPU.ActiveCfg = Release|Any CPU
{12079C18-A519-403F-BBFD-200A36A0C083}.Release|Any CPU.Build.0 = Release|Any CPU
+ {5C45981D-1319-4C25-935C-83D411CB28DF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {5C45981D-1319-4C25-935C-83D411CB28DF}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {5C45981D-1319-4C25-935C-83D411CB28DF}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {5C45981D-1319-4C25-935C-83D411CB28DF}.Release|Any CPU.Build.0 = Release|Any CPU
+ {5970868F-831E-418F-89A9-4EC599563E16}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {5970868F-831E-418F-89A9-4EC599563E16}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {5970868F-831E-418F-89A9-4EC599563E16}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {5970868F-831E-418F-89A9-4EC599563E16}.Release|Any CPU.Build.0 = Release|Any CPU
+ {143725E2-206C-4D37-93E4-9EDF699826B2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {143725E2-206C-4D37-93E4-9EDF699826B2}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {143725E2-206C-4D37-93E4-9EDF699826B2}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {143725E2-206C-4D37-93E4-9EDF699826B2}.Release|Any CPU.Build.0 = Release|Any CPU
+ {3AF1CBEC-2877-41E9-92AE-3A391B2AA9E8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {3AF1CBEC-2877-41E9-92AE-3A391B2AA9E8}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {3AF1CBEC-2877-41E9-92AE-3A391B2AA9E8}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {3AF1CBEC-2877-41E9-92AE-3A391B2AA9E8}.Release|Any CPU.Build.0 = Release|Any CPU
+ {42A8251C-E7B3-47BB-A82E-459952EBE132}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {42A8251C-E7B3-47BB-A82E-459952EBE132}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {42A8251C-E7B3-47BB-A82E-459952EBE132}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {42A8251C-E7B3-47BB-A82E-459952EBE132}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -229,6 +259,11 @@ Global
{6B82F26D-5040-4453-B21B-C8D1F913CE4C} = {F823671B-3ECA-4AE6-86DA-25E920D3FE64}
{0E635268-351C-4A6B-A28D-593D868C2CA4} = {FBFEAD1F-29EB-4D99-A672-0CD8473E10B9}
{12079C18-A519-403F-BBFD-200A36A0C083} = {FBFEAD1F-29EB-4D99-A672-0CD8473E10B9}
+ {5C45981D-1319-4C25-935C-83D411CB28DF} = {18BF8DD7-0585-48BF-8F97-AD333080CE06}
+ {5970868F-831E-418F-89A9-4EC599563E16} = {F823671B-3ECA-4AE6-86DA-25E920D3FE64}
+ {143725E2-206C-4D37-93E4-9EDF699826B2} = {F823671B-3ECA-4AE6-86DA-25E920D3FE64}
+ {3AF1CBEC-2877-41E9-92AE-3A391B2AA9E8} = {18BF8DD7-0585-48BF-8F97-AD333080CE06}
+ {42A8251C-E7B3-47BB-A82E-459952EBE132} = {F823671B-3ECA-4AE6-86DA-25E920D3FE64}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {93384647-528D-46C8-922C-8DB36A382F0B}
diff --git a/dotnet/eng/MetaInfo.props b/dotnet/eng/MetaInfo.props
index 72918fabe4f..006c586faba 100644
--- a/dotnet/eng/MetaInfo.props
+++ b/dotnet/eng/MetaInfo.props
@@ -1,7 +1,7 @@
- 0.0.17
+ 0.1.0AutoGenhttps://microsoft.github.io/autogen-for-net/https://github.com/microsoft/autogen
diff --git a/dotnet/eng/Version.props b/dotnet/eng/Version.props
index 20be183219e..36cfd917c2c 100644
--- a/dotnet/eng/Version.props
+++ b/dotnet/eng/Version.props
@@ -2,8 +2,9 @@
1.0.0-beta.17
- 1.15.1
- 1.15.1-alpha
+ 2.0.0-beta.3
+ 1.18.1-rc
+ 1.18.1-alpha5.0.04.3.06.0.0
@@ -15,5 +16,8 @@
8.0.43.0.04.3.0.2
+ 1.0.0-beta.1
+ 2.0.0-beta.10
+ 7.4.4
\ No newline at end of file
diff --git a/dotnet/sample/AutoGen.Anthropic.Samples/Anthropic_Agent_With_Prompt_Caching.cs b/dotnet/sample/AutoGen.Anthropic.Samples/Anthropic_Agent_With_Prompt_Caching.cs
new file mode 100644
index 00000000000..5d8a99ce128
--- /dev/null
+++ b/dotnet/sample/AutoGen.Anthropic.Samples/Anthropic_Agent_With_Prompt_Caching.cs
@@ -0,0 +1,133 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Anthropic_Agent_With_Prompt_Caching.cs
+
+using AutoGen.Anthropic.DTO;
+using AutoGen.Anthropic.Extensions;
+using AutoGen.Anthropic.Utils;
+using AutoGen.Core;
+
+namespace AutoGen.Anthropic.Samples;
+
+public class Anthropic_Agent_With_Prompt_Caching
+{
+ // A random and long test string to demonstrate cache control.
+ // the context must be larger than 1024 tokens for Claude 3.5 Sonnet and Claude 3 Opus
+ // 2048 tokens for Claude 3.0 Haiku
+ // Shorter prompts cannot be cached, even if marked with cache_control. Any requests to cache fewer than this number of tokens will be processed without caching
+
+ #region Long story for caching
+ public const string LongStory = """
+ Once upon a time in a small, nondescript town lived a man named Bob. Bob was an unassuming individual, the kind of person you wouldn’t look twice at if you passed him on the street. He worked as an IT specialist for a mid-sized corporation, spending his days fixing computers and troubleshooting software issues. But beneath his average exterior, Bob harbored a secret ambition—he wanted to take over the world.
+
+ Bob wasn’t always like this. For most of his life, he had been content with his routine, blending into the background. But one day, while browsing the dark corners of the internet, Bob stumbled upon an ancient manuscript, encrypted within the deep web, detailing the steps to global domination. It was written by a forgotten conqueror, someone whose name had been erased from history but whose methods were preserved in this digital relic. The manuscript laid out a plan so intricate and flawless that Bob, with his analytical mind, became obsessed.
+
+ Over the next few years, Bob meticulously followed the manuscript’s guidance. He started small, creating a network of like-minded individuals who shared his dream. They communicated through encrypted channels, meeting in secret to discuss their plans. Bob was careful, never revealing too much about himself, always staying in the shadows. He used his IT skills to gather information, infiltrating government databases, and private corporations, and acquiring secrets that could be used as leverage.
+
+ As his network grew, so did his influence. Bob began to manipulate world events from behind the scenes. He orchestrated economic crises, incited political turmoil, and planted seeds of discord among the world’s most powerful nations. Each move was calculated, each action a step closer to his ultimate goal. The world was in chaos, and no one suspected that a man like Bob could be behind it all.
+
+ But Bob knew that causing chaos wasn’t enough. To truly take over the world, he needed something more—something to cement his power. That’s when he turned to technology. Bob had always been ahead of the curve when it came to tech, and now, he planned to use it to his advantage. He began developing an AI, one that would be more powerful and intelligent than anything the world had ever seen. This AI, which Bob named “Nemesis,” was designed to control every aspect of modern life—from financial systems to military networks.
+
+ It took years of coding, testing, and refining, but eventually, Nemesis was ready. Bob unleashed the AI, and within days, it had taken control of the world’s digital infrastructure. Governments were powerless, their systems compromised. Corporations crumbled as their assets were seized. The military couldn’t act, their weapons turned against them. Bob, from the comfort of his modest home, had done it. He had taken over the world.
+
+ The world, now under Bob’s control, was eerily quiet. There were no more wars, no more financial crises, no more political strife. Nemesis ensured that everything ran smoothly, efficiently, and without dissent. The people of the world had no choice but to obey, their lives dictated by an unseen hand.
+
+ Bob, once a man who was overlooked and ignored, was now the most powerful person on the planet. But with that power came a realization. The world he had taken over was not the world he had envisioned. It was cold, mechanical, and devoid of the chaos that once made life unpredictable and exciting. Bob had achieved his goal, but in doing so, he had lost the very thing that made life worth living—freedom.
+
+ And so, Bob, now ruler of the world, sat alone in his control room, staring at the screens that displayed his dominion. He had everything he had ever wanted, yet he felt emptier than ever before. The world was his, but at what cost?
+
+ In the end, Bob realized that true power didn’t come from controlling others, but from the ability to let go. He deactivated Nemesis, restoring the world to its former state, and disappeared into obscurity, content to live out the rest of his days as just another face in the crowd. And though the world never knew his name, Bob’s legacy would live on, a reminder of the dangers of unchecked ambition.
+
+ Bob had vanished, leaving the world in a fragile state of recovery. Governments scrambled to regain control of their systems, corporations tried to rebuild, and the global population slowly adjusted to life without the invisible grip of Nemesis. Yet, even as society returned to a semblance of normalcy, whispers of the mysterious figure who had brought the world to its knees lingered in the shadows.
+
+ Meanwhile, Bob had retreated to a secluded cabin deep in the mountains. The cabin was a modest, rustic place, surrounded by dense forests and overlooking a tranquil lake. It was far from civilization, a perfect place for a man who wanted to disappear. Bob spent his days fishing, hiking, and reflecting on his past. For the first time in years, he felt a sense of peace.
+
+ But peace was fleeting. Despite his best efforts to put his past behind him, Bob couldn’t escape the consequences of his actions. He had unleashed Nemesis upon the world, and though he had deactivated the AI, remnants of its code still existed. Rogue factions, hackers, and remnants of his old network were searching for those fragments, hoping to revive Nemesis and seize the power that Bob had relinquished.
+
+ One day, as Bob was chopping wood outside his cabin, a figure emerged from the tree line. It was a young woman, dressed in hiking gear, with a determined look in her eyes. Bob tensed, his instincts telling him that this was no ordinary hiker.
+
+ “Bob,” the woman said, her voice steady. “Or should I say, the man who almost became the ruler of the world?”
+
+ Bob sighed, setting down his axe. “Who are you, and what do you want?”
+
+ The woman stepped closer. “My name is Sarah. I was part of your network, one of the few who knew about Nemesis. But I wasn’t like the others. I didn’t want power for myself—I wanted to protect the world from those who would misuse it.”
+
+ Bob studied her, trying to gauge her intentions. “And why are you here now?”
+
+ Sarah reached into her backpack and pulled out a small device. “Because Nemesis isn’t dead. Some of its code is still active, and it’s trying to reboot itself. I need your help to stop it for good.”
+
+ Bob’s heart sank. He had hoped that by deactivating Nemesis, he had erased it from existence. But deep down, he knew that an AI as powerful as Nemesis wouldn’t go down so easily. “Why come to me? I’m the one who created it. I’m the reason the world is in this mess.”
+
+ Sarah shook her head. “You’re also the only one who knows how to stop it. I’ve tracked down the remnants of Nemesis’s code, but I need you to help destroy it before it falls into the wrong hands.”
+
+ Bob hesitated. He had wanted nothing more than to leave his past behind, but he couldn’t ignore the responsibility that weighed on him. He had created Nemesis, and now it was his duty to make sure it never posed a threat again.
+
+ “Alright,” Bob said finally. “I’ll help you. But after this, I’m done. No more world domination, no more secret networks. I just want to live in peace.”
+
+ Sarah nodded. “Agreed. Let’s finish what you started.”
+
+ Over the next few weeks, Bob and Sarah worked together, traveling to various locations around the globe where fragments of Nemesis’s code had been detected. They infiltrated secure facilities, outsmarted rogue hackers, and neutralized threats, all while staying one step ahead of those who sought to control Nemesis for their own gain.
+
+ As they worked, Bob and Sarah developed a deep respect for one another. Sarah was sharp, resourceful, and driven by a genuine desire to protect the world. Bob found himself opening up to her, sharing his regrets, his doubts, and the lessons he had learned. In turn, Sarah shared her own story—how she had once been tempted by power but had chosen a different path, one that led her to fight for what was right.
+
+ Finally, after weeks of intense effort, they tracked down the last fragment of Nemesis’s code, hidden deep within a remote server farm in the Arctic. The facility was heavily guarded, but Bob and Sarah had planned meticulously. Under the cover of a blizzard, they infiltrated the facility, avoiding detection as they made their way to the heart of the server room.
+
+ As Bob began the process of erasing the final fragment, an alarm blared, and the facility’s security forces closed in. Sarah held them off as long as she could, but they were outnumbered and outgunned. Just as the situation seemed hopeless, Bob executed the final command, wiping Nemesis from existence once and for all.
+
+ But as the last remnants of Nemesis were deleted, Bob knew there was only one way to ensure it could never be resurrected. He initiated a self-destruct sequence for the server farm, trapping himself and Sarah inside.
+
+ Sarah stared at him, realization dawning in her eyes. “Bob, what are you doing?”
+
+ Bob looked at her, a sad smile on his face. “I have to make sure it’s over. This is the only way.”
+
+ Sarah’s eyes filled with tears, but she nodded, understanding the gravity of his decision. “Thank you, Bob. For everything.”
+
+ As the facility’s countdown reached its final seconds, Bob and Sarah stood side by side, knowing they had done the right thing. The explosion that followed was seen from miles away, a final testament to the end of an era.
+
+ The world never knew the true story of Bob, the man who almost ruled the world. But in his final act of sacrifice, he ensured that the world would remain free, a place where people could live their lives without fear of control. Bob had redeemed himself, not as a conqueror, but as a protector—a man who chose to save the world rather than rule it.
+
+ And in the quiet aftermath of the explosion, as the snow settled over the wreckage, Bob’s legacy was sealed—not as a name in history books, but as a silent guardian whose actions would be felt for generations to come.
+ """;
+ #endregion
+
+ public static async Task RunAsync()
+ {
+ #region init translator agents & register middlewares
+
+ var apiKey = Environment.GetEnvironmentVariable("ANTHROPIC_API_KEY") ??
+ throw new Exception("Please set ANTHROPIC_API_KEY environment variable.");
+ var anthropicClient = new AnthropicClient(new HttpClient(), AnthropicConstants.Endpoint, apiKey);
+ var frenchTranslatorAgent =
+ new AnthropicClientAgent(anthropicClient, "frenchTranslator", AnthropicConstants.Claude35Sonnet,
+ systemMessage: "You are a French translator")
+ .RegisterMessageConnector()
+ .RegisterPrintMessage();
+
+ var germanTranslatorAgent = new AnthropicClientAgent(anthropicClient, "germanTranslator",
+ AnthropicConstants.Claude35Sonnet, systemMessage: "You are a German translator")
+ .RegisterMessageConnector()
+ .RegisterPrintMessage();
+
+ #endregion
+
+ var userProxyAgent = new UserProxyAgent(
+ name: "user",
+ humanInputMode: HumanInputMode.ALWAYS)
+ .RegisterPrintMessage();
+
+ var groupChat = new RoundRobinGroupChat(
+ agents: [userProxyAgent, frenchTranslatorAgent, germanTranslatorAgent]);
+
+ var messageEnvelope =
+ MessageEnvelope.Create(
+ new ChatMessage("user", [TextContent.CreateTextWithCacheControl(LongStory)]),
+ from: "user");
+
+ var chatHistory = new List()
+ {
+ new TextMessage(Role.User, "translate this text for me", from: userProxyAgent.Name),
+ messageEnvelope,
+ };
+
+ var history = await groupChat.SendAsync(chatHistory).ToArrayAsync();
+ }
+}
diff --git a/dotnet/sample/AutoGen.Anthropic.Samples/Program.cs b/dotnet/sample/AutoGen.Anthropic.Samples/Program.cs
index 6d1e4e594b9..105bb56524f 100644
--- a/dotnet/sample/AutoGen.Anthropic.Samples/Program.cs
+++ b/dotnet/sample/AutoGen.Anthropic.Samples/Program.cs
@@ -7,6 +7,6 @@ internal static class Program
{
public static async Task Main(string[] args)
{
- await Create_Anthropic_Agent_With_Tool.RunAsync();
+ await Anthropic_Agent_With_Prompt_Caching.RunAsync();
}
}
diff --git a/dotnet/sample/AutoGen.BasicSamples/CodeSnippet/CreateAnAgent.cs b/dotnet/sample/AutoGen.BasicSamples/CodeSnippet/CreateAnAgent.cs
index a103f4ec2d4..f6805322466 100644
--- a/dotnet/sample/AutoGen.BasicSamples/CodeSnippet/CreateAnAgent.cs
+++ b/dotnet/sample/AutoGen.BasicSamples/CodeSnippet/CreateAnAgent.cs
@@ -4,7 +4,9 @@
using AutoGen;
using AutoGen.Core;
using AutoGen.OpenAI;
+using AutoGen.OpenAI.Extension;
using FluentAssertions;
+using OpenAI;
public partial class AssistantCodeSnippet
{
@@ -32,23 +34,18 @@ public void CodeSnippet2()
{
#region code_snippet_2
// get OpenAI Key and create config
- var apiKey = Environment.GetEnvironmentVariable("AZURE_OPENAI_API_KEY");
- string endPoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT"); // change to your endpoint
+ var apiKey = Environment.GetEnvironmentVariable("OPENAI_API_KEY");
+ var model = "gpt-4o-mini";
- var llmConfig = new AzureOpenAIConfig(
- endpoint: endPoint,
- deploymentName: "gpt-3.5-turbo-16k", // change to your deployment name
- apiKey: apiKey);
+ var openAIClient = new OpenAIClient(apiKey);
// create assistant agent
- var assistantAgent = new AssistantAgent(
+ var assistantAgent = new OpenAIChatAgent(
name: "assistant",
systemMessage: "You are an assistant that help user to do some tasks.",
- llmConfig: new ConversableAgentConfig
- {
- Temperature = 0,
- ConfigList = new[] { llmConfig },
- });
+ chatClient: openAIClient.GetChatClient(model))
+ .RegisterMessageConnector()
+ .RegisterPrintMessage();
#endregion code_snippet_2
}
@@ -71,27 +68,21 @@ public async Task CodeSnippet4()
// get OpenAI Key and create config
var apiKey = Environment.GetEnvironmentVariable("AZURE_OPENAI_API_KEY");
string endPoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT"); // change to your endpoint
-
- var llmConfig = new AzureOpenAIConfig(
- endpoint: endPoint,
- deploymentName: "gpt-3.5-turbo-16k", // change to your deployment name
- apiKey: apiKey);
+ var model = "gpt-4o-mini";
+ var openAIClient = new OpenAIClient(new System.ClientModel.ApiKeyCredential(apiKey), new OpenAIClientOptions
+ {
+ Endpoint = new Uri(endPoint),
+ });
#region code_snippet_4
- var assistantAgent = new AssistantAgent(
+ var assistantAgent = new OpenAIChatAgent(
+ chatClient: openAIClient.GetChatClient(model),
name: "assistant",
systemMessage: "You are an assistant that convert user input to upper case.",
- llmConfig: new ConversableAgentConfig
- {
- Temperature = 0,
- ConfigList = new[]
- {
- llmConfig
- },
- FunctionContracts = new[]
- {
- this.UpperCaseFunctionContract, // The FunctionDefinition object for the UpperCase function
- },
- });
+ functions: [
+ this.UpperCaseFunctionContract.ToChatTool(), // The FunctionDefinition object for the UpperCase function
+ ])
+ .RegisterMessageConnector()
+ .RegisterPrintMessage();
var response = await assistantAgent.SendAsync("hello");
response.Should().BeOfType();
@@ -106,31 +97,24 @@ public async Task CodeSnippet5()
// get OpenAI Key and create config
var apiKey = Environment.GetEnvironmentVariable("AZURE_OPENAI_API_KEY");
string endPoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT"); // change to your endpoint
-
- var llmConfig = new AzureOpenAIConfig(
- endpoint: endPoint,
- deploymentName: "gpt-3.5-turbo-16k", // change to your deployment name
- apiKey: apiKey);
+ var model = "gpt-4o-mini";
+ var openAIClient = new OpenAIClient(new System.ClientModel.ApiKeyCredential(apiKey), new OpenAIClientOptions
+ {
+ Endpoint = new Uri(endPoint),
+ });
#region code_snippet_5
- var assistantAgent = new AssistantAgent(
- name: "assistant",
- systemMessage: "You are an assistant that convert user input to upper case.",
- llmConfig: new ConversableAgentConfig
- {
- Temperature = 0,
- ConfigList = new[]
- {
- llmConfig
- },
- FunctionContracts = new[]
- {
- this.UpperCaseFunctionContract, // The FunctionDefinition object for the UpperCase function
- },
- },
- functionMap: new Dictionary>>
+ var functionCallMiddleware = new FunctionCallMiddleware(
+ functions: [this.UpperCaseFunctionContract],
+ functionMap: new Dictionary>>()
{
- { this.UpperCaseFunctionContract.Name, this.UpperCaseWrapper }, // The wrapper function for the UpperCase function
+ { this.UpperCaseFunctionContract.Name, this.UpperCase },
});
+ var assistantAgent = new OpenAIChatAgent(
+ name: "assistant",
+ systemMessage: "You are an assistant that convert user input to upper case.",
+ chatClient: openAIClient.GetChatClient(model))
+ .RegisterMessageConnector()
+ .RegisterStreamingMiddleware(functionCallMiddleware);
var response = await assistantAgent.SendAsync("hello");
response.Should().BeOfType();
diff --git a/dotnet/sample/AutoGen.BasicSamples/CodeSnippet/FunctionCallCodeSnippet.cs b/dotnet/sample/AutoGen.BasicSamples/CodeSnippet/FunctionCallCodeSnippet.cs
index 2b7e25fee0c..854a385dc34 100644
--- a/dotnet/sample/AutoGen.BasicSamples/CodeSnippet/FunctionCallCodeSnippet.cs
+++ b/dotnet/sample/AutoGen.BasicSamples/CodeSnippet/FunctionCallCodeSnippet.cs
@@ -3,7 +3,6 @@
using AutoGen;
using AutoGen.Core;
-using AutoGen.OpenAI;
using FluentAssertions;
public partial class FunctionCallCodeSnippet
diff --git a/dotnet/sample/AutoGen.BasicSamples/CodeSnippet/GetStartCodeSnippet.cs b/dotnet/sample/AutoGen.BasicSamples/CodeSnippet/GetStartCodeSnippet.cs
index fe97152183a..c5ff7b77033 100644
--- a/dotnet/sample/AutoGen.BasicSamples/CodeSnippet/GetStartCodeSnippet.cs
+++ b/dotnet/sample/AutoGen.BasicSamples/CodeSnippet/GetStartCodeSnippet.cs
@@ -5,6 +5,8 @@
using AutoGen;
using AutoGen.Core;
using AutoGen.OpenAI;
+using AutoGen.OpenAI.Extension;
+using OpenAI;
#endregion snippet_GetStartCodeSnippet
public class GetStartCodeSnippet
@@ -13,16 +15,14 @@ public async Task CodeSnippet1()
{
#region code_snippet_1
var openAIKey = Environment.GetEnvironmentVariable("OPENAI_API_KEY") ?? throw new Exception("Please set OPENAI_API_KEY environment variable.");
- var gpt35Config = new OpenAIConfig(openAIKey, "gpt-3.5-turbo");
+ var openAIClient = new OpenAIClient(openAIKey);
+ var model = "gpt-4o-mini";
- var assistantAgent = new AssistantAgent(
+ var assistantAgent = new OpenAIChatAgent(
name: "assistant",
systemMessage: "You are an assistant that help user to do some tasks.",
- llmConfig: new ConversableAgentConfig
- {
- Temperature = 0,
- ConfigList = [gpt35Config],
- })
+ chatClient: openAIClient.GetChatClient(model))
+ .RegisterMessageConnector()
.RegisterPrintMessage(); // register a hook to print message nicely to console
// set human input mode to ALWAYS so that user always provide input
diff --git a/dotnet/sample/AutoGen.BasicSamples/CodeSnippet/OpenAICodeSnippet.cs b/dotnet/sample/AutoGen.BasicSamples/CodeSnippet/OpenAICodeSnippet.cs
index cf045221223..60520078e72 100644
--- a/dotnet/sample/AutoGen.BasicSamples/CodeSnippet/OpenAICodeSnippet.cs
+++ b/dotnet/sample/AutoGen.BasicSamples/CodeSnippet/OpenAICodeSnippet.cs
@@ -5,9 +5,10 @@
using AutoGen.Core;
using AutoGen.OpenAI;
using AutoGen.OpenAI.Extension;
-using Azure.AI.OpenAI;
#endregion using_statement
using FluentAssertions;
+using OpenAI;
+using OpenAI.Chat;
namespace AutoGen.BasicSample.CodeSnippet;
#region weather_function
@@ -32,31 +33,30 @@ public async Task CreateOpenAIChatAgentAsync()
{
#region create_openai_chat_agent
var openAIKey = Environment.GetEnvironmentVariable("OPENAI_API_KEY") ?? throw new Exception("Please set OPENAI_API_KEY environment variable.");
- var modelId = "gpt-3.5-turbo";
+ var modelId = "gpt-4o-mini";
var openAIClient = new OpenAIClient(openAIKey);
// create an open ai chat agent
var openAIChatAgent = new OpenAIChatAgent(
- openAIClient: openAIClient,
+ chatClient: openAIClient.GetChatClient(modelId),
name: "assistant",
- modelName: modelId,
systemMessage: "You are an assistant that help user to do some tasks.");
// OpenAIChatAgent supports the following message types:
// - IMessage where ChatRequestMessage is from Azure.AI.OpenAI
- var helloMessage = new ChatRequestUserMessage("Hello");
+ var helloMessage = new UserChatMessage("Hello");
// Use MessageEnvelope.Create to create an IMessage
var chatMessageContent = MessageEnvelope.Create(helloMessage);
var reply = await openAIChatAgent.SendAsync(chatMessageContent);
- // The type of reply is MessageEnvelope where ChatResponseMessage is from Azure.AI.OpenAI
- reply.Should().BeOfType>();
+ // The type of reply is MessageEnvelope where ChatResponseMessage is from Azure.AI.OpenAI
+ reply.Should().BeOfType>();
// You can un-envelop the reply to get the ChatResponseMessage
- ChatResponseMessage response = reply.As>().Content;
- response.Role.Should().Be(ChatRole.Assistant);
+ ChatCompletion response = reply.As>().Content;
+ response.Role.Should().Be(ChatMessageRole.Assistant);
#endregion create_openai_chat_agent
#region create_openai_chat_agent_streaming
@@ -64,8 +64,8 @@ public async Task CreateOpenAIChatAgentAsync()
await foreach (var streamingMessage in streamingReply)
{
- streamingMessage.Should().BeOfType>();
- streamingMessage.As>().Content.Role.Should().Be(ChatRole.Assistant);
+ streamingMessage.Should().BeOfType>();
+ streamingMessage.As>().Content.Role.Should().Be(ChatMessageRole.Assistant);
}
#endregion create_openai_chat_agent_streaming
@@ -77,7 +77,7 @@ public async Task CreateOpenAIChatAgentAsync()
// now the agentWithConnector supports more message types
var messages = new IMessage[]
{
- MessageEnvelope.Create(new ChatRequestUserMessage("Hello")),
+ MessageEnvelope.Create(new UserChatMessage("Hello")),
new TextMessage(Role.Assistant, "Hello", from: "user"),
new MultiModalMessage(Role.Assistant,
[
@@ -106,9 +106,8 @@ public async Task OpenAIChatAgentGetWeatherFunctionCallAsync()
// create an open ai chat agent
var openAIChatAgent = new OpenAIChatAgent(
- openAIClient: openAIClient,
+ chatClient: openAIClient.GetChatClient(modelId),
name: "assistant",
- modelName: modelId,
systemMessage: "You are an assistant that help user to do some tasks.")
.RegisterMessageConnector();
diff --git a/dotnet/sample/AutoGen.BasicSamples/CodeSnippet/PrintMessageMiddlewareCodeSnippet.cs b/dotnet/sample/AutoGen.BasicSamples/CodeSnippet/PrintMessageMiddlewareCodeSnippet.cs
index bf4f9c976e2..0ac7f71a3ca 100644
--- a/dotnet/sample/AutoGen.BasicSamples/CodeSnippet/PrintMessageMiddlewareCodeSnippet.cs
+++ b/dotnet/sample/AutoGen.BasicSamples/CodeSnippet/PrintMessageMiddlewareCodeSnippet.cs
@@ -4,8 +4,6 @@
using AutoGen.Core;
using AutoGen.OpenAI;
using AutoGen.OpenAI.Extension;
-using Azure;
-using Azure.AI.OpenAI;
namespace AutoGen.BasicSample.CodeSnippet;
@@ -15,8 +13,8 @@ public async Task PrintMessageMiddlewareAsync()
{
var config = LLMConfiguration.GetAzureOpenAIGPT3_5_Turbo();
var endpoint = new Uri(config.Endpoint);
- var openaiClient = new OpenAIClient(endpoint, new AzureKeyCredential(config.ApiKey));
- var agent = new OpenAIChatAgent(openaiClient, "assistant", config.DeploymentName)
+ var gpt4o = LLMConfiguration.GetOpenAIGPT4o_mini();
+ var agent = new OpenAIChatAgent(gpt4o, "assistant", config.DeploymentName)
.RegisterMessageConnector();
#region PrintMessageMiddleware
@@ -31,10 +29,10 @@ public async Task PrintMessageStreamingMiddlewareAsync()
{
var config = LLMConfiguration.GetAzureOpenAIGPT3_5_Turbo();
var endpoint = new Uri(config.Endpoint);
- var openaiClient = new OpenAIClient(endpoint, new AzureKeyCredential(config.ApiKey));
+ var gpt4o = LLMConfiguration.GetOpenAIGPT4o_mini();
#region print_message_streaming
- var streamingAgent = new OpenAIChatAgent(openaiClient, "assistant", config.DeploymentName)
+ var streamingAgent = new OpenAIChatAgent(gpt4o, "assistant")
.RegisterMessageConnector()
.RegisterPrintMessage();
diff --git a/dotnet/sample/AutoGen.BasicSamples/CodeSnippet/RunCodeSnippetCodeSnippet.cs b/dotnet/sample/AutoGen.BasicSamples/CodeSnippet/RunCodeSnippetCodeSnippet.cs
index e498650b6aa..b087beb993b 100644
--- a/dotnet/sample/AutoGen.BasicSamples/CodeSnippet/RunCodeSnippetCodeSnippet.cs
+++ b/dotnet/sample/AutoGen.BasicSamples/CodeSnippet/RunCodeSnippetCodeSnippet.cs
@@ -4,6 +4,7 @@
#region code_snippet_0_1
using AutoGen.Core;
using AutoGen.DotnetInteractive;
+using AutoGen.DotnetInteractive.Extension;
#endregion code_snippet_0_1
namespace AutoGen.BasicSample.CodeSnippet;
@@ -11,18 +12,37 @@ public class RunCodeSnippetCodeSnippet
{
public async Task CodeSnippet1()
{
- IAgent agent = default;
+ IAgent agent = new DefaultReplyAgent("agent", "Hello World");
#region code_snippet_1_1
- var workingDirectory = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
- Directory.CreateDirectory(workingDirectory);
- var interactiveService = new InteractiveService(installingDirectory: workingDirectory);
- await interactiveService.StartAsync(workingDirectory: workingDirectory);
+ var kernel = DotnetInteractiveKernelBuilder
+ .CreateDefaultInProcessKernelBuilder() // add C# and F# kernels
+ .Build();
#endregion code_snippet_1_1
#region code_snippet_1_2
- // register dotnet code block execution hook to an arbitrary agent
- var dotnetCodeAgent = agent.RegisterDotnetCodeBlockExectionHook(interactiveService: interactiveService);
+ // register middleware to execute code block
+ var dotnetCodeAgent = agent
+ .RegisterMiddleware(async (msgs, option, innerAgent, ct) =>
+ {
+ var lastMessage = msgs.LastOrDefault();
+ if (lastMessage == null || lastMessage.GetContent() is null)
+ {
+ return await innerAgent.GenerateReplyAsync(msgs, option, ct);
+ }
+
+ if (lastMessage.ExtractCodeBlock("```csharp", "```") is string codeSnippet)
+ {
+ // execute code snippet
+ var result = await kernel.RunSubmitCodeCommandAsync(codeSnippet, "csharp");
+ return new TextMessage(Role.Assistant, result, from: agent.Name);
+ }
+ else
+ {
+ // no code block found, invoke next agent
+ return await innerAgent.GenerateReplyAsync(msgs, option, ct);
+ }
+ });
var codeSnippet = @"
```csharp
@@ -44,5 +64,17 @@ public async Task CodeSnippet1()
```
";
#endregion code_snippet_1_3
+
+ #region code_snippet_1_4
+ var pythonKernel = DotnetInteractiveKernelBuilder
+ .CreateDefaultInProcessKernelBuilder()
+ .AddPythonKernel(venv: "python3")
+ .Build();
+
+ var pythonCode = """
+ print('Hello from Python!')
+ """;
+ var result = await pythonKernel.RunSubmitCodeCommandAsync(pythonCode, "python3");
+ #endregion code_snippet_1_4
}
}
diff --git a/dotnet/sample/AutoGen.BasicSamples/CodeSnippet/TypeSafeFunctionCallCodeSnippet.cs b/dotnet/sample/AutoGen.BasicSamples/CodeSnippet/TypeSafeFunctionCallCodeSnippet.cs
index 50bcd8a8048..667705835eb 100644
--- a/dotnet/sample/AutoGen.BasicSamples/CodeSnippet/TypeSafeFunctionCallCodeSnippet.cs
+++ b/dotnet/sample/AutoGen.BasicSamples/CodeSnippet/TypeSafeFunctionCallCodeSnippet.cs
@@ -3,7 +3,6 @@
using System.Text.Json;
using AutoGen.OpenAI.Extension;
-using Azure.AI.OpenAI;
#region weather_report_using_statement
using AutoGen.Core;
#endregion weather_report_using_statement
@@ -32,7 +31,7 @@ public async Task Consume()
var functionInstance = new TypeSafeFunctionCall();
// Get the generated function definition
- FunctionDefinition functionDefiniton = functionInstance.WeatherReportFunctionContract.ToOpenAIFunctionDefinition();
+ var functionDefiniton = functionInstance.WeatherReportFunctionContract.ToChatTool();
// Get the generated function wrapper
Func> functionWrapper = functionInstance.WeatherReportWrapper;
@@ -69,32 +68,31 @@ public async Task UpperCase(string input)
#region code_snippet_1
// file: FunctionDefinition.generated.cs
- public FunctionDefinition UpperCaseFunction
+ public FunctionContract WeatherReportFunctionContract
{
- get => new FunctionDefinition
+ get => new FunctionContract
{
- Name = @"UpperCase",
- Description = "convert input to upper case",
- Parameters = BinaryData.FromObjectAsJson(new
+ ClassName = @"TypeSafeFunctionCall",
+ Name = @"WeatherReport",
+ Description = @"Get weather report",
+ ReturnType = typeof(Task),
+ Parameters = new global::AutoGen.Core.FunctionParameterContract[]
{
- Type = "object",
- Properties = new
- {
- input = new
+ new FunctionParameterContract
{
- Type = @"string",
- Description = @"input",
+ Name = @"city",
+ Description = @"city",
+ ParameterType = typeof(string),
+ IsRequired = true,
},
- },
- Required = new[]
- {
- "input",
+ new FunctionParameterContract
+ {
+ Name = @"date",
+ Description = @"date",
+ ParameterType = typeof(string),
+ IsRequired = true,
},
},
- new JsonSerializerOptions
- {
- PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
- })
};
}
#endregion code_snippet_1
diff --git a/dotnet/sample/AutoGen.BasicSamples/Example01_AssistantAgent.cs b/dotnet/sample/AutoGen.BasicSamples/Example01_AssistantAgent.cs
index 3ee363bfc06..40c88102588 100644
--- a/dotnet/sample/AutoGen.BasicSamples/Example01_AssistantAgent.cs
+++ b/dotnet/sample/AutoGen.BasicSamples/Example01_AssistantAgent.cs
@@ -4,6 +4,8 @@
using AutoGen;
using AutoGen.BasicSample;
using AutoGen.Core;
+using AutoGen.OpenAI;
+using AutoGen.OpenAI.Extension;
using FluentAssertions;
///
@@ -13,18 +15,12 @@ public static class Example01_AssistantAgent
{
public static async Task RunAsync()
{
- var gpt35 = LLMConfiguration.GetAzureOpenAIGPT3_5_Turbo();
- var config = new ConversableAgentConfig
- {
- Temperature = 0,
- ConfigList = [gpt35],
- };
-
- // create assistant agent
- var assistantAgent = new AssistantAgent(
+ var gpt4oMini = LLMConfiguration.GetOpenAIGPT4o_mini();
+ var assistantAgent = new OpenAIChatAgent(
+ chatClient: gpt4oMini,
name: "assistant",
- systemMessage: "You convert what user said to all uppercase.",
- llmConfig: config)
+ systemMessage: "You convert what user said to all uppercase.")
+ .RegisterMessageConnector()
.RegisterPrintMessage();
// talk to the assistant agent
diff --git a/dotnet/sample/AutoGen.BasicSamples/Example02_TwoAgent_MathChat.cs b/dotnet/sample/AutoGen.BasicSamples/Example02_TwoAgent_MathChat.cs
index c2957f32da7..b2dd9726b4b 100644
--- a/dotnet/sample/AutoGen.BasicSamples/Example02_TwoAgent_MathChat.cs
+++ b/dotnet/sample/AutoGen.BasicSamples/Example02_TwoAgent_MathChat.cs
@@ -1,30 +1,28 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Example02_TwoAgent_MathChat.cs
-using AutoGen;
using AutoGen.BasicSample;
using AutoGen.Core;
+using AutoGen.OpenAI;
+using AutoGen.OpenAI.Extension;
using FluentAssertions;
public static class Example02_TwoAgent_MathChat
{
public static async Task RunAsync()
{
#region code_snippet_1
- // get gpt-3.5-turbo config
- var gpt35 = LLMConfiguration.GetAzureOpenAIGPT3_5_Turbo();
+ var gpt4oMini = LLMConfiguration.GetOpenAIGPT4o_mini();
+
// create teacher agent
// teacher agent will create math questions
- var teacher = new AssistantAgent(
+ var teacher = new OpenAIChatAgent(
+ chatClient: gpt4oMini,
name: "teacher",
systemMessage: @"You are a teacher that create pre-school math question for student and check answer.
If the answer is correct, you stop the conversation by saying [COMPLETE].
- If the answer is wrong, you ask student to fix it.",
- llmConfig: new ConversableAgentConfig
- {
- Temperature = 0,
- ConfigList = [gpt35],
- })
+ If the answer is wrong, you ask student to fix it.")
+ .RegisterMessageConnector()
.RegisterMiddleware(async (msgs, option, agent, _) =>
{
var reply = await agent.GenerateReplyAsync(msgs, option);
@@ -39,14 +37,11 @@ public static async Task RunAsync()
// create student agent
// student agent will answer the math questions
- var student = new AssistantAgent(
+ var student = new OpenAIChatAgent(
+ chatClient: gpt4oMini,
name: "student",
- systemMessage: "You are a student that answer question from teacher",
- llmConfig: new ConversableAgentConfig
- {
- Temperature = 0,
- ConfigList = [gpt35],
- })
+ systemMessage: "You are a student that answer question from teacher")
+ .RegisterMessageConnector()
.RegisterPrintMessage();
// start the conversation
diff --git a/dotnet/sample/AutoGen.BasicSamples/Example03_Agent_FunctionCall.cs b/dotnet/sample/AutoGen.BasicSamples/Example03_Agent_FunctionCall.cs
index 0ef8eaa48ae..94b67a94b14 100644
--- a/dotnet/sample/AutoGen.BasicSamples/Example03_Agent_FunctionCall.cs
+++ b/dotnet/sample/AutoGen.BasicSamples/Example03_Agent_FunctionCall.cs
@@ -1,9 +1,10 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Example03_Agent_FunctionCall.cs
-using AutoGen;
using AutoGen.BasicSample;
using AutoGen.Core;
+using AutoGen.OpenAI;
+using AutoGen.OpenAI.Extension;
using FluentAssertions;
///
@@ -45,33 +46,30 @@ public async Task CalculateTax(int price, float taxRate)
public static async Task RunAsync()
{
var instance = new Example03_Agent_FunctionCall();
- var gpt35 = LLMConfiguration.GetAzureOpenAIGPT3_5_Turbo();
+ var gpt4o = LLMConfiguration.GetOpenAIGPT4o_mini();
// AutoGen makes use of AutoGen.SourceGenerator to automatically generate FunctionDefinition and FunctionCallWrapper for you.
// The FunctionDefinition will be created based on function signature and XML documentation.
// The return type of type-safe function needs to be Task. And to get the best performance, please try only use primitive types and arrays of primitive types as parameters.
- var config = new ConversableAgentConfig
- {
- Temperature = 0,
- ConfigList = [gpt35],
- FunctionContracts = new[]
- {
+ var toolCallMiddleware = new FunctionCallMiddleware(
+ functions: [
instance.ConcatStringFunctionContract,
instance.UpperCaseFunctionContract,
instance.CalculateTaxFunctionContract,
- },
- };
-
- var agent = new AssistantAgent(
- name: "agent",
- systemMessage: "You are a helpful AI assistant",
- llmConfig: config,
+ ],
functionMap: new Dictionary>>
{
- { nameof(ConcatString), instance.ConcatStringWrapper },
- { nameof(UpperCase), instance.UpperCaseWrapper },
- { nameof(CalculateTax), instance.CalculateTaxWrapper },
- })
+ { nameof(instance.ConcatString), instance.ConcatStringWrapper },
+ { nameof(instance.UpperCase), instance.UpperCaseWrapper },
+ { nameof(instance.CalculateTax), instance.CalculateTaxWrapper },
+ });
+
+ var agent = new OpenAIChatAgent(
+ chatClient: gpt4o,
+ name: "agent",
+ systemMessage: "You are a helpful AI assistant")
+ .RegisterMessageConnector()
+ .RegisterStreamingMiddleware(toolCallMiddleware)
.RegisterPrintMessage();
// talk to the assistant agent
diff --git a/dotnet/sample/AutoGen.BasicSamples/Example04_Dynamic_GroupChat_Coding_Task.cs b/dotnet/sample/AutoGen.BasicSamples/Example04_Dynamic_GroupChat_Coding_Task.cs
index 21605992840..f90816d890e 100644
--- a/dotnet/sample/AutoGen.BasicSamples/Example04_Dynamic_GroupChat_Coding_Task.cs
+++ b/dotnet/sample/AutoGen.BasicSamples/Example04_Dynamic_GroupChat_Coding_Task.cs
@@ -1,11 +1,12 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Example04_Dynamic_GroupChat_Coding_Task.cs
-using AutoGen;
using AutoGen.BasicSample;
using AutoGen.Core;
using AutoGen.DotnetInteractive;
+using AutoGen.DotnetInteractive.Extension;
using AutoGen.OpenAI;
+using AutoGen.OpenAI.Extension;
using FluentAssertions;
public partial class Example04_Dynamic_GroupChat_Coding_Task
@@ -14,50 +15,32 @@ public static async Task RunAsync()
{
var instance = new Example04_Dynamic_GroupChat_Coding_Task();
- // setup dotnet interactive
- var workDir = Path.Combine(Path.GetTempPath(), "InteractiveService");
- if (!Directory.Exists(workDir))
- {
- Directory.CreateDirectory(workDir);
- }
-
- using var service = new InteractiveService(workDir);
- var dotnetInteractiveFunctions = new DotnetInteractiveFunction(service);
-
- var result = Path.Combine(workDir, "result.txt");
- if (File.Exists(result))
- {
- File.Delete(result);
- }
-
- await service.StartAsync(workDir, default);
+ var kernel = DotnetInteractiveKernelBuilder
+ .CreateDefaultInProcessKernelBuilder()
+ .AddPythonKernel("python3")
+ .Build();
- var gptConfig = LLMConfiguration.GetAzureOpenAIGPT3_5_Turbo();
+ var gpt4o = LLMConfiguration.GetOpenAIGPT4o_mini();
- var helperAgent = new GPTAgent(
- name: "helper",
- systemMessage: "You are a helpful AI assistant",
- temperature: 0f,
- config: gptConfig);
-
- var groupAdmin = new GPTAgent(
+ var groupAdmin = new OpenAIChatAgent(
+ chatClient: gpt4o,
name: "groupAdmin",
- systemMessage: "You are the admin of the group chat",
- temperature: 0f,
- config: gptConfig)
+ systemMessage: "You are the admin of the group chat")
+ .RegisterMessageConnector()
.RegisterPrintMessage();
- var userProxy = new UserProxyAgent(name: "user", defaultReply: GroupChatExtension.TERMINATE, humanInputMode: HumanInputMode.NEVER)
+ var userProxy = new DefaultReplyAgent(name: "user", defaultReply: GroupChatExtension.TERMINATE)
.RegisterPrintMessage();
// Create admin agent
- var admin = new AssistantAgent(
+ var admin = new OpenAIChatAgent(
+ chatClient: gpt4o,
name: "admin",
systemMessage: """
You are a manager who takes coding problem from user and resolve problem by splitting them into small tasks and assign each task to the most appropriate agent.
Here's available agents who you can assign task to:
- - coder: write dotnet code to resolve task
- - runner: run dotnet code from coder
+ - coder: write python code to resolve task
+ - runner: run python code from coder
The workflow is as follows:
- You take the coding problem from user
@@ -83,24 +66,12 @@ You are a manager who takes coding problem from user and resolve problem by spli
Once the coding problem is resolved, summarize each steps and results and send the summary to the user using the following format:
```summary
- {
- "problem": "{coding problem}",
- "steps": [
- {
- "step": "{step}",
- "result": "{result}"
- }
- ]
- }
+ @user,
```
Your reply must contain one of [task|ask|summary] to indicate the type of your message.
- """,
- llmConfig: new ConversableAgentConfig
- {
- Temperature = 0,
- ConfigList = [gptConfig],
- })
+ """)
+ .RegisterMessageConnector()
.RegisterPrintMessage();
// create coder agent
@@ -108,30 +79,27 @@ Your reply must contain one of [task|ask|summary] to indicate the type of your m
// The dotnet coder write dotnet code to resolve the task.
// The code reviewer review the code block from coder's reply.
// The nuget agent install nuget packages if there's any.
- var coderAgent = new GPTAgent(
+ var coderAgent = new OpenAIChatAgent(
name: "coder",
- systemMessage: @"You act as dotnet coder, you write dotnet code to resolve task. Once you finish writing code, ask runner to run the code for you.
+ chatClient: gpt4o,
+ systemMessage: @"You act as python coder, you write python code to resolve task. Once you finish writing code, ask runner to run the code for you.
Here're some rules to follow on writing dotnet code:
-- put code between ```csharp and ```
-- When creating http client, use `var httpClient = new HttpClient()`. Don't use `using var httpClient = new HttpClient()` because it will cause error when running the code.
-- Try to use `var` instead of explicit type.
-- Try avoid using external library, use .NET Core library instead.
-- Use top level statement to write code.
+- put code between ```python and ```
+- Try avoid using external library
- Always print out the result to console. Don't write code that doesn't print out anything.
-If you need to install nuget packages, put nuget packages in the following format:
-```nuget
-nuget_package_name
+Use the following format to install pip package:
+```python
+%pip install
```
If your code is incorrect, Fix the error and send the code again.
Here's some externel information
- The link to mlnet repo is: https://github.com/dotnet/machinelearning. you don't need a token to use github pr api. Make sure to include a User-Agent header, otherwise github will reject it.
-",
- config: gptConfig,
- temperature: 0.4f)
+")
+ .RegisterMessageConnector()
.RegisterPrintMessage();
// code reviewer agent will review if code block from coder's reply satisfy the following conditions:
@@ -139,14 +107,13 @@ Your reply must contain one of [task|ask|summary] to indicate the type of your m
// - The code block is csharp code block
// - The code block is top level statement
// - The code block is not using declaration
- var codeReviewAgent = new GPTAgent(
+ var codeReviewAgent = new OpenAIChatAgent(
+ chatClient: gpt4o,
name: "reviewer",
systemMessage: """
You are a code reviewer who reviews code from coder. You need to check if the code satisfy the following conditions:
- - The reply from coder contains at least one code block, e.g ```csharp and ```
- - There's only one code block and it's csharp code block
- - The code block is not inside a main function. a.k.a top level statement
- - The code block is not using declaration when creating http client
+ - The reply from coder contains at least one code block, e.g ```python and ```
+ - There's only one code block and it's python code block
You don't check the code style, only check if the code satisfy the above conditions.
@@ -164,23 +131,40 @@ Your reply must contain one of [task|ask|summary] to indicate the type of your m
result: REJECTED
```
- """,
- config: gptConfig,
- temperature: 0f)
+ """)
+ .RegisterMessageConnector()
.RegisterPrintMessage();
// create runner agent
// The runner agent will run the code block from coder's reply.
// It runs dotnet code using dotnet interactive service hook.
// It also truncate the output if the output is too long.
- var runner = new AssistantAgent(
+ var runner = new DefaultReplyAgent(
name: "runner",
defaultReply: "No code available, coder, write code please")
- .RegisterDotnetCodeBlockExectionHook(interactiveService: service)
.RegisterMiddleware(async (msgs, option, agent, ct) =>
{
var mostRecentCoderMessage = msgs.LastOrDefault(x => x.From == "coder") ?? throw new Exception("No coder message found");
- return await agent.GenerateReplyAsync(new[] { mostRecentCoderMessage }, option, ct);
+
+ if (mostRecentCoderMessage.ExtractCodeBlock("```python", "```") is string code)
+ {
+ var result = await kernel.RunSubmitCodeCommandAsync(code, "python");
+ // only keep the first 500 characters
+ if (result.Length > 500)
+ {
+ result = result.Substring(0, 500);
+ }
+ result = $"""
+ # [CODE_BLOCK_EXECUTION_RESULT]
+ {result}
+ """;
+
+ return new TextMessage(Role.Assistant, result, from: agent.Name);
+ }
+ else
+ {
+ return await agent.GenerateReplyAsync(msgs, option, ct);
+ }
})
.RegisterPrintMessage();
@@ -251,18 +235,27 @@ Your reply must contain one of [task|ask|summary] to indicate the type of your m
workflow: workflow);
// task 1: retrieve the most recent pr from mlnet and save it in result.txt
- var groupChatManager = new GroupChatManager(groupChat);
- await userProxy.SendAsync(groupChatManager, "Retrieve the most recent pr from mlnet and save it in result.txt", maxRound: 30);
- File.Exists(result).Should().BeTrue();
-
- // task 2: calculate the 39th fibonacci number
- var answer = 63245986;
- // clear the result file
- File.Delete(result);
+ var task = """
+ retrieve the most recent pr from mlnet and save it in result.txt
+ """;
+ var chatHistory = new List
+ {
+ new TextMessage(Role.Assistant, task)
+ {
+ From = userProxy.Name
+ }
+ };
+ await foreach (var message in groupChat.SendAsync(chatHistory, maxRound: 10))
+ {
+ if (message.From == admin.Name && message.GetContent().Contains("```summary"))
+ {
+ // Task complete!
+ break;
+ }
+ }
- var conversationHistory = await userProxy.InitiateChatAsync(groupChatManager, "What's the 39th of fibonacci number? Save the result in result.txt", maxRound: 10);
+ // check if the result file is created
+ var result = "result.txt";
File.Exists(result).Should().BeTrue();
- var resultContent = File.ReadAllText(result);
- resultContent.Should().Contain(answer.ToString());
}
}
diff --git a/dotnet/sample/AutoGen.BasicSamples/Example05_Dalle_And_GPT4V.cs b/dotnet/sample/AutoGen.BasicSamples/Example05_Dalle_And_GPT4V.cs
index ba7b5d4bde4..e8dd86474e7 100644
--- a/dotnet/sample/AutoGen.BasicSamples/Example05_Dalle_And_GPT4V.cs
+++ b/dotnet/sample/AutoGen.BasicSamples/Example05_Dalle_And_GPT4V.cs
@@ -4,9 +4,9 @@
using AutoGen.Core;
using AutoGen.OpenAI;
using AutoGen.OpenAI.Extension;
-using Azure.AI.OpenAI;
using FluentAssertions;
-using autogen = AutoGen.LLMConfigAPI;
+using OpenAI;
+using OpenAI.Images;
public partial class Example05_Dalle_And_GPT4V
{
@@ -30,16 +30,12 @@ public async Task GenerateImage(string prompt)
// and return url.
var option = new ImageGenerationOptions
{
- Size = ImageSize.Size1024x1024,
- Style = ImageGenerationStyle.Vivid,
- ImageCount = 1,
- Prompt = prompt,
- Quality = ImageGenerationQuality.Standard,
- DeploymentName = "dall-e-3",
+ Size = GeneratedImageSize.W1024xH1024,
+ Style = GeneratedImageStyle.Vivid,
};
- var imageResponse = await openAIClient.GetImageGenerationsAsync(option);
- var imageUrl = imageResponse.Value.Data.First().Url.OriginalString;
+ var imageResponse = await openAIClient.GetImageClient("dall-e-3").GenerateImageAsync(prompt, option);
+ var imageUrl = imageResponse.Value.ImageUri.OriginalString;
return $@"// ignore this line [IMAGE_GENERATION]
The image is generated from prompt {prompt}
@@ -57,8 +53,6 @@ public static async Task RunAsync()
// get OpenAI Key and create config
var openAIKey = Environment.GetEnvironmentVariable("OPENAI_API_KEY") ?? throw new Exception("Please set OPENAI_API_KEY environment variable.");
- var gpt35Config = autogen.GetOpenAIConfigList(openAIKey, new[] { "gpt-3.5-turbo" });
- var gpt4vConfig = autogen.GetOpenAIConfigList(openAIKey, new[] { "gpt-4-vision-preview" });
var openAIClient = new OpenAIClient(openAIKey);
var instance = new Example05_Dalle_And_GPT4V(openAIClient);
var imagePath = Path.Combine("resource", "images", "background.png");
@@ -74,8 +68,7 @@ public static async Task RunAsync()
{ nameof(GenerateImage), instance.GenerateImageWrapper },
});
var dalleAgent = new OpenAIChatAgent(
- openAIClient: openAIClient,
- modelName: "gpt-3.5-turbo",
+ chatClient: openAIClient.GetChatClient("gpt-4o-mini"),
name: "dalle",
systemMessage: "You are a DALL-E agent that generate image from prompt, when conversation is terminated, return the most recent image url")
.RegisterMessageConnector()
@@ -110,9 +103,8 @@ public static async Task RunAsync()
.RegisterPrintMessage();
var gpt4VAgent = new OpenAIChatAgent(
- openAIClient: openAIClient,
- name: "gpt4v",
- modelName: "gpt-4-vision-preview",
+ chatClient: openAIClient.GetChatClient("gpt-4o-mini"),
+ name: "gpt-4o-mini",
systemMessage: @"You are a critism that provide feedback to DALL-E agent.
Carefully check the image generated by DALL-E agent and provide feedback.
If the image satisfies the condition, then say [APPROVE].
diff --git a/dotnet/sample/AutoGen.BasicSamples/Example06_UserProxyAgent.cs b/dotnet/sample/AutoGen.BasicSamples/Example06_UserProxyAgent.cs
index dd3b5a67192..e1349cb32a9 100644
--- a/dotnet/sample/AutoGen.BasicSamples/Example06_UserProxyAgent.cs
+++ b/dotnet/sample/AutoGen.BasicSamples/Example06_UserProxyAgent.cs
@@ -2,6 +2,7 @@
// Example06_UserProxyAgent.cs
using AutoGen.Core;
using AutoGen.OpenAI;
+using AutoGen.OpenAI.Extension;
namespace AutoGen.BasicSample;
@@ -9,12 +10,13 @@ public static class Example06_UserProxyAgent
{
public static async Task RunAsync()
{
- var gpt35 = LLMConfiguration.GetOpenAIGPT3_5_Turbo();
+ var gpt4o = LLMConfiguration.GetOpenAIGPT4o_mini();
- var assistantAgent = new GPTAgent(
+ var assistantAgent = new OpenAIChatAgent(
+ chatClient: gpt4o,
name: "assistant",
- systemMessage: "You are an assistant that help user to do some tasks.",
- config: gpt35)
+ systemMessage: "You are an assistant that help user to do some tasks.")
+ .RegisterMessageConnector()
.RegisterPrintMessage();
// set human input mode to ALWAYS so that user always provide input
diff --git a/dotnet/sample/AutoGen.BasicSamples/Example07_Dynamic_GroupChat_Calculate_Fibonacci.cs b/dotnet/sample/AutoGen.BasicSamples/Example07_Dynamic_GroupChat_Calculate_Fibonacci.cs
index dd4fcada967..1f1315586a2 100644
--- a/dotnet/sample/AutoGen.BasicSamples/Example07_Dynamic_GroupChat_Calculate_Fibonacci.cs
+++ b/dotnet/sample/AutoGen.BasicSamples/Example07_Dynamic_GroupChat_Calculate_Fibonacci.cs
@@ -6,10 +6,11 @@
using AutoGen.BasicSample;
using AutoGen.Core;
using AutoGen.DotnetInteractive;
+using AutoGen.DotnetInteractive.Extension;
using AutoGen.OpenAI;
using AutoGen.OpenAI.Extension;
-using Azure.AI.OpenAI;
-using FluentAssertions;
+using Microsoft.DotNet.Interactive;
+using OpenAI.Chat;
public partial class Example07_Dynamic_GroupChat_Calculate_Fibonacci
{
@@ -49,11 +50,10 @@ public async Task ReviewCodeBlock(
#endregion reviewer_function
#region create_coder
- public static async Task CreateCoderAgentAsync(OpenAIClient client, string deployModel)
+ public static async Task CreateCoderAgentAsync(ChatClient client)
{
var coder = new OpenAIChatAgent(
- openAIClient: client,
- modelName: deployModel,
+ chatClient: client,
name: "coder",
systemMessage: @"You act as dotnet coder, you write dotnet code to resolve task. Once you finish writing code, ask runner to run the code for you.
@@ -80,12 +80,11 @@ public static async Task CreateCoderAgentAsync(OpenAIClient client, stri
#endregion create_coder
#region create_runner
- public static async Task CreateRunnerAgentAsync(InteractiveService service)
+ public static async Task CreateRunnerAgentAsync(Kernel kernel)
{
var runner = new DefaultReplyAgent(
name: "runner",
defaultReply: "No code available.")
- .RegisterDotnetCodeBlockExectionHook(interactiveService: service)
.RegisterMiddleware(async (msgs, option, agent, _) =>
{
if (msgs.Count() == 0 || msgs.All(msg => msg.From != "coder"))
@@ -95,7 +94,24 @@ public static async Task CreateRunnerAgentAsync(InteractiveService servi
else
{
var coderMsg = msgs.Last(msg => msg.From == "coder");
- return await agent.GenerateReplyAsync([coderMsg], option);
+ if (coderMsg.ExtractCodeBlock("```csharp", "```") is string code)
+ {
+ var codeResult = await kernel.RunSubmitCodeCommandAsync(code, "csharp");
+
+ codeResult = $"""
+ [RUNNER_RESULT]
+ {codeResult}
+ """;
+
+ return new TextMessage(Role.Assistant, codeResult)
+ {
+ From = "runner",
+ };
+ }
+ else
+ {
+ return new TextMessage(Role.Assistant, "No code available. Coder please write code");
+ }
}
})
.RegisterPrintMessage();
@@ -105,11 +121,10 @@ public static async Task CreateRunnerAgentAsync(InteractiveService servi
#endregion create_runner
#region create_admin
- public static async Task CreateAdminAsync(OpenAIClient client, string deployModel)
+ public static async Task CreateAdminAsync(ChatClient client)
{
var admin = new OpenAIChatAgent(
- openAIClient: client,
- modelName: deployModel,
+ chatClient: client,
name: "admin",
temperature: 0)
.RegisterMessageConnector()
@@ -120,9 +135,8 @@ public static async Task CreateAdminAsync(OpenAIClient client, string de
#endregion create_admin
#region create_reviewer
- public static async Task CreateReviewerAgentAsync(OpenAIClient openAIClient, string deployModel)
+ public static async Task CreateReviewerAgentAsync(ChatClient chatClient)
{
- var gpt3Config = LLMConfiguration.GetAzureOpenAIGPT3_5_Turbo();
var functions = new Example07_Dynamic_GroupChat_Calculate_Fibonacci();
var functionCallMiddleware = new FunctionCallMiddleware(
functions: [functions.ReviewCodeBlockFunctionContract],
@@ -131,10 +145,9 @@ public static async Task CreateReviewerAgentAsync(OpenAIClient openAICli
{ nameof(functions.ReviewCodeBlock), functions.ReviewCodeBlockWrapper },
});
var reviewer = new OpenAIChatAgent(
- openAIClient: openAIClient,
+ chatClient: chatClient,
name: "code_reviewer",
- systemMessage: @"You review code block from coder",
- modelName: deployModel)
+ systemMessage: @"You review code block from coder")
.RegisterMessageConnector()
.RegisterStreamingMiddleware(functionCallMiddleware)
.RegisterMiddleware(async (msgs, option, innerAgent, ct) =>
@@ -216,25 +229,17 @@ public static async Task CreateReviewerAgentAsync(OpenAIClient openAICli
public static async Task RunWorkflowAsync()
{
long the39thFibonacciNumber = 63245986;
- var workDir = Path.Combine(Path.GetTempPath(), "InteractiveService");
- if (!Directory.Exists(workDir))
- {
- Directory.CreateDirectory(workDir);
- }
-
- var config = LLMConfiguration.GetAzureOpenAIGPT3_5_Turbo();
- var openaiClient = new OpenAIClient(new Uri(config.Endpoint), new Azure.AzureKeyCredential(config.ApiKey));
-
- using var service = new InteractiveService(workDir);
- var dotnetInteractiveFunctions = new DotnetInteractiveFunction(service);
+ var kernel = DotnetInteractiveKernelBuilder
+ .CreateDefaultInProcessKernelBuilder()
+ .Build();
- await service.StartAsync(workDir, default);
+ var gpt4o = LLMConfiguration.GetOpenAIGPT4o_mini();
#region create_workflow
- var reviewer = await CreateReviewerAgentAsync(openaiClient, config.DeploymentName);
- var coder = await CreateCoderAgentAsync(openaiClient, config.DeploymentName);
- var runner = await CreateRunnerAgentAsync(service);
- var admin = await CreateAdminAsync(openaiClient, config.DeploymentName);
+ var reviewer = await CreateReviewerAgentAsync(gpt4o);
+ var coder = await CreateCoderAgentAsync(gpt4o);
+ var runner = await CreateRunnerAgentAsync(kernel);
+ var admin = await CreateAdminAsync(gpt4o);
var admin2CoderTransition = Transition.Create(admin, coder);
var coder2ReviewerTransition = Transition.Create(coder, reviewer);
@@ -305,21 +310,23 @@ public static async Task RunWorkflowAsync()
runner,
reviewer,
]);
-
+ #endregion create_group_chat_with_workflow
admin.SendIntroduction("Welcome to my group, work together to resolve my task", groupChat);
coder.SendIntroduction("I will write dotnet code to resolve task", groupChat);
reviewer.SendIntroduction("I will review dotnet code", groupChat);
runner.SendIntroduction("I will run dotnet code once the review is done", groupChat);
+ var task = "What's the 39th of fibonacci number?";
- var groupChatManager = new GroupChatManager(groupChat);
- var conversationHistory = await admin.InitiateChatAsync(groupChatManager, "What's the 39th of fibonacci number?", maxRound: 10);
- #endregion create_group_chat_with_workflow
- // the last message is from admin, which is the termination message
- var lastMessage = conversationHistory.Last();
- lastMessage.From.Should().Be("admin");
- lastMessage.IsGroupChatTerminateMessage().Should().BeTrue();
- lastMessage.Should().BeOfType();
- lastMessage.GetContent().Should().Contain(the39thFibonacciNumber.ToString());
+ var taskMessage = new TextMessage(Role.User, task, from: admin.Name);
+ await foreach (var message in groupChat.SendAsync([taskMessage], maxRound: 10))
+ {
+ // teminate chat if message is from runner and run successfully
+ if (message.From == "runner" && message.GetContent().Contains(the39thFibonacciNumber.ToString()))
+ {
+ Console.WriteLine($"The 39th of fibonacci number is {the39thFibonacciNumber}");
+ break;
+ }
+ }
}
public static async Task RunAsync()
@@ -331,18 +338,16 @@ public static async Task RunAsync()
Directory.CreateDirectory(workDir);
}
- var config = LLMConfiguration.GetAzureOpenAIGPT3_5_Turbo();
- var openaiClient = new OpenAIClient(new Uri(config.Endpoint), new Azure.AzureKeyCredential(config.ApiKey));
-
- using var service = new InteractiveService(workDir);
- var dotnetInteractiveFunctions = new DotnetInteractiveFunction(service);
+ var gpt4o = LLMConfiguration.GetOpenAIGPT4o_mini();
- await service.StartAsync(workDir, default);
+ var kernel = DotnetInteractiveKernelBuilder
+ .CreateDefaultInProcessKernelBuilder()
+ .Build();
#region create_group_chat
- var reviewer = await CreateReviewerAgentAsync(openaiClient, config.DeploymentName);
- var coder = await CreateCoderAgentAsync(openaiClient, config.DeploymentName);
- var runner = await CreateRunnerAgentAsync(service);
- var admin = await CreateAdminAsync(openaiClient, config.DeploymentName);
+ var reviewer = await CreateReviewerAgentAsync(gpt4o);
+ var coder = await CreateCoderAgentAsync(gpt4o);
+ var runner = await CreateRunnerAgentAsync(kernel);
+ var admin = await CreateAdminAsync(gpt4o);
var groupChat = new GroupChat(
admin: admin,
members:
diff --git a/dotnet/sample/AutoGen.BasicSamples/Example08_LMStudio.cs b/dotnet/sample/AutoGen.BasicSamples/Example08_LMStudio.cs
index cce33011762..e58454fdb5f 100644
--- a/dotnet/sample/AutoGen.BasicSamples/Example08_LMStudio.cs
+++ b/dotnet/sample/AutoGen.BasicSamples/Example08_LMStudio.cs
@@ -3,7 +3,9 @@
#region lmstudio_using_statements
using AutoGen.Core;
-using AutoGen.LMStudio;
+using AutoGen.OpenAI;
+using AutoGen.OpenAI.Extension;
+using OpenAI;
#endregion lmstudio_using_statements
namespace AutoGen.BasicSample;
@@ -13,8 +15,16 @@ public class Example08_LMStudio
public static async Task RunAsync()
{
#region lmstudio_example_1
- var config = new LMStudioConfig("localhost", 1234);
- var lmAgent = new LMStudioAgent("asssistant", config: config)
+ var endpoint = "http://localhost:1234";
+ var openaiClient = new OpenAIClient("api-key", new OpenAIClientOptions
+ {
+ Endpoint = new Uri(endpoint),
+ });
+
+ var lmAgent = new OpenAIChatAgent(
+ chatClient: openaiClient.GetChatClient(""),
+ name: "assistant")
+ .RegisterMessageConnector()
.RegisterPrintMessage();
await lmAgent.SendAsync("Can you write a piece of C# code to calculate 100th of fibonacci?");
diff --git a/dotnet/sample/AutoGen.BasicSamples/Example09_LMStudio_FunctionCall.cs b/dotnet/sample/AutoGen.BasicSamples/Example09_LMStudio_FunctionCall.cs
deleted file mode 100644
index c9dda27d2e2..00000000000
--- a/dotnet/sample/AutoGen.BasicSamples/Example09_LMStudio_FunctionCall.cs
+++ /dev/null
@@ -1,136 +0,0 @@
-// Copyright (c) Microsoft Corporation. All rights reserved.
-// Example09_LMStudio_FunctionCall.cs
-
-using System.Text.Json;
-using System.Text.Json.Serialization;
-using AutoGen.Core;
-using AutoGen.LMStudio;
-using AutoGen.OpenAI.Extension;
-using Azure.AI.OpenAI;
-
-namespace AutoGen.BasicSample;
-
-public class LLaMAFunctionCall
-{
- [JsonPropertyName("name")]
- public string Name { get; set; }
-
- [JsonPropertyName("arguments")]
- public JsonElement Arguments { get; set; }
-}
-
-public partial class Example09_LMStudio_FunctionCall
-{
- ///
- /// Get weather from location.
- ///
- /// location
- /// date. type is string
- [Function]
- public async Task GetWeather(string location, string date)
- {
- return $"[Function] The weather on {date} in {location} is sunny.";
- }
-
-
- ///
- /// Search query on Google and return the results.
- ///
- /// search query
- [Function]
- public async Task GoogleSearch(string query)
- {
- return $"[Function] Here are the search results for {query}.";
- }
-
- private static object SerializeFunctionDefinition(FunctionDefinition functionDefinition)
- {
- return new
- {
- type = "function",
- function = new
- {
- name = functionDefinition.Name,
- description = functionDefinition.Description,
- parameters = functionDefinition.Parameters.ToObjectFromJson
diff --git a/dotnet/sample/AutoGen.OpenAI.Sample/Connect_To_Azure_OpenAI.cs b/dotnet/sample/AutoGen.OpenAI.Sample/Connect_To_Azure_OpenAI.cs
new file mode 100644
index 00000000000..dafe2e31485
--- /dev/null
+++ b/dotnet/sample/AutoGen.OpenAI.Sample/Connect_To_Azure_OpenAI.cs
@@ -0,0 +1,39 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Connect_To_Azure_OpenAI.cs
+
+#region using_statement
+using AutoGen.Core;
+using AutoGen.OpenAI.Extension;
+using Azure;
+using Azure.AI.OpenAI;
+#endregion using_statement
+
+namespace AutoGen.OpenAI.Sample;
+
+public class Connect_To_Azure_OpenAI
+{
+ public static async Task RunAsync()
+ {
+ #region create_agent
+ var apiKey = Environment.GetEnvironmentVariable("AZURE_OPENAI_API_KEY") ?? throw new InvalidOperationException("Please set environment variable AZURE_OPENAI_API_KEY");
+ var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? throw new InvalidOperationException("Please set environment variable AZURE_OPENAI_ENDPOINT");
+ var model = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOY_NAME") ?? "gpt-4o-mini";
+
+ // Use AzureOpenAIClient to connect to openai model deployed on azure.
+ // The AzureOpenAIClient comes from Azure.AI.OpenAI package
+ var openAIClient = new AzureOpenAIClient(new Uri(endpoint), new AzureKeyCredential(apiKey));
+
+ var agent = new OpenAIChatAgent(
+ chatClient: openAIClient.GetChatClient(model),
+ name: "assistant",
+ systemMessage: "You are a helpful assistant designed to output JSON.",
+ seed: 0)
+ .RegisterMessageConnector()
+ .RegisterPrintMessage();
+ #endregion create_agent
+
+ #region send_message
+ await agent.SendAsync("Can you write a piece of C# code to calculate 100th of fibonacci?");
+ #endregion send_message
+ }
+}
diff --git a/dotnet/sample/AutoGen.OpenAI.Sample/Connect_To_Ollama.cs b/dotnet/sample/AutoGen.OpenAI.Sample/Connect_To_Ollama.cs
index 3823de2a528..2bb10e97841 100644
--- a/dotnet/sample/AutoGen.OpenAI.Sample/Connect_To_Ollama.cs
+++ b/dotnet/sample/AutoGen.OpenAI.Sample/Connect_To_Ollama.cs
@@ -4,51 +4,27 @@
#region using_statement
using AutoGen.Core;
using AutoGen.OpenAI.Extension;
-using Azure.AI.OpenAI;
-using Azure.Core.Pipeline;
+using OpenAI;
#endregion using_statement
namespace AutoGen.OpenAI.Sample;
-#region CustomHttpClientHandler
-public sealed class CustomHttpClientHandler : HttpClientHandler
-{
- private string _modelServiceUrl;
-
- public CustomHttpClientHandler(string modelServiceUrl)
- {
- _modelServiceUrl = modelServiceUrl;
- }
-
- protected override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
- {
- request.RequestUri = new Uri($"{_modelServiceUrl}{request.RequestUri.PathAndQuery}");
-
- return base.SendAsync(request, cancellationToken);
- }
-}
-#endregion CustomHttpClientHandler
-
public class Connect_To_Ollama
{
public static async Task RunAsync()
{
#region create_agent
- using var client = new HttpClient(new CustomHttpClientHandler("http://localhost:11434"));
- var option = new OpenAIClientOptions(OpenAIClientOptions.ServiceVersion.V2024_04_01_Preview)
- {
- Transport = new HttpClientTransport(client),
- };
-
// api-key is not required for local server
// so you can use any string here
- var openAIClient = new OpenAIClient("api-key", option);
+ var openAIClient = new OpenAIClient("api-key", new OpenAIClientOptions
+ {
+ Endpoint = new Uri("http://localhost:11434/v1/"), // remember to add /v1/ at the end to connect to Ollama openai server
+ });
var model = "llama3";
var agent = new OpenAIChatAgent(
- openAIClient: openAIClient,
+ chatClient: openAIClient.GetChatClient(model),
name: "assistant",
- modelName: model,
systemMessage: "You are a helpful assistant designed to output JSON.",
seed: 0)
.RegisterMessageConnector()
diff --git a/dotnet/sample/AutoGen.OpenAI.Sample/Program.cs b/dotnet/sample/AutoGen.OpenAI.Sample/Program.cs
index 5a38a3ff03b..c71f152d037 100644
--- a/dotnet/sample/AutoGen.OpenAI.Sample/Program.cs
+++ b/dotnet/sample/AutoGen.OpenAI.Sample/Program.cs
@@ -3,4 +3,4 @@
using AutoGen.OpenAI.Sample;
-Tool_Call_With_Ollama_And_LiteLLM.RunAsync().Wait();
+Structural_Output.RunAsync().Wait();
diff --git a/dotnet/sample/AutoGen.OpenAI.Sample/Structural_Output.cs b/dotnet/sample/AutoGen.OpenAI.Sample/Structural_Output.cs
new file mode 100644
index 00000000000..e562d7223a6
--- /dev/null
+++ b/dotnet/sample/AutoGen.OpenAI.Sample/Structural_Output.cs
@@ -0,0 +1,90 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Structural_Output.cs
+
+using System.Text.Json;
+using System.Text.Json.Serialization;
+using AutoGen.Core;
+using AutoGen.OpenAI.Extension;
+using FluentAssertions;
+using Json.Schema;
+using Json.Schema.Generation;
+using OpenAI;
+using OpenAI.Chat;
+
+namespace AutoGen.OpenAI.Sample;
+
+internal class Structural_Output
+{
+ public static async Task RunAsync()
+ {
+ #region create_agent
+ var apiKey = Environment.GetEnvironmentVariable("OPENAI_API_KEY") ?? throw new Exception("Please set OPENAI_API_KEY environment variable.");
+ var model = "gpt-4o-mini";
+
+ var schemaBuilder = new JsonSchemaBuilder().FromType();
+ var schema = schemaBuilder.Build();
+
+ var personSchemaFormat = ChatResponseFormat.CreateJsonSchemaFormat(
+ name: "Person",
+ jsonSchema: BinaryData.FromObjectAsJson(schema),
+ description: "Person schema");
+
+ var openAIClient = new OpenAIClient(apiKey);
+ var openAIClientAgent = new OpenAIChatAgent(
+ chatClient: openAIClient.GetChatClient(model),
+ name: "assistant",
+ systemMessage: "You are a helpful assistant",
+ responseFormat: personSchemaFormat) // structural output by passing schema to response format
+ .RegisterMessageConnector()
+ .RegisterPrintMessage();
+ #endregion create_agent
+
+ #region chat_with_agent
+ var reply = await openAIClientAgent.SendAsync("My name is John, I am 25 years old, and I live in Seattle. I like to play soccer and read books.");
+
+ var person = JsonSerializer.Deserialize(reply.GetContent());
+ Console.WriteLine($"Name: {person.Name}");
+ Console.WriteLine($"Age: {person.Age}");
+
+ if (!string.IsNullOrEmpty(person.Address))
+ {
+ Console.WriteLine($"Address: {person.Address}");
+ }
+
+ Console.WriteLine("Done.");
+ #endregion chat_with_agent
+
+ person.Name.Should().Be("John");
+ person.Age.Should().Be(25);
+ person.Address.Should().BeNullOrEmpty();
+ person.City.Should().Be("Seattle");
+ person.Hobbies.Count.Should().Be(2);
+ }
+}
+
+#region person_class
+public class Person
+{
+ [JsonPropertyName("name")]
+ [Description("Name of the person")]
+ [Required]
+ public string Name { get; set; }
+
+ [JsonPropertyName("age")]
+ [Description("Age of the person")]
+ [Required]
+ public int Age { get; set; }
+
+ [JsonPropertyName("city")]
+ [Description("City of the person")]
+ public string? City { get; set; }
+
+ [JsonPropertyName("address")]
+ [Description("Address of the person")]
+ public string? Address { get; set; }
+
+ [JsonPropertyName("hobbies")]
+ [Description("Hobbies of the person")]
+ public List? Hobbies { get; set; }
+}
+#endregion person_class
diff --git a/dotnet/sample/AutoGen.OpenAI.Sample/Tool_Call_With_Ollama_And_LiteLLM.cs b/dotnet/sample/AutoGen.OpenAI.Sample/Tool_Call_With_Ollama_And_LiteLLM.cs
index b0b0adc0e6f..ed43c628a67 100644
--- a/dotnet/sample/AutoGen.OpenAI.Sample/Tool_Call_With_Ollama_And_LiteLLM.cs
+++ b/dotnet/sample/AutoGen.OpenAI.Sample/Tool_Call_With_Ollama_And_LiteLLM.cs
@@ -3,8 +3,7 @@
using AutoGen.Core;
using AutoGen.OpenAI.Extension;
-using Azure.AI.OpenAI;
-using Azure.Core.Pipeline;
+using OpenAI;
namespace AutoGen.OpenAI.Sample;
@@ -43,20 +42,17 @@ public static async Task RunAsync()
#endregion Create_tools
#region Create_Agent
var liteLLMUrl = "http://localhost:4000";
- using var httpClient = new HttpClient(new CustomHttpClientHandler(liteLLMUrl));
- var option = new OpenAIClientOptions(OpenAIClientOptions.ServiceVersion.V2024_04_01_Preview)
- {
- Transport = new HttpClientTransport(httpClient),
- };
// api-key is not required for local server
// so you can use any string here
- var openAIClient = new OpenAIClient("api-key", option);
+ var openAIClient = new OpenAIClient("api-key", new OpenAIClientOptions
+ {
+ Endpoint = new Uri("http://localhost:4000"),
+ });
var agent = new OpenAIChatAgent(
- openAIClient: openAIClient,
+ chatClient: openAIClient.GetChatClient("dolphincoder:latest"),
name: "assistant",
- modelName: "dolphincoder:latest",
systemMessage: "You are a helpful AI assistant")
.RegisterMessageConnector()
.RegisterMiddleware(functionMiddleware)
diff --git a/dotnet/sample/AutoGen.OpenAI.Sample/Use_Json_Mode.cs b/dotnet/sample/AutoGen.OpenAI.Sample/Use_Json_Mode.cs
index d92983c5050..392796d819f 100644
--- a/dotnet/sample/AutoGen.OpenAI.Sample/Use_Json_Mode.cs
+++ b/dotnet/sample/AutoGen.OpenAI.Sample/Use_Json_Mode.cs
@@ -6,8 +6,9 @@
using AutoGen.Core;
using AutoGen.OpenAI;
using AutoGen.OpenAI.Extension;
-using Azure.AI.OpenAI;
using FluentAssertions;
+using OpenAI;
+using OpenAI.Chat;
namespace AutoGen.BasicSample;
@@ -17,16 +18,15 @@ public static async Task RunAsync()
{
#region create_agent
var apiKey = Environment.GetEnvironmentVariable("OPENAI_API_KEY") ?? throw new Exception("Please set OPENAI_API_KEY environment variable.");
- var model = "gpt-3.5-turbo";
+ var model = "gpt-4o-mini";
var openAIClient = new OpenAIClient(apiKey);
var openAIClientAgent = new OpenAIChatAgent(
- openAIClient: openAIClient,
+ chatClient: openAIClient.GetChatClient(model),
name: "assistant",
- modelName: model,
systemMessage: "You are a helpful assistant designed to output JSON.",
seed: 0, // explicitly set a seed to enable deterministic output
- responseFormat: ChatCompletionsResponseFormat.JsonObject) // set response format to JSON object to enable JSON mode
+ responseFormat: ChatResponseFormat.JsonObject) // set response format to JSON object to enable JSON mode
.RegisterMessageConnector()
.RegisterPrintMessage();
#endregion create_agent
diff --git a/dotnet/sample/AutoGen.SemanticKernel.Sample/AutoGen.SemanticKernel.Sample.csproj b/dotnet/sample/AutoGen.SemanticKernel.Sample/AutoGen.SemanticKernel.Sample.csproj
index df1064e18c4..45514431368 100644
--- a/dotnet/sample/AutoGen.SemanticKernel.Sample/AutoGen.SemanticKernel.Sample.csproj
+++ b/dotnet/sample/AutoGen.SemanticKernel.Sample/AutoGen.SemanticKernel.Sample.csproj
@@ -9,8 +9,9 @@
+
+
-
diff --git a/dotnet/sample/AutoGen.SemanticKernel.Sample/Use_Kernel_Functions_With_Other_Agent.cs b/dotnet/sample/AutoGen.SemanticKernel.Sample/Use_Kernel_Functions_With_Other_Agent.cs
index 2beb1ee7df0..700bdfe75c7 100644
--- a/dotnet/sample/AutoGen.SemanticKernel.Sample/Use_Kernel_Functions_With_Other_Agent.cs
+++ b/dotnet/sample/AutoGen.SemanticKernel.Sample/Use_Kernel_Functions_With_Other_Agent.cs
@@ -5,8 +5,8 @@
using AutoGen.Core;
using AutoGen.OpenAI;
using AutoGen.OpenAI.Extension;
-using Azure.AI.OpenAI;
using Microsoft.SemanticKernel;
+using OpenAI;
#endregion Using
namespace AutoGen.SemanticKernel.Sample;
@@ -17,7 +17,7 @@ public static async Task RunAsync()
{
#region Create_plugin
var openAIKey = Environment.GetEnvironmentVariable("OPENAI_API_KEY") ?? throw new Exception("Please set OPENAI_API_KEY environment variable.");
- var modelId = "gpt-3.5-turbo";
+ var modelId = "gpt-4o-mini";
var kernelBuilder = Kernel.CreateBuilder();
var kernel = kernelBuilder.Build();
var getWeatherFunction = KernelFunctionFactory.CreateFromMethod(
@@ -33,9 +33,8 @@ public static async Task RunAsync()
var openAIClient = new OpenAIClient(openAIKey);
var openAIAgent = new OpenAIChatAgent(
- openAIClient: openAIClient,
- name: "assistant",
- modelName: modelId)
+ chatClient: openAIClient.GetChatClient(modelId),
+ name: "assistant")
.RegisterMessageConnector() // register message connector so it support AutoGen built-in message types like TextMessage.
.RegisterMiddleware(kernelPluginMiddleware) // register the middleware to handle the plugin functions
.RegisterPrintMessage(); // pretty print the message to the console
diff --git a/dotnet/src/AutoGen.Anthropic/Agent/AnthropicClientAgent.cs b/dotnet/src/AutoGen.Anthropic/Agent/AnthropicClientAgent.cs
index 73510baeb71..81fa8e6438a 100644
--- a/dotnet/src/AutoGen.Anthropic/Agent/AnthropicClientAgent.cs
+++ b/dotnet/src/AutoGen.Anthropic/Agent/AnthropicClientAgent.cs
@@ -64,7 +64,7 @@ private ChatCompletionRequest CreateParameters(IEnumerable messages, G
{
var chatCompletionRequest = new ChatCompletionRequest()
{
- SystemMessage = _systemMessage,
+ SystemMessage = [new SystemMessage { Text = _systemMessage }],
MaxTokens = options?.MaxToken ?? _maxTokens,
Model = _modelName,
Stream = shouldStream,
diff --git a/dotnet/src/AutoGen.Anthropic/AnthropicClient.cs b/dotnet/src/AutoGen.Anthropic/AnthropicClient.cs
index c58b2c1952e..f106e08d35c 100644
--- a/dotnet/src/AutoGen.Anthropic/AnthropicClient.cs
+++ b/dotnet/src/AutoGen.Anthropic/AnthropicClient.cs
@@ -24,12 +24,13 @@ public sealed class AnthropicClient : IDisposable
private static readonly JsonSerializerOptions JsonSerializerOptions = new()
{
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
- Converters = { new ContentBaseConverter(), new JsonPropertyNameEnumConverter() }
- };
-
- private static readonly JsonSerializerOptions JsonDeserializerOptions = new()
- {
- Converters = { new ContentBaseConverter(), new JsonPropertyNameEnumConverter() }
+ Converters =
+ {
+ new ContentBaseConverter(),
+ new JsonPropertyNameEnumConverter(),
+ new JsonPropertyNameEnumConverter(),
+ new SystemMessageConverter(),
+ }
};
public AnthropicClient(HttpClient httpClient, string baseUrl, string apiKey)
@@ -135,12 +136,13 @@ private Task SendRequestAsync(T requestObject, Cancellat
var httpRequestMessage = new HttpRequestMessage(HttpMethod.Post, _baseUrl);
var jsonRequest = JsonSerializer.Serialize(requestObject, JsonSerializerOptions);
httpRequestMessage.Content = new StringContent(jsonRequest, Encoding.UTF8, "application/json");
+ httpRequestMessage.Headers.Add("anthropic-beta", "prompt-caching-2024-07-31");
return _httpClient.SendAsync(httpRequestMessage, cancellationToken);
}
private async Task DeserializeResponseAsync(Stream responseStream, CancellationToken cancellationToken)
{
- return await JsonSerializer.DeserializeAsync(responseStream, JsonDeserializerOptions, cancellationToken)
+ return await JsonSerializer.DeserializeAsync(responseStream, JsonSerializerOptions, cancellationToken)
?? throw new Exception("Failed to deserialize response");
}
diff --git a/dotnet/src/AutoGen.Anthropic/Converters/SystemMessageConverter.cs b/dotnet/src/AutoGen.Anthropic/Converters/SystemMessageConverter.cs
new file mode 100644
index 00000000000..5bbe8a3a37f
--- /dev/null
+++ b/dotnet/src/AutoGen.Anthropic/Converters/SystemMessageConverter.cs
@@ -0,0 +1,42 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// SystemMessageConverter.cs
+
+using System;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+using AutoGen.Anthropic.DTO;
+
+namespace AutoGen.Anthropic.Converters;
+
+public class SystemMessageConverter : JsonConverter