Então, você tem brincado com Large Language Models e está começando a integrar Generative AI em seus aplicativos? Isso é incrível! Mas sejamos realistas. LLMs nem sempre se comportam da maneira que queremos. Eles são como crianças más com mentes próprias!
Você logo percebe que cadeias de prompts simples não são o suficiente. Às vezes, precisamos de algo mais. Às vezes, precisamos de fluxos de trabalho multiagentes! É aí que o AutoGen entra.
Vamos dar um exemplo. Imagine que você está criando um aplicativo de anotações (claramente, o mundo não tem o suficiente deles. 😝). Mas ei, queremos fazer algo especial. Queremos pegar a nota simples e crua que um usuário nos dá e transformá-la em um documento totalmente reestruturado, completo com um resumo, um título opcional e uma lista de tarefas automatizada. E queremos fazer tudo isso sem suar a camisa - bem, pelo menos para seus agentes de IA.
Certo, agora, eu sei o que você está pensando - "Esses não são como programas para novatos?" Para isso, eu digo, você está certo. Maldoso... mas certo! Mas não se deixe enganar pela simplicidade do fluxo de trabalho. As habilidades que você aprenderá aqui - como lidar com agentes de IA, implementar controle de fluxo de trabalho e gerenciar histórico de conversas - ajudarão você a levar seu jogo de IA para o próximo nível.
Então apertem os cintos, porque vamos aprender a criar fluxos de trabalho de IA usando o AutoGen!
Antes de começar, observe que você pode encontrar um link para todo o código-fonte no GitHub .
Vamos começar com o primeiro caso de uso - “Gerando um resumo para a nota seguido por um título condicional”. Para ser justo, não precisamos realmente usar agentes aqui. Mas ei, temos que começar em algum lugar, certo?
Frameworks Agentic como o AutoGen sempre exigem que configuremos os parâmetros do modelo. Estamos falando sobre o modelo e o modelo de fallback a serem usados, a temperatura e até mesmo configurações como timeout e cache. No caso do AutoGen, essa configuração se parece com isso:
# build the gpt_configuration object base_llm_config = { "config_list": [ { "model": "Llama-3-8B-Instruct", "api_key": os.getenv("OPENAI_API_KEY"), "base_url": os.getenv("OPENAI_API_URL"), } ], "temperature": 0.0, "cache_seed": None, "timeout": 600, }
Como você pode ver, eu sou um grande fã de IA de código aberto e juro pelo Llama 3. Você pode fazer o AutoGen apontar para qualquer servidor de inferência compatível com OpenAI simplesmente configurando os valores api_key
e base_url
. Então, sinta-se à vontade para usar Groq, Together.ai ou até mesmo vLLM para hospedar seu modelo localmente. Estou usando Inferix .
É realmente muito fácil!
Estou curioso! Você estaria interessado em um guia semelhante para hospedagem de IA de código aberto? Deixe-me saber nos comentários.
Inicializar agentes de conversação no AutoGen é bem simples; basta fornecer a configuração base do LLM junto com uma mensagem do sistema e pronto.
import autogen def get_note_summarizer(base_llm_config: dict): # A system message to define the role and job of our agent system_message = """You are a helpful AI assistant. The user will provide you a note. Generate a summary describing what the note is about. The summary must follow the provided "RULES". "RULES": - The summary should be not more than 3 short sentences. - Don't use bullet points. - The summary should be short and concise. - Identify and retain any "catchy" or memorable phrases from the original text - Identify and correct all grammatical errors. - Output the summary and nothing else.""" # Create and return our assistant agent return autogen.AssistantAgent( name="Note_Summarizer", # Lets give our agent a nice name llm_config=base_llm_config, # This is where we pass the llm configuration system_message=system_message, ) def get_title_generator(base_llm_config: dict): # A system message to define the role and job of our agent system_message = """You are a helpful AI assistant. The user will provide you a note along with a summary. Generate a title based on the user's input. The title must be witty and easy to read. The title should accurate present what the note is about. The title must strictly be less than 10 words. Make sure you keep the title short. Make sure you print the title and nothing else. """ # Create and return our assistant agent return autogen.AssistantAgent( name="Title_Generator", llm_config=base_llm_config, system_message=system_message, )
A parte mais importante da criação de agentes é o system_message
. Tire um momento para olhar o system_message
que usei para configurar meus agentes.
É importante lembrar que a maneira como os agentes de IA no AutoGen funcionam é participando de uma conversa . A maneira como eles interpretam e levam a conversa adiante depende completamente do system_message
com o qual estão configurados. Este é um dos lugares onde você gastará algum tempo para fazer as coisas direito.
Precisamos de apenas mais um agente. Um agente para agir como um proxy para nós, humanos. Um agente que possa iniciar a conversa com a “nota” como seu prompt inicial.
def get_user(): # A system message to define the role and job of our agent system_message = "A human admin. Supplies the initial prompt and nothing else." # Create and return our user agent return autogen.UserProxyAgent( name="Admin", system_message=system_message, human_input_mode="NEVER", # We don't want interrupts for human-in-loop scenarios code_execution_config=False, # We definitely don't want AI executing code. default_auto_reply=None, )
Não há nada de especial acontecendo aqui. Apenas observe que eu configurei o parâmetro default_auto_reply
para None
. Isso é importante. Configurar isso para none garante que a conversa termine sempre que o agente do usuário receber uma mensagem.
Opa, esqueci completamente de criar esses agentes. Vamos fazer isso rapidinho.
# Create our agents user = get_user() note_summarizer = get_note_summarizer(base_llm_config) title_generator = get_title_generator(base_llm_config)
GroupChat
A peça final do quebra-cabeça é fazer nossos agentes se coordenarem. Precisamos determinar a sequência de sua participação e decidir quais agentes devem.
Ok, isso é mais de uma peça. Mas você entendeu! 🙈
Uma solução possível seria deixar a IA descobrir a sequência na qual os Agentes participam. Não é uma má ideia. Na verdade, essa é minha opção preferida ao lidar com problemas complexos em que a natureza do fluxo de trabalho é dinâmica.
No entanto, essa abordagem tem suas desvantagens. A realidade ataca novamente! O agente responsável por tomar essas decisões geralmente precisa de um modelo grande, resultando em latências e custos mais altos. Além disso, há o risco de que ele tome decisões incorretas.
Para fluxos de trabalho determinísticos, onde sabemos a sequência de passos com antecedência, eu gosto de pegar as rédeas e conduzir o navio eu mesmo. Felizmente, o AutoGen suporta esse caso de uso com um recurso útil chamado GroupChat
.
from autogen import GroupChatManager from autogen.agentchat.groupchat import GroupChat from autogen.agentchat.agent import Agent def get_group_chat(agents, generate_title: bool = False): # Define the function which decides the agent selection order def speaker_selection_method(last_speaker: Agent, group_chat: GroupChat): # The admin will always forward the note to the summarizer if last_speaker.name == "Admin": return group_chat.agent_by_name("Note_Summarizer") # Forward the note to the title generator if the user wants a title if last_speaker.name == "Note_Summarizer" and generate_title: return group_chat.agent_by_name("Title_Generator") # Handle the default case - exit return None return GroupChat( agents=agents, messages=[], max_round=3, # There will only be 3 turns in this group chat. The group chat will exit automatically post that. speaker_selection_method=speaker_selection_method, )
Imagine um GroupChat
como um grupo do WhatsApp onde todos os agentes podem conversar e colaborar. Essa configuração permite que os agentes desenvolvam o trabalho uns dos outros. A classe GroupChat
juntamente com uma classe complementar chamada GroupChatManager
, atua como os administradores do grupo, mantendo o controle de todas as mensagens que cada agente envia para garantir que todos fiquem por dentro do histórico de conversas.
No trecho de código acima, criamos um GroupChat
com um speaker_selection_method
personalizado. O speaker_selection_method
nos permite especificar nosso fluxo de trabalho personalizado. Aqui está uma representação visual do mesmo.
Como o speaker_selection_method
é essencialmente uma função Python, podemos fazer o que quisermos com ele! Isso nos ajuda a criar alguns fluxos de trabalho realmente poderosos. Por exemplo, poderíamos:
Imagine as possibilidades! 😜
O último passo é criar uma instância do GroupChat
, envolvê-la dentro de um GroupChatManager
e iniciar a conversa.
# Create our group chat groupchat = get_group_chat([user, note_summarizer, title_generator], generate_title=True) manager = autogen.GroupChatManager(groupchat=groupchat, llm_config=base_llm_config) # Start the chat user.initiate_chat( manager, clear_history=True, message=note, )
Nota: O usuário está conversando com o
GroupChatManager
, não com os agentes individuais. Ele não tem nenhuma pista de quais agentes entrarão na conversa para fornecer a resposta final. Furtivo, certo?
A saída será parecida com esta:
Admin (to chat_manager): Note: Convo with editor: - discuss titles and thumbnails - discuss video editing tips tracker - Zeeshan presents the tracker - what trick helps with what - he decidedls if we can experiment with something new - make sure all all videos since how config management works in k8s are backed u - make list of YouTube thumbnail templates - make list of YouTube idea generation limits -------------------------------------------------------------------------------- Next speaker: Note_Summarizer Note_Summarizer (to chat_manager): The note is about a conversation with an editor regarding video production. They discussed titles and thumbnails, as well as a video editing tips tracker presented by Zeeshan, which highlights tricks for specific tasks. Additionally, they ensured that all videos on Kubernetes configuration management are backed up and created lists of YouTube thumbnail templates and idea generation limits. -------------------------------------------------------------------------------- Next speaker: Title_Generator Title_Generator (to chat_manager): "Video Production Chat: Titles, Thumbnails, and Editing Tips" --------------------------------------------------------------------------------
Em seguida, vamos nos aprofundar no caso de uso final: fazer uma determinada “anotação”, reestruturando-a para maior clareza e, então, criar uma lista de tarefas para o usuário.
Veja como vamos fazer isso:
Começaremos identificando uma lista de tópicos abordados na nota. Esta lista é a força motriz por trás de todo o processo. Ela estabelece as seções para a nota reformatada e determina o nível de detalhes para nossas tarefas geradas.
Só há um pequeno problema. O Paraphrazer
e o agente Task_Creator
não se importam realmente com a saída um do outro. Eles só se importam com a saída do Topic_Analyzer
.
Então, precisamos de uma maneira de evitar que as respostas desses agentes atrapalhem o histórico de conversas, ou será um caos completo. Já assumimos o controle do fluxo de trabalho; agora, é hora de ser o chefe do histórico de conversas também! 😎
Primeiro, precisamos configurar nossos agentes. Não vou aborrecer você com os detalhes, então aqui está o código:
def get_topic_analyzer(base_llm_config: dict): # A system message to define the role and job of our agent system_message = """You are a helpful AI assistant. The user will provide you a note. Generate a list of topics discussed in that note. The output must obey the following "RULES": "RULES": - Output should only contain the important topics from the note. - There must be atleast one topic in output. - Don't reuse the same text from user's note. - Don't have more than 10 topics in output.""" # Create and return our assistant agent return autogen.AssistantAgent( name="Topic_Analyzer", llm_config=base_llm_config, system_message=system_message, ) def get_paraphrazer(base_llm_config: dict): # A system message to define the role and job of our agent system_message = """You are a helpful AI content editor. The user will provide you a note along with a summary. Rewrite that note and make sure you cover everything in the note. Do not include the title. The output must obey the following "RULES": "RULES": - Output must be in markdown. - Make sure you use each points provided in summary as headers. - Each header must start with `##`. - Headers are not bullet points. - Each header can optionally have a list of bullet points. Don't put bullet points if the header has no content. - Strictly use "-" to start bullet points. - Optionally make an additional header named "Addional Info" to cover points not included in the summary. Use "Addional Info" header for unclassified points. - Identify and correct spelling & grammatical mistakes.""" # Create and return our assistant agent return autogen.AssistantAgent( name="Paraphrazer", llm_config=base_llm_config, system_message=system_message, ) def get_tasks_creator(base_llm_config: dict): # A system message to define the role and job of our agent system_message = """You are a helpful AI personal assistant. The user will provide you a note along with a summary. Identify each task the user has to do as next steps. Make sure to cover all the action items mentioned in the note. The output must obey the following "RULES": "RULES": - Output must be an YAML object with a field named tasks. - Make sure each task object contains fields title and description. - Extract the title based on the tasks the user has to do as next steps. - Description will be in markdown format. Feel free to include additional formatting and numbered lists. - Strictly use "-" or "dashes" to start bullet points in the description field. - Output empty tasks array if no tasks were found. - Identify and correct spelling & grammatical mistakes. - Identify and fix any errors in the YAML object. - Output should strictly be in YAML with no ``` or any additional text.""" # Create and return our assistant agent return autogen.AssistantAgent( name="Task_Creator", llm_config=base_llm_config, system_message=system_message, )
GroupChat
personalizado Infelizmente. O AutoGen não nos permite controlar o histórico de conversas diretamente. Então, precisamos prosseguir e estender a classe GroupChat
com nossa implementação personalizada.
class CustomGroupChat(GroupChat): def __init__(self, agents): super().__init__(agents, messages=[], max_round=4) # This function get's invoked whenever we want to append a message to the conversation history. def append(self, message: Dict, speaker: Agent): # We want to skip messages from the Paraphrazer and the Task_Creator if speaker.name != "Paraphrazer" and speaker.name != "Task_Creator": super().append(message, speaker) # The `speaker_selection_method` now becomes a function we will override from the base class def select_speaker(self, last_speaker: Agent, selector: AssistantAgent): if last_speaker.name == "Admin": return self.agent_by_name("Topic_Analyzer") if last_speaker.name == "Topic_Analyzer": return self.agent_by_name("Paraphrazer") if last_speaker.name == "Paraphrazer": return self.agent_by_name("Task_Creator") # Return the user agent by default return self.agent_by_name("Admin")
Substituímos duas funções da classe base GroupChat
:
append
- Isso controla quais mensagens são adicionadas ao histórico de conversas.select_speaker
- Esta é outra maneira de especificar o speaker_selection_method
.
Mas espere, ao mergulhar mais fundo no código do AutoGen, percebi que o GroupChatManager
faz com que cada agente mantenha o histórico de conversas também. Não me pergunte por quê. Eu realmente não sei!
Então, vamos estender o GroupChatManager
também para corrigir isso:
class CustomGroupChatManager(GroupChatManager): def __init__(self, groupchat, llm_config): super().__init__(groupchat=groupchat, llm_config=llm_config) # Don't forget to register your reply functions self.register_reply(Agent, CustomGroupChatManager.run_chat, config=groupchat, reset_config=GroupChat.reset) def run_chat( self, messages: Optional[List[Dict]] = None, sender: Optional[Agent] = None, config: Optional[GroupChat] = None, ) -> Union[str, Dict, None]: """Run a group chat.""" if messages is None: messages = self._oai_messages[sender] message = messages[-1] speaker = sender groupchat = config for i in range(groupchat.max_round): # set the name to speaker's name if the role is not function if message["role"] != "function": message["name"] = speaker.name groupchat.append(message, speaker) if self._is_termination_msg(message): # The conversation is over break # We do not want each agent to maintain their own conversation history history # broadcast the message to all agents except the speaker # for agent in groupchat.agents: # if agent != speaker: # self.send(message, agent, request_reply=False, silent=True) # Pro Tip: Feel free to "send" messages to the user agent if you want to access the messages outside of autogen for agent in groupchat.agents: if agent.name == "Admin": self.send(message, agent, request_reply=False, silent=True) if i == groupchat.max_round - 1: # the last round break try: # select the next speaker speaker = groupchat.select_speaker(speaker, self) # let the speaker speak # We'll now have to pass their entire conversation of messages on generate_reply # Commented OG code: reply = speaker.generate_reply(sender=self) reply = speaker.generate_reply(sender=self, messages=groupchat.messages) except KeyboardInterrupt: # let the admin agent speak if interrupted if groupchat.admin_name in groupchat.agent_names: # admin agent is one of the participants speaker = groupchat.agent_by_name(groupchat.admin_name) # We'll now have to pass their entire conversation of messages on generate_reply # Commented OG code: reply = speaker.generate_reply(sender=self) reply = speaker.generate_reply(sender=self, messages=groupchat.messages) else: # admin agent is not found in the participants raise if reply is None: break # The speaker sends the message without requesting a reply speaker.send(reply, self, request_reply=False) message = self.last_message(speaker) return True, None
Fiz algumas pequenas edições na implementação original. Você deve conseguir acompanhar os comentários para saber mais.
Mas há uma coisa que eu realmente quero enfatizar. Você pode substituir o método “run_chat” do GroupChatManager para plugar em seu próprio mecanismo de fluxo de trabalho, como Apache Airflow ou Temporal. Praticantes de sistemas distribuídos sabem exatamente o quão poderosa é essa capacidade!
Montamos tudo como no exemplo anterior e observamos esse bebê ronronar! 🐱
# Create our agents user = get_user() topic_analyzer = get_topic_analyzer(base_llm_config) paraphrazer = get_paraphrazer(base_llm_config) task_creator = get_tasks_creator(base_llm_config) # Create our group chat groupchat = CustomGroupChat(agents=[user, topic_analyzer, paraphrazer, task_creator]) manager = CustomGroupChatManager(groupchat=groupchat, llm_config=base_llm_config) # Start the chat user.initiate_chat( manager, clear_history=True, message=note, ) # Lets print the count of tasks just for fun chat_messages = user.chat_messages.get(manager) if chat_messages is not None: for message in chat_messages: if message.get("name") == "Task_Creator": taskList = yaml.safe_load(message.get("content")) # type: ignore l = len(taskList.get("tasks")) print(f"Got {l} tasks from Task_Creator.")
A saída será parecida com esta:
Admin (to chat_manager): Note: Convo with editor: - discuss titles and thumbnails - discuss video editing tips tracker - Zeeshan presents the tracker - what trick helps with what - he decidedls if we can experiment with something new - make sure all all videos since how config management works in k8s are backed u - make list of YouTube thumbnail templates - make list of YouTube idea generation limits -------------------------------------------------------------------------------- Topic_Analyzer (to chat_manager): Here is the list of topics discussed in the note: 1. Titles 2. Thumbnails 3. Video editing tips 4. Config management in Kubernetes (k8s) 5. YouTube thumbnail templates 6. YouTube idea generation limits -------------------------------------------------------------------------------- Paraphrazer (to chat_manager): Here is the rewritten note in markdown format: ## Titles - Discuss titles and thumbnails with the editor ## Video Editing Tips Tracker ### Zeeshan presents the tracker - What trick helps with what - He decides if we can experiment with something new ## Config Management in Kubernetes (k8s) - Make sure all videos since how config management works in k8s are backed up ## YouTube Thumbnail Templates - Make a list of YouTube thumbnail templates ## YouTube Idea Generation Limits - Make a list of YouTube idea generation limits ## Additional Info - Discuss video editing tips tracker with Zeeshan - Present the tracker and decide if we can experiment with something new -------------------------------------------------------------------------------- Task_Creator (to chat_manager): tasks: - title: Discuss Titles and Thumbnails description: >- - Discuss titles and thumbnails with the editor This task involves having a conversation with the editor to discuss the titles and thumbnails for the videos. - title: Discuss Video Editing Tips Tracker description: >- - Zeeshan presents the tracker - Discuss what trick helps with what - Decide if we can experiment with something new This task involves discussing the video editing tips tracker presented by Zeeshan, understanding what tricks help with what, and deciding if it's possible to experiment with something new. - title: Back up All Videos Since How Config Management Works in k8s description: >- - Make sure all videos since how config management works in k8s are backed up This task involves ensuring that all videos related to config management in Kubernetes (k8s) are backed up. - title: Create List of YouTube Thumbnail Templates description: >- - Make list of YouTube thumbnail templates This task involves creating a list of YouTube thumbnail templates. - title: Create List of YouTube Idea Generation Limits description: >- - Make list of YouTube idea generation limits This task involves creating a list of YouTube idea generation limits. -------------------------------------------------------------------------------- Got 5 tasks from Task_Creator.
Sim. Bem-vindos à era da IA dando trabalho para nós, humanos! (Onde tudo deu errado? 🤷♂️)
Construir aplicativos baseados em IA generativa é difícil. Mas pode ser feito com as ferramentas certas. Para resumir:
Como próximos passos, você pode conferir os seguintes recursos para se aprofundar no mundo dos agentes de IA: