Lesson · 40 min · Free
Capstone Part 3: Completing the Nutrition Agent
Capstone Part 3: Completing the Nutrition Agent body { font-family: sans-serif; line-height: 1.6; margin: 20px; } h1, h2 { color: #2c3e50; } pre { background-color: #ecf0f1; padding: 15px; border-radius: 5px; overflow-x:
Capstone Part 3: Completing the Nutrition Agent
Welcome to the final part of our Capstone Project! In this lesson, we will synthesize all the components developed in previous modules to complete our Nutrition Agent. We'll focus on integrating the data retrieval mechanisms, the prompt engineering strategies, and the agentic orchestration to create a robust and interactive system. Our goal is to enable the agent to accurately answer complex nutritional queries, considering user-specific dietary restrictions and health goals, leveraging its access to a knowledge base (simulated or real). Recall that in Part 1, we designed the agent's architecture and defined its persona. In Part 2, we focused on prompt engineering, crafting effective instructions and few-shot examples for various nutritional tasks. Now, we bring these together. The core of our completion involves establishing a clear execution flow: receiving a user query, processing it to identify intent, invoking appropriate tools (e.g., database lookup, API call for nutrient data), synthesizing information, and generating a coherent, context-aware response.
Integrating Tools and Orchestration
A key aspect of building an effective agent is its ability to use tools. For our Nutrition Agent, these tools might include a database of food items and their nutritional content, an API to check for drug-nutrient interactions, or even a simple function to calculate daily caloric needs based on user input. The agent's "brain" (the LLM) needs to be instructed on when and how to use these tools. This is often achieved through a combination of prompt engineering and a framework that facilitates tool calling. Let's consider a simplified integration using a hypothetical NutritionDatabase tool. The agent's prompt will guide it to call this tool when it needs specific nutritional information. # Assuming 'NutritionDatabase' is a class with a 'lookup_food_data' method # and 'llm' is our instantiated Language Model class NutritionDatabase: def lookup_food_data(self, food_item: str) -> dict: """ Simulates looking up nutritional data for a given food item. In a real scenario, this would query a database or external API. """ data = { "apple": {"calories": 95, "carbs": 25, "protein": 0.5, "fat": 0.3}, "chicken breast": {"calories": 165, "carbs": 0, "protein": 31, "fat": 3.6}, "spinach": {"calories": 23, "carbs": 3.6, "protein": 2.9, "fat": 0.4} } return data.get(food_item.lower(), {"error": "Food item not found."}) # Define the tools available to the agent tools = [ { "name": "NutritionDatabase", "description": "Provides detailed nutritional information for various food items.", "parameters": { "type": "object", "properties": { "food_item": {"type": "string", "description": "The food item to look up."} }, "required": ["food_item"] }, "function": NutritionDatabase().lookup_food_data } ] # Example of how an agent might be structured (simplified for illustration) def run_nutrition_agent(query: str, llm, available_tools): # Initial prompt to guide the LLM system_prompt = ( "You are a helpful Nutrition Agent. Your goal is to provide accurate " "nutritional advice. Use the available tools to find specific food data " "when necessary. If a user asks about a food item, you should use the NutritionDatabase tool." ) # Simulate LLM's thought process and tool calling # In a real framework (like LangChain, LlamaIndex), this would be automated. if "nutritional information for" in query.lower(): food_item = query.lower().split("for ")[-1].strip("?.") print(f"Agent thought: User is asking about {food_item}. Calling NutritionDatabase tool.") tool_output = available_tools[0]["function"](food_item) # Directly calling the function if "error" not in tool_output: response = f"Here is the nutritional data for {food_item}: " \ f"Calories: {tool_output['calories']}kcal, " \ f"Carbohydrates: {tool_output['carbs']}g, " \ f"Protein: {tool_output['protein']}g, " \ f"Fat: {tool_output['fat']}g." else: response = f"Sorry, I couldn't find nutritional data for {food_item}." else: # Fallback for general queries or if no tool is explicitly triggered # In a full agent, this would involve a direct LLM call response = llm.generate_response(system_prompt + "\nUser query: " + query) return response # Example usage: # Assuming 'llm' has a simple 'generate_response' method for non-tool queries class MockLLM: def generate_response(self, prompt): if "dietary restrictions" in prompt: return "Understanding dietary restrictions is crucial. Please specify your restrictions, and I can help tailor advice." return "I can help with general nutritional queries. What would you like to know?" mock_llm = MockLLM() print(run_nutrition_agent("What is the nutritional information for an apple?", mock_llm, tools)) print(run_nutrition_agent("Tell me about dietary restrictions.", mock_llm, tools)) The agent's ability to reason and plan is paramount. For complex queries, it might need to chain multiple tool calls or perform intermediate reasoning steps. For instance, if a user asks, "Suggest a low-carb dinner for someone with a nut allergy," the agent would first identify "low-carb" and "nut allergy" as constraints, then query the database for suitable ingredients, and finally formulate a meal suggestion, possibly checking for cross-contamination risks if such data were available. Let's refine our agent's response generation to incorporate user-specific context, a critical feature for pharmacy and biotech applications where personalized health advice is key. We'll ensure the agent can remember past interactions or explicitly take user profiles into account. # Extending our agent with context management class NutritionAgent: def __init__(self, llm, tools): self.llm = llm self.tools = tools self.user_profile = {} # Stores user-specific data like allergies, goals self.conversation_history = [] # For maintaining context def update_user_profile(self, key, value): self.user_profile[key] = value print(f"Updated user profile: {key} = {value}") def _select_and_execute_tool(self, tool_name: str, **kwargs): for tool_spec in self.tools: if tool_spec["name"] == tool_name: print(f"Agent thought: Executing tool '{tool_name}' with args: {kwargs}") return tool_spec["function"](**kwargs) return {"error": f"Tool '{tool_name}' not found."} def process_query(self, query: str) -> str: # Add current query to history self.conversation_history.append({"role": "user", "content": query}) # Step 1: Intent Recognition & Tool Calling Logic # This is where a more sophisticated LLM would determine intent # For simplicity, we use keyword matching here. if "my allergy is" in query.lower(): allergy = query.lower().split("my allergy is")[-1].strip("?. ") self.update_user_profile("allergy", allergy) return f"Understood. I've noted your {allergy} allergy." elif "my goal is" in query.lower(): goal = query.lower().split("my goal is")[-1].strip("?. ") self.update_user_profile("health_goal", goal) return f"Understood. I've noted your health goal: {goal}." elif "nutritional information for" in query.lower(): food_item = query.lower().split("for ")[-1].strip("?.") tool_output = self._select_and_execute_tool("NutritionDatabase", food_item=food_item) if "error" not in tool_output: response = f"Here is the nutritional data for {food_item}: " \ f"Calories: {tool_output['calories']}kcal, " \ f"Carbohydrates: {tool_output['carbs']}g, " \ f"Protein: {tool_output['protein']}g, " \ f"Fat: {tool_output['fat']}g." # Add a personalized touch based on profile if self.user_profile.get("allergy") and self.user_profile["allergy"] in food_item.lower(): response += f" Please note this food contains {self.user_profile['allergy']}, which you are allergic to." if self.user_profile.get("health_goal") == "low-carb" and tool_output['carbs'] > 10: response += f" This item is relatively high in carbohydrates, which might not align with your '{self.user_profile['health_goal']}' goal." return response else: return f"Sorry, I couldn't find nutritional data for {food_item}." else: # Step 2: Direct LLM response for general queries, with context # In a real system, the entire conversation history and user_profile # would be passed to the LLM to generate a context-aware response. full_prompt = ( f"You are a helpful Nutrition Agent. Current user profile: {self.user_profile}. " f"Conversation history (last few turns): {self.conversation_history[-3:]}. " f"Please respond to the user query: '{query}'" ) return self.llm.generate_response(full_prompt) # Instantiate the agent nutrition_agent = NutritionAgent(mock_llm, tools) # Simulate interaction print(nutrition_agent.process_query("My allergy is peanuts.")) print(nutrition_agent.process_query("My goal is low-carb.")) print(nutrition_agent.process_query("What is the nutritional information for an apple?")) print(nutrition_agent.process_query("What is the nutritional information for chicken breast?")) print(nutrition_agent.process_query("I'm looking for a low-carb meal idea.")) # This would trigger the general LLM response This extended example demonstrates how the agent can maintain a user profile and conversation history, using this context to provide more relevant and personalized responses. The orchestration logic decides when to update the profile, when to call a tool, and when to rely on the LLM's general knowledge, all while injecting context into the final response generation. For pharmacy students, this ability to integrate patient-specific data (e.g., drug interactions, contraindications) into an agent's advice is directly analogous and highly valuable.
Watch the full lesson — free
This topic is part of AI Agents Crash Course: From Zero to Nutrition Agent, a complete AI-narrated video course. Press play once and watch the entire lecture like a movie.
Start the course free →