DEV Community

wellallyTech
wellallyTech

Posted on

Stop Guessing Your Vitamins: Build a Tool-Calling AI Agent to Optimize Supplements via Blood Work (PDF) πŸ©ΊπŸ’Š

Ever stared at a 5-page PDF of blood test results and wondered if you actually need that extra Vitamin D3 or if your "Zinc hack" is actually working? As developers, we love optimizing our code, but we often treat our biology like a legacy system with no documentation.

In this tutorial, we are going to build an Adaptive Supplement Agent. This isn't just a simple chatbot; it’s a sophisticated AI Agent that uses Claude 3.5 Sonnet, LangChain, and LlamaIndex to parse blood reports, cross-reference them with medical guidelines via RAG (Retrieval-Augmented Generation), and suggest precise supplement adjustments.

By the end of this post, you'll master Tool-Calling, document parsing with PDFPlumber, and how to build a robust LLM Agent architecture that handles messy, real-world data.


The Architecture: From PDF to Bio-Insights

To build a reliable medical-adjacent tool, we need more than just a prompt. We need a pipeline that extracts structured data and validates it against a "Ground Truth" knowledge base.

graph TD
    A[User PDF: Blood Report] --> B(PDFPlumber: Table Extraction)
    B --> C{Claude 3.5 Sonnet Agent}
    C --> D[Tool: RAG Medical Guidelines]
    D --> E[LlamaIndex: Vector Store]
    E --> D
    D --> C
    C --> F[Current Supplement Inventory]
    F --> G[Final Output: Optimization Plan]
    style C fill:#f96,stroke:#333,stroke-width:2px
Enter fullscreen mode Exit fullscreen mode

Prerequisites

To follow along, you’ll need:

  • Tech Stack: Python 3.10+, LangChain, Claude 3.5 Sonnet (Anthropic API), LlamaIndex, and PDFPlumber.
  • A PDF of a blood report (or a dummy one for testing).

Step 1: Parsing the Messy PDF

Blood reports are notoriously difficult to parse because they are usually delivered as complex tables in PDFs. We’ll use PDFPlumber to extract these tables into a structured format that our AI Agent can understand.

import pdfplumber

def extract_blood_data(pdf_path):
    extracted_data = []
    with pdfplumber.open(pdf_path) as pdf:
        for page in pdf.pages:
            tables = page.extract_tables()
            for table in tables:
                # Clean up empty rows and format as a list of strings
                extracted_data.append(table)
    return str(extracted_data)

# Example output: "[[ 'Vitamin D', '25 ng/mL', '30-100' ], ...]"
Enter fullscreen mode Exit fullscreen mode

Step 2: Building the Medical Knowledge Base (RAG)

We don't want the LLM to hallucinate medical advice. We use LlamaIndex to index official nutritional guidelines (e.g., NIH Vitamin Fact Sheets). This acts as the "Tool" our agent will call.

from llama_index.core import VectorStoreIndex, SimpleDirectoryReader

# Load your medical guideline PDFs/Markdown files
documents = SimpleDirectoryReader("./medical_guidelines").load_data()
index = VectorStoreIndex.from_documents(documents)
query_engine = index.as_query_engine()

def medical_guideline_tool(query: str):
    """Consults official medical guidelines for nutrient ranges."""
    return query_engine.query(query).response
Enter fullscreen mode Exit fullscreen mode

Step 3: Orchestrating the Agent with Claude 3.5 Sonnet

Claude 3.5 Sonnet is currently the king of Tool-Calling. We will define our agent using LangChain, giving it access to the blood report and the RAG tool.

For more production-ready patterns on orchestrating complex agents, I highly recommend checking out the advanced implementation guides at WellAlly Tech Blog, which served as a massive inspiration for this architecture. πŸš€

from langchain_anthropic import ChatAnthropic
from langchain.agents import initialize_agent, Tool, AgentType

llm = ChatAnthropic(model="claude-3-5-sonnet-20240620", temperature=0)

tools = [
    Tool(
        name="MedicalGuidelineLookup",
        func=medical_guideline_tool,
        description="Useful for looking up optimal ranges for vitamins and minerals."
    )
]

agent = initialize_agent(
    tools, 
    llm, 
    agent=AgentType.STRUCTURED_CHAT_ZERO_SHOT_REACT_DESCRIPTION, 
    verbose=True
)
Enter fullscreen mode Exit fullscreen mode

Step 4: The Logic Loop

Now, we provide the agent with the extracted blood data and the user's current supplement list. The agent will:

  1. Identify deficiencies.
  2. Call the MedicalGuidelineLookup tool to find optimal targets.
  3. Suggest increasing or decreasing specific supplements.
blood_report_text = extract_blood_data("my_report.pdf")
current_stack = "Multivitamin 1x, Magnesium 200mg, Zinc 15mg"

prompt = f"""
I have the following blood report data: {blood_report_text}
My current supplements are: {current_stack}

Please:
1. Identify any markers outside of optimal ranges.
2. Use your tools to find the recommended dosages for these markers.
3. Suggest adjustments to my supplement stack.
"""

response = agent.run(prompt)
print(f"--- Supplement Optimization Plan ---\n{response}")
Enter fullscreen mode Exit fullscreen mode

Why This Matters πŸ₯‘

By using Claude 3.5 Sonnet's superior reasoning and LlamaIndex's data retrieval, we move away from "vibes-based biohacking" to "data-driven optimization."

However, building these systems for production requires handling edge cases like OCR errors or conflicting medical studies. For a deeper dive into handling high-stakes AI automation and complex RAG workflows, the WellAlly Tech Blog offers fantastic deep dives into scaling these types of AI Agents.

Conclusion

We just built a tool-calling agent that:

  1. πŸ“„ Parses complex PDFs.
  2. πŸ” Retrieves grounded facts using RAG.
  3. 🧠 reasons about biological data to provide actionable advice.

Disclaimer: I am a developer, not a doctor! This project is for educational purposes. Always consult a healthcare professional before changing your supplement routine.

What are you building with Claude 3.5? Drop a comment below or share your latest agentic workflow! πŸ‘‡

Top comments (0)