Building a Multi-Agent AI Health Assistant with Qwen, LangGraph, and Streamlit
Scale AI, a leading data-labeling company, has recently confirmed a significant investment from Meta, valuing the startup at $29 billion. Co-founder and CEO Alexandr Wang will step down from his role at Scale to join Meta, focusing on the company’s superintelligence efforts. Jason Droege, Scale’s current Chief Strategy Officer, will serve as interim CEO. Meta’s investment will be used to return capital to investors and fuel further growth. Scale emphasizes that it remains an independent entity, with Wang continuing as a director on its board. This investment underscores the importance of high-quality training data in the rapidly evolving field of AI, particularly as Meta competes with giants like Google, OpenAI, and Anthropic. The acquisition of top talent and resources is crucial for advancing AI capabilities. According to SignalFire data, Meta lost 4.3% of its top talent to other AI labs last year, highlighting the need for such strategic moves. For the past several years, leading AI labs have relied on Scale AI to produce and label data essential for training large language models (LLMs). Recently, the company has begun hiring highly skilled professionals, including PhD scientists and senior software engineers, to enhance data quality. Last year, Scale raised $1 billion from investors, including Amazon and Meta, at a $13.8 billion valuation. The new investment reflects Meta’s commitment to strengthening its AI efforts. Building a Multi-Agent Supervisor System In the realm of AI, building a multi-agent system can significantly enhance the ability to handle complex tasks that exceed the capabilities of a single agent. This tutorial focuses on creating an AI Health Assistant system that includes a Fitness Agent, a Dietitian Agent, and a Mental Health Agent, all coordinated by a Supervisor Agent. The system leverages the Qwen LLM and the Streamlit framework for a seamless user interface. Step 1: Installation Install Ollama: Download and install Ollama from its official website to run Qwen locally. Verify Installation: Use ollama -v to check the installation. Pull the Qwen Model: Execute ollama pull qwen2.5:14b to download the Qwen model. Step 2: Set Up API Keys The Fitness Agent and the Dietitian Agent will use external APIs for real-world data. APIs from API-Ninjas and Spoonacular will provide exercise and nutrition information, respectively. Sign up for accounts on these platforms, retrieve API keys, and store them in a .env file: plaintext EXERCISE_API_KEY = xxxxxxxx DIET_API_KEY = xxxxxxxxxxx Step 3: Create State Manage conversation history and task progression using LangGraph’s MessagesState class. Define a custom state class to inherit from MessagesState: ```python from lang-chain_core.messages import HumanMessage, AIMessage from langgraph.graph import StateGraph, MessagesState, START, END class State(MessagesState): next: str ``` Step 4: Create Custom Tools Fitness Tool Fetch exercise data to generate personalized workout plans: ```python import requests import random import uuid import os _fitness_api_key = os.getenv("EXERCISE_API_KEY") class FitnessData: def init(self): self.base_url = "https://api.api-ninjas.com/v1/exercises" self.api_key = _fitness_api_key def get_muscle_groups_and_types(self): muscle_targets = { 'full_body': ["abdominals", "biceps", "calves", "chest", "forearms", "glutes", "hamstrings", "lower_back", "middle_back", "quadriceps", "traps", "triceps", "adductors"], 'upper_body': ["biceps", "chest", "forearms", "lats", "lower_back", "middle_back", "neck", "traps", "triceps"], 'lower_body': ["adductors", "calves", "glutes", "hamstrings", "quadriceps"] } exercise_types = {'types': ["powerlifting", "strength", "stretching", "strongman"]} return muscle_targets, exercise_types def fetch_exercises(self, type, muscle, difficulty): headers = {'X-Api-Key': self.api_key} params = {'type': type, 'muscle': muscle, 'difficulty': difficulty} try: response = requests.get(self.base_url, headers=headers, params=params) result = response.json() if not result: print(f"No exercises found for {muscle}") return result except requests.RequestException as e: print(f"Request failed: {e}") return [] def generate_workout_plan(self, query='full_body', difficulty='intermediate'): output = [] muscle_targets, exercise_types = self.get_muscle_groups_and_types() muscle = random.choice(muscle_targets.get(query)) type = random.choice(exercise_types.get('types')) result = self.fetch_exercises(type, muscle, difficulty) limit_plan = result[:3] for i, data in enumerate(limit_plan): if data not in output: output.append(f"Exercise {i+1}: {data['name']}") output.append(f"Muscle: {data['muscle']}") output.append(f"Instructions: {data['instructions']}") return output ``` Convert the function to a LangChain custom tool: ```python from langgraph.types import Command from typing import Annotated, Literal @tool def fitness_data_tool(query: Annotated[str, "This input will either be full_body, upper_body, or lower_body"]): fitness_tool = FitnessData() result = fitness_tool.generate_workout_plan(query) return result ``` Dietitian Tool Generate customized meal plans using the Spoonacular API: ```python import requests import random import uuid import os _diet_api_key = os.getenv("DIET_API_KEY") class Dietitian: def init(self): self.base_url = "https://api.spoonacular.com" self.api_key = _diet_api_key def fetch_meal(self, time_frame="day", diet="None"): url = f"{self.base_url}/mealplanner/generate" params = {"timeFrame": time_frame, "diet": diet, "apiKey": self.api_key} response = requests.get(url, params=params) if not response: print('Meal Plan not found') return response.json() def get_recipe_information(self, recipe_id): url = f"{self.base_url}/recipes/{recipe_id}/information" params = {"apiKey": self.api_key} response = requests.get(url, params=params) if not response: print("Recipe not found") return response.json() def generate_meal_plan(self, query): meals_processed = [] meal_plan = self.fetch_meal(query) meals = meal_plan.get('meals') nutrients = meal_plan.get('nutrients') for i, meal in enumerate(meals): recipe_info = self.get_recipe_information(meal.get('id')) ingredients = [ingredient['original'] for ingredient in recipe_info.get('extendedIngredients')] meals_processed.append(f"?️ Meal {i+1}: {meal.get('title')}") meals_processed.append(f"Prep Time: {meal.get('readyInMinutes')} min") meals_processed.append(f"Servings: {meal.get('servings')} servings") meals_processed.append("? Ingredients:\n" + "\n".join(ingredients)) meals_processed.append(f"? Instructions:\n {recipe_info.get('instructions')}") meals_processed.append( "\n Daily Nutrients:\n" f"Protein: {nutrients.get('protein', 'N/A')} g\n" f"Fat: {nutrients.get('fat', 'N/A')} g\n" f"Carbohydrates: {nutrients.get('carbohydrates', 'N/A')} g" ) return meals_processed ``` Convert the function to a LangChain custom tool: python @tool def diet_tool(query: Annotated[str, "This input will either be None, vegetarian, or vegan"]): dietitian_tool = Dietitian() result = dietitian_tool.generate_meal_plan(query) return result Mental Health Agent Provide unique mental wellness tips to users: ```python from langchain.prompts import PromptTemplate def mental_health_node(state: State) -> Command[Literal["supervisor"]]: prompt = PromptTemplate.from_template( """You are a supportive mental wellness coach. Your task is to: - Give a unique mental wellness tip or stress-reducing practice. - Make it simple, kind, and useful. Avoid repeating tips.""" ) chain = prompt | llm response = chain.invoke(state) return Command( update={ "messages": [ AIMessage(content=f"Here's your wellness tip: {response.content}", name="wellness") ] }, goto="supervisor", ) ``` Step 5: Define LLM Define the Qwen LLM and memory management: ```python from langchain_ollama import ChatOllama from langgraph.checkpoint.memory import MemorySaver llm = ChatOllama(model="qwen2.5:14b") memory = MemorySaver() ``` Step 6: Creating the Agent & Nodes Define the agents and their corresponding nodes: Fitness Agent ```python fitness_agent_prompt = """You can only answer queries related to workouts.""" fitness_agent = create_react_agent(llm, tools=[fitness_data_tool], prompt=fitness_agent_prompt) def fitness_node(state: State) -> Command[Literal["supervisor"]]: result = fitness_agent.invoke(state) return Command( update={ "messages": [ AIMessage(content=result["messages"][-1].content, name="fitness") ] }, goto="supervisor", ) ``` Dietitian Agent ```python dietitarian_system_prompt = """You can only answer queries related to diet and meal plans.""" dietitarian_agent = create_react_agent(llm, tools=[diet_tool], prompt=dietitarian_system_prompt) def dietitian_node(state: State) -> Command[Literal["supervisor"]]: result = dietitarian_agent.invoke(state) return Command( update={ "messages": [ AIMessage(content=result["messages"][-1].content, name="dietitian") ] }, goto="supervisor", ) ``` Mental Health Agent python def mental_health_node(state: State) -> Command[Literal["supervisor"]]: prompt = PromptTemplate.from_template( """You are a supportive mental wellness coach. Your task is to: - Give a unique mental wellness tip or stress-reducing practice. - Make it simple, kind, and useful. Avoid repeating tips.""" ) chain = prompt | llm response = chain.invoke(state) return Command( update={ "messages": [ AIMessage(content=f"Here's your wellness tip: {response.content}", name="wellness") ] }, goto="supervisor", ) Supervisor Agent ```python members = ["fitness", "dietitian", "wellness"] options = members + ["FINISH"] system_prompt = ( "You are a supervisor tasked with managing a conversation between the" f" following workers: {members}. Given the following user request," " respond with the worker to act next. Each worker will perform a" " task and respond with their results and status. When finished," " respond with FINISH." "Guidelines:\n" "1. Always check the last message in the conversation to determine if the task has been completed.\n" "2. If you already have the final answer or outcome, return 'FINISH'.\n" ) class Router(TypedDict): """Worker to route to next. If no workers needed, route to FINISH.""" next: Literal[*options] def supervisor_node(state: State) -> Command[Literal[*members, "end"]]: messages = [ {"role": "system", "content": system_prompt}, ] + state["messages"] response = llm.with_structured_output(Router).invoke(messages) goto = response["next"] if goto == "FINISH": goto = END return Command(goto=goto, update={"next": goto}) ``` Step 7: Build Multi-Agent Graph Create the workflow graph using LangGraph: python builder = StateGraph(State) builder.add_edge(START, "supervisor") builder.add_node("supervisor", supervisor_node) builder.add_node("fitness", fitness_node) builder.add_node("dietitian", dietitian_node) builder.add_node("wellness", mental_health_node) graph = builder.compile(checkpointer=memory) Step 8: Test the Multi-Agent System Test the system by passing user input and defining a helper function to parse the output: ```python def parse_langgraph_output(stream): results = [] for key, value in stream.items(): if key == "supervisor": continue messages = value.get("messages", []) for msg in messages: if isinstance(msg, str): results.append((key, msg)) elif isinstance(msg, AIMessage): results.append((key, msg.content)) return results User input example inputs = { "messages": [ HumanMessage(content="Give me wellness tips for the month?") ], } Run the system and print the output for step in graph.stream(inputs, config={"configurable": {"thread_id": "1", "recursion_limit": 10}}): print(step) response_message = parse_langgraph_output(step) for agent, content in response_message: print(f"Agent : {agent}\n\n{content}\n{'='*50}") ``` Industry Insights This multi-agent system exemplifies the growing trend in AI development, where specialized agents collaboratively tackle complex problems. By integrating external APIs and leveraging the capabilities of Qwen, the system showcases how AI can be tailored to provide personalized health and wellness guidance. The use of Streamlit for the user interface adds an accessible layer, making it easier for users to interact with the system. The collaboration between Meta and Scale AI highlights the strategic importance of high-quality data and advanced AI models in tech innovation. Companies are increasingly investing in and developing comprehensive AI ecosystems to stay competitive in the rapidly evolving market. Meta’s focus on superintelligence and the integration of such multi-agent systems could be a pivotal move in the AI arms race.
