Created
December 23, 2025 11:40
-
-
Save duarteocarmo/58fda6d982fb6d9dd5bfb561214a77ce to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| { | |
| "cells": [ | |
| { | |
| "cell_type": "code", | |
| "execution_count": 1, | |
| "metadata": {}, | |
| "outputs": [], | |
| "source": [ | |
| "import polars\n", | |
| "import jupyter_black\n", | |
| "import dspy\n", | |
| "from dspy.evaluate import Evaluate\n", | |
| "import typing as t\n", | |
| "import random\n", | |
| "import matplotlib.pyplot as plt\n", | |
| "import os\n", | |
| "from openai import OpenAI\n", | |
| "from pydantic import BaseModel\n", | |
| "\n", | |
| "jupyter_black.load()\n", | |
| "\n", | |
| "\n", | |
| "DATASET_NAME = \"../datasets/curated/golden_dataset_reviewed_v3.json\"\n", | |
| "MAX_LABELED_DEMOS = 4\n", | |
| "MAX_BOOTSTRAPPED_DEMOS = 4\n", | |
| "NUM_THREADS = 32" | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": 2, | |
| "metadata": {}, | |
| "outputs": [], | |
| "source": [ | |
| "class EvalResults:\n", | |
| " def __init__(self, metric_name: str = \"Calories accuracy @ 10%\"):\n", | |
| " self.results = {}\n", | |
| " self.metric_name = metric_name\n", | |
| "\n", | |
| " def add_result(self, name, result):\n", | |
| " self.results[name] = result\n", | |
| "\n", | |
| " def print_results(self):\n", | |
| " sorted_results = sorted(self.results.items(), key=lambda x: x[1], reverse=True)\n", | |
| " for name, score in sorted_results:\n", | |
| " print(f\"{name}: {score}\")\n", | |
| "\n", | |
| " def plot_results(self):\n", | |
| " # Remove models with 0 score for clarity\n", | |
| " valid_results = [\n", | |
| " (name, score) for name, score in self.results.items() if score > 0\n", | |
| " ]\n", | |
| " if not valid_results:\n", | |
| " print(\"No valid results to plot.\")\n", | |
| " return\n", | |
| "\n", | |
| " valid_results.sort(key=lambda x: x[1], reverse=True)\n", | |
| " names, scores = zip(*valid_results)\n", | |
| "\n", | |
| " plt.figure(figsize=(10, 6))\n", | |
| " plt.bar(names, scores, color=\"steelblue\")\n", | |
| " plt.ylabel(\"Score\")\n", | |
| " plt.xlabel(\"Model\")\n", | |
| " plt.title(self.metric_name)\n", | |
| " plt.xticks(rotation=35, ha=\"right\")\n", | |
| " plt.ylim(0, max(scores) * 1.1)\n", | |
| " plt.grid(True, axis=\"y\", linestyle=\"--\", alpha=0.7)\n", | |
| " plt.tight_layout()\n", | |
| " plt.show()\n", | |
| "\n", | |
| "\n", | |
| "er = EvalResults()" | |
| ] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "metadata": {}, | |
| "source": [ | |
| "# Get train validation dataset" | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": 3, | |
| "metadata": {}, | |
| "outputs": [ | |
| { | |
| "data": { | |
| "text/html": [ | |
| "<div><style>\n", | |
| ".dataframe > thead > tr,\n", | |
| ".dataframe > tbody > tr {\n", | |
| " text-align: right;\n", | |
| " white-space: pre-wrap;\n", | |
| "}\n", | |
| "</style>\n", | |
| "<small>shape: (107, 4)</small><table border=\"1\" class=\"dataframe\"><thead><tr><th>input</th><th>total_calories</th><th>food_groups</th><th>source</th></tr><tr><td>str</td><td>f64</td><td>list[str]</td><td>str</td></tr></thead><tbody><tr><td>"300 g of pasta with butter and…</td><td>618.0</td><td>["dairy", "grain"]</td><td>"golden_dataset"</td></tr><tr><td>"250 g of pasta with cherry tom…</td><td>468.0</td><td>["vegetable", "grain", "dairy"]</td><td>"golden_dataset"</td></tr><tr><td>"200 g of pasta with pesto and …</td><td>430.0</td><td>["vegetable", "grain", "dairy"]</td><td>"golden_dataset"</td></tr><tr><td>"100 g of pasta"</td><td>158.0</td><td>["grain"]</td><td>"golden_dataset"</td></tr><tr><td>"2 toasts with cheese and butte…</td><td>388.0</td><td>["vegetable", "dairy", "grain"]</td><td>"golden_dataset"</td></tr><tr><td>…</td><td>…</td><td>…</td><td>…</td></tr><tr><td>"For lunch, I'm having an Engli…</td><td>406.69</td><td>["grain", "dairy", … "fruit"]</td><td>"nutribench"</td></tr><tr><td>"I enjoyed a snack that include…</td><td>60.0</td><td>["fruit"]</td><td>"nutribench"</td></tr><tr><td>"I made myself a quick lunch wi…</td><td>153.87</td><td>["meat and alternatives", "grain"]</td><td>"nutribench"</td></tr><tr><td>"I’ve got 507 grams of bottled …</td><td>71.5</td><td>["meat and alternatives"]</td><td>"nutribench"</td></tr><tr><td>"For breakfast, I'm having 190g…</td><td>1542.0</td><td>["grain", "dairy", "meat and alternatives"]</td><td>"nutribench"</td></tr></tbody></table></div>" | |
| ], | |
| "text/plain": [ | |
| "shape: (107, 4)\n", | |
| "┌────────────────────────────────┬────────────────┬───────────────────────────────┬────────────────┐\n", | |
| "│ input ┆ total_calories ┆ food_groups ┆ source │\n", | |
| "│ --- ┆ --- ┆ --- ┆ --- │\n", | |
| "│ str ┆ f64 ┆ list[str] ┆ str │\n", | |
| "╞════════════════════════════════╪════════════════╪═══════════════════════════════╪════════════════╡\n", | |
| "│ 300 g of pasta with butter ┆ 618.0 ┆ [\"dairy\", \"grain\"] ┆ golden_dataset │\n", | |
| "│ and… ┆ ┆ ┆ │\n", | |
| "│ 250 g of pasta with cherry ┆ 468.0 ┆ [\"vegetable\", \"grain\", ┆ golden_dataset │\n", | |
| "│ tom… ┆ ┆ \"dairy\"… ┆ │\n", | |
| "│ 200 g of pasta with pesto and ┆ 430.0 ┆ [\"vegetable\", \"grain\", ┆ golden_dataset │\n", | |
| "│ … ┆ ┆ \"dairy\"… ┆ │\n", | |
| "│ 100 g of pasta ┆ 158.0 ┆ [\"grain\"] ┆ golden_dataset │\n", | |
| "│ 2 toasts with cheese and ┆ 388.0 ┆ [\"vegetable\", \"dairy\", ┆ golden_dataset │\n", | |
| "│ butte… ┆ ┆ \"grain\"… ┆ │\n", | |
| "│ … ┆ … ┆ … ┆ … │\n", | |
| "│ For lunch, I'm having an ┆ 406.69 ┆ [\"grain\", \"dairy\", … \"fruit\"] ┆ nutribench │\n", | |
| "│ Engli… ┆ ┆ ┆ │\n", | |
| "│ I enjoyed a snack that ┆ 60.0 ┆ [\"fruit\"] ┆ nutribench │\n", | |
| "│ include… ┆ ┆ ┆ │\n", | |
| "│ I made myself a quick lunch ┆ 153.87 ┆ [\"meat and alternatives\", ┆ nutribench │\n", | |
| "│ wi… ┆ ┆ \"gra… ┆ │\n", | |
| "│ I’ve got 507 grams of bottled ┆ 71.5 ┆ [\"meat and alternatives\"] ┆ nutribench │\n", | |
| "│ … ┆ ┆ ┆ │\n", | |
| "│ For breakfast, I'm having ┆ 1542.0 ┆ [\"grain\", \"dairy\", \"meat and ┆ nutribench │\n", | |
| "│ 190g… ┆ ┆ a… ┆ │\n", | |
| "└────────────────────────────────┴────────────────┴───────────────────────────────┴────────────────┘" | |
| ] | |
| }, | |
| "execution_count": 3, | |
| "metadata": {}, | |
| "output_type": "execute_result" | |
| } | |
| ], | |
| "source": [ | |
| "dataset = polars.read_json(DATASET_NAME)\n", | |
| "dataset" | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": 6, | |
| "metadata": {}, | |
| "outputs": [ | |
| { | |
| "data": { | |
| "text/plain": [ | |
| "[{'input': \"For dinner, I'm having 25 grams of bread, 150 grams of chicken wings, and a 250-gram mixed vegetable salad.\",\n", | |
| " 'total_calories': 633.0,\n", | |
| " 'food_groups': ['grain', 'meat and alternatives', 'vegetable'],\n", | |
| " 'source': 'nutribench'},\n", | |
| " {'input': 'I enjoyed 200 grams of tea with sugar along with 230 grams of coconut milk rice for breakfast.',\n", | |
| " 'total_calories': 558.0,\n", | |
| " 'food_groups': ['grain', 'fruit'],\n", | |
| " 'source': 'nutribench'},\n", | |
| " {'input': 'stracciatella with confit grapes and 2 little focaccia pieces',\n", | |
| " 'total_calories': 350.0,\n", | |
| " 'food_groups': ['fruit', 'dairy', 'grain'],\n", | |
| " 'source': 'golden_dataset'}]" | |
| ] | |
| }, | |
| "execution_count": 6, | |
| "metadata": {}, | |
| "output_type": "execute_result" | |
| } | |
| ], | |
| "source": [ | |
| "dataset.sample(3).to_dicts()" | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": 4, | |
| "metadata": {}, | |
| "outputs": [ | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "len(dataset)=107\n", | |
| "len(trainset)=53\n", | |
| "len(testset)=54\n" | |
| ] | |
| } | |
| ], | |
| "source": [ | |
| "dataset_list = dataset.to_dicts()\n", | |
| "random.Random(42).shuffle(dataset_list) # make deterministic\n", | |
| "n_dataset = len(dataset_list)\n", | |
| "train = 0.50\n", | |
| "val = 1 - train\n", | |
| "\n", | |
| "trainset = [\n", | |
| " dspy.Example(\n", | |
| " food_description=item[\"input\"],\n", | |
| " food_groups=item[\"food_groups\"],\n", | |
| " total_calories=item[\"total_calories\"],\n", | |
| " source=item[\"source\"],\n", | |
| " ).with_inputs(\"food_description\")\n", | |
| " for item in dataset_list[: int(n_dataset * train)]\n", | |
| "]\n", | |
| "\n", | |
| "testset = [\n", | |
| " dspy.Example(\n", | |
| " food_description=item[\"input\"],\n", | |
| " food_groups=item[\"food_groups\"],\n", | |
| " total_calories=item[\"total_calories\"],\n", | |
| " source=item[\"source\"],\n", | |
| " ).with_inputs(\"food_description\")\n", | |
| " for item in dataset_list[int(n_dataset * train) :]\n", | |
| "]\n", | |
| "print(f\"{len(dataset)=}\")\n", | |
| "print(f\"{len(trainset)=}\")\n", | |
| "print(f\"{len(testset)=}\")" | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": 5, | |
| "metadata": {}, | |
| "outputs": [ | |
| { | |
| "data": { | |
| "text/plain": [ | |
| "Example({'food_description': 'For lunch, I have a serving of 57.5g stewed common seabream, 49g of boiled matabala, and 67.1g of rice with vegetables.', 'food_groups': ['meat and alternatives', 'vegetable', 'grain'], 'total_calories': 124.0, 'source': 'nutribench'}) (input_keys={'food_description'})" | |
| ] | |
| }, | |
| "execution_count": 5, | |
| "metadata": {}, | |
| "output_type": "execute_result" | |
| } | |
| ], | |
| "source": [ | |
| "trainset[0]" | |
| ] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "metadata": {}, | |
| "source": [ | |
| "# Current model " | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": 6, | |
| "metadata": {}, | |
| "outputs": [], | |
| "source": [ | |
| "class FoodItem(dspy.Signature):\n", | |
| " \"\"\"\n", | |
| " A food item.\n", | |
| "\n", | |
| " Notes:\n", | |
| " - The quantity is the amount of the food item in the user's description.\n", | |
| " - quantity multiplied by calories should result in a reasonable value!\n", | |
| "\n", | |
| " \"\"\"\n", | |
| "\n", | |
| " name: str = dspy.OutputField(description=\"The name of the food item\")\n", | |
| " quantity: float = dspy.OutputField(description=\"The quantity of the food item\")\n", | |
| " calories: float = dspy.OutputField(\n", | |
| " description=\"Calories in kilocalories (kcal) for a single unit of the food item\"\n", | |
| " )\n", | |
| " carbs: float = dspy.OutputField(\n", | |
| " description=\"Carbohydrates in grams for a single unit of the food item\"\n", | |
| " )\n", | |
| " fat: float = dspy.OutputField(\n", | |
| " description=\"Fat in grams for a single unit of the food item\"\n", | |
| " )\n", | |
| " protein: float = dspy.OutputField(\n", | |
| " description=\"Protein in grams for a single unit of the food item\"\n", | |
| " )\n", | |
| " fiber: float = dspy.OutputField(\n", | |
| " description=\"Fiber in grams for a single unit of the food item\"\n", | |
| " )\n", | |
| " food_groups: list[\n", | |
| " t.Literal[\"dairy\", \"meat and alternatives\", \"grain\", \"fruit\", \"vegetable\"]\n", | |
| " ] = dspy.OutputField(\n", | |
| " default_factory=list,\n", | |
| " description=\"The food groups to which the food item belongs\",\n", | |
| " )\n", | |
| "\n", | |
| "\n", | |
| "class NutritionAnalysis(dspy.Signature):\n", | |
| " \"\"\"Nutritional analysis\"\"\"\n", | |
| "\n", | |
| " food_description: str = dspy.InputField(\n", | |
| " description=\"The description of the food item\"\n", | |
| " )\n", | |
| "\n", | |
| " food_items: list[FoodItem] = dspy.OutputField(\n", | |
| " description=\"A list of food items with their nutritional information\"\n", | |
| " )\n", | |
| "\n", | |
| " def total_calories(self):\n", | |
| " return sum(item.calories * item.quantity for item in self.food_items)\n", | |
| "\n", | |
| " def total_food_groups(self):\n", | |
| " food_groups = []\n", | |
| " for item in self.food_items:\n", | |
| " food_groups.extend(item.food_groups)\n", | |
| "\n", | |
| " return len(list(set(food_groups)))\n", | |
| "\n", | |
| "\n", | |
| "module_vanilla = dspy.Predict(NutritionAnalysis)" | |
| ] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "metadata": {}, | |
| "source": [ | |
| "# Define metric" | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": 7, | |
| "metadata": {}, | |
| "outputs": [], | |
| "source": [ | |
| "def eval_metric(\n", | |
| " example, pred, trace=None, pred_name=None, pred_trace=None\n", | |
| ") -> dspy.Prediction:\n", | |
| "\n", | |
| " try:\n", | |
| " na = NutritionAnalysis(\n", | |
| " **pred.toDict(), food_description=example.food_description\n", | |
| " )\n", | |
| " except Exception as e:\n", | |
| " score = 0\n", | |
| " feedback_text = f\"INCORRECT: Your output format was incorrect: {e}\"\n", | |
| " return dspy.Prediction(score=score, feedback=feedback_text)\n", | |
| "\n", | |
| " total_calories_example = example.total_calories\n", | |
| " total_calories_predicted = na.total_calories()\n", | |
| "\n", | |
| " if total_calories_predicted > 5000:\n", | |
| " score = 0\n", | |
| " feedback_text = f\"INCORRECT: Total calories is too high (yours: {total_calories_predicted}, correct: {total_calories_example})\"\n", | |
| " return dspy.Prediction(score=score, feedback=feedback_text)\n", | |
| "\n", | |
| " within_threshold = abs(total_calories_example - total_calories_predicted) <= abs(\n", | |
| " 0.1 * total_calories_example\n", | |
| " )\n", | |
| "\n", | |
| " if not within_threshold:\n", | |
| " score = 0\n", | |
| " feedback_text = f\"INCORRECT: Your answer was not within 10% of the correct answer (yours: {total_calories_predicted}, correct: {total_calories_example})\"\n", | |
| " return dspy.Prediction(score=score, feedback=feedback_text)\n", | |
| "\n", | |
| " feedback_text = f\"CORRECT: Your answer was within 10% of the correct answer (yours: {total_calories_predicted}, correct: {total_calories_example})\"\n", | |
| " score = 1\n", | |
| "\n", | |
| " return dspy.Prediction(score=score, feedback=feedback_text)" | |
| ] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "metadata": {}, | |
| "source": [ | |
| "# Eval vanilla" | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": 8, | |
| "metadata": {}, | |
| "outputs": [ | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "Average Metric: 16.00 / 54 (29.6%): 100%|██████████| 54/54 [00:00<00:00, 370.10it/s]" | |
| ] | |
| }, | |
| { | |
| "name": "stderr", | |
| "output_type": "stream", | |
| "text": [ | |
| "2025/12/20 10:00:17 INFO dspy.evaluate.evaluate: Average Metric: 16.0 / 54 (29.6%)\n" | |
| ] | |
| }, | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "\n", | |
| "EvaluationResult(score=29.63, results=<list of 54 results>)\n" | |
| ] | |
| } | |
| ], | |
| "source": [ | |
| "gemini_2_5_flash = dspy.LM(\"openrouter/google/gemini-2.5-flash\", temperature=0.0)\n", | |
| "dspy.configure(lm=gemini_2_5_flash)\n", | |
| "\n", | |
| "evaluate = dspy.Evaluate(\n", | |
| " devset=testset,\n", | |
| " metric=eval_metric,\n", | |
| " num_threads=NUM_THREADS,\n", | |
| " display_progress=True,\n", | |
| " display_table=False,\n", | |
| " max_errors=5,\n", | |
| ")\n", | |
| "with dspy.context(lm=gemini_2_5_flash):\n", | |
| " score = evaluate(module_vanilla)\n", | |
| "er.add_result(\"vanilla_gemini_2.5_flash\", score.score)\n", | |
| "print(score)" | |
| ] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "metadata": {}, | |
| "source": [ | |
| "## Eval current" | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": 9, | |
| "metadata": {}, | |
| "outputs": [ | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "Average Metric: 24.00 / 54 (44.4%): 100%|██████████| 54/54 [00:00<00:00, 818.99it/s]" | |
| ] | |
| }, | |
| { | |
| "name": "stderr", | |
| "output_type": "stream", | |
| "text": [ | |
| "2025/12/20 10:00:17 INFO dspy.evaluate.evaluate: Average Metric: 24.0 / 54 (44.4%)\n" | |
| ] | |
| }, | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "\n", | |
| "EvaluationResult(score=44.44, results=<list of 54 results>)\n" | |
| ] | |
| } | |
| ], | |
| "source": [ | |
| "module_current = dspy.Predict(NutritionAnalysis)\n", | |
| "module_current.load(\"../optimized_prompts/bootstrap_fewshot_random_search.json\")\n", | |
| "\n", | |
| "with dspy.context(lm=gemini_2_5_flash):\n", | |
| " current_score = evaluate(module_current)\n", | |
| " er.add_result(\"optimized_gemini_2.5_flash\", current_score.score)\n", | |
| " print(current_score)" | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": 10, | |
| "metadata": {}, | |
| "outputs": [ | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "optimized_gemini_2.5_flash: 44.44\n", | |
| "vanilla_gemini_2.5_flash: 29.63\n" | |
| ] | |
| } | |
| ], | |
| "source": [ | |
| "er.print_results()" | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": 11, | |
| "metadata": {}, | |
| "outputs": [ | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "Going to sample between 1 and 4 traces per predictor.\n", | |
| "Will attempt to bootstrap 16 candidate sets.\n", | |
| "Average Metric: 11.00 / 53 (20.8%): 100%|██████████| 53/53 [00:00<00:00, 478.72it/s]" | |
| ] | |
| }, | |
| { | |
| "name": "stderr", | |
| "output_type": "stream", | |
| "text": [ | |
| "2025/12/20 10:00:17 INFO dspy.evaluate.evaluate: Average Metric: 11.0 / 53 (20.8%)\n" | |
| ] | |
| }, | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "\n", | |
| "New best score: 20.75 for seed -3\n", | |
| "Scores so far: [20.75]\n", | |
| "Best score so far: 20.75\n", | |
| "Average Metric: 11.00 / 53 (20.8%): 100%|██████████| 53/53 [00:00<00:00, 1184.38it/s]" | |
| ] | |
| }, | |
| { | |
| "name": "stderr", | |
| "output_type": "stream", | |
| "text": [ | |
| "2025/12/20 10:00:17 INFO dspy.evaluate.evaluate: Average Metric: 11.0 / 53 (20.8%)\n" | |
| ] | |
| }, | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "\n", | |
| "Scores so far: [20.75, 20.75]\n", | |
| "Best score so far: 20.75\n" | |
| ] | |
| }, | |
| { | |
| "name": "stderr", | |
| "output_type": "stream", | |
| "text": [ | |
| " 8%|▊ | 4/53 [00:00<00:00, 121.91it/s]" | |
| ] | |
| }, | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "Bootstrapped 4 full traces after 4 examples for up to 1 rounds, amounting to 4 attempts.\n" | |
| ] | |
| }, | |
| { | |
| "name": "stderr", | |
| "output_type": "stream", | |
| "text": [ | |
| "\n" | |
| ] | |
| }, | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "Average Metric: 11.00 / 53 (20.8%): 100%|██████████| 53/53 [00:00<00:00, 427.75it/s]" | |
| ] | |
| }, | |
| { | |
| "name": "stderr", | |
| "output_type": "stream", | |
| "text": [ | |
| "2025/12/20 10:00:17 INFO dspy.evaluate.evaluate: Average Metric: 11.0 / 53 (20.8%)\n" | |
| ] | |
| }, | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "\n", | |
| "Scores so far: [20.75, 20.75, 20.75]\n", | |
| "Best score so far: 20.75\n" | |
| ] | |
| }, | |
| { | |
| "name": "stderr", | |
| "output_type": "stream", | |
| "text": [ | |
| " 8%|▊ | 4/53 [00:00<00:00, 158.47it/s]" | |
| ] | |
| }, | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "Bootstrapped 4 full traces after 4 examples for up to 1 rounds, amounting to 4 attempts.\n" | |
| ] | |
| }, | |
| { | |
| "name": "stderr", | |
| "output_type": "stream", | |
| "text": [ | |
| "\n" | |
| ] | |
| }, | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "Average Metric: 11.00 / 53 (20.8%): 100%|██████████| 53/53 [00:00<00:00, 466.36it/s]" | |
| ] | |
| }, | |
| { | |
| "name": "stderr", | |
| "output_type": "stream", | |
| "text": [ | |
| "2025/12/20 10:00:17 INFO dspy.evaluate.evaluate: Average Metric: 11.0 / 53 (20.8%)\n" | |
| ] | |
| }, | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "\n", | |
| "Scores so far: [20.75, 20.75, 20.75, 20.75]\n", | |
| "Best score so far: 20.75\n" | |
| ] | |
| }, | |
| { | |
| "name": "stderr", | |
| "output_type": "stream", | |
| "text": [ | |
| " 4%|▍ | 2/53 [00:00<00:00, 138.59it/s]" | |
| ] | |
| }, | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "Bootstrapped 2 full traces after 2 examples for up to 1 rounds, amounting to 2 attempts.\n" | |
| ] | |
| }, | |
| { | |
| "name": "stderr", | |
| "output_type": "stream", | |
| "text": [ | |
| "\n" | |
| ] | |
| }, | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "Average Metric: 12.00 / 53 (22.6%): 100%|██████████| 53/53 [00:00<00:00, 472.61it/s]" | |
| ] | |
| }, | |
| { | |
| "name": "stderr", | |
| "output_type": "stream", | |
| "text": [ | |
| "2025/12/20 10:00:17 INFO dspy.evaluate.evaluate: Average Metric: 12.0 / 53 (22.6%)\n" | |
| ] | |
| }, | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "\n", | |
| "New best score: 22.64 for seed 1\n", | |
| "Scores so far: [20.75, 20.75, 20.75, 20.75, 22.64]\n", | |
| "Best score so far: 22.64\n" | |
| ] | |
| }, | |
| { | |
| "name": "stderr", | |
| "output_type": "stream", | |
| "text": [ | |
| " 2%|▏ | 1/53 [00:00<00:00, 85.38it/s]" | |
| ] | |
| }, | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "Bootstrapped 1 full traces after 1 examples for up to 1 rounds, amounting to 1 attempts.\n" | |
| ] | |
| }, | |
| { | |
| "name": "stderr", | |
| "output_type": "stream", | |
| "text": [ | |
| "\n" | |
| ] | |
| }, | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "Average Metric: 11.00 / 53 (20.8%): 100%|██████████| 53/53 [00:00<00:00, 470.15it/s]" | |
| ] | |
| }, | |
| { | |
| "name": "stderr", | |
| "output_type": "stream", | |
| "text": [ | |
| "2025/12/20 10:00:18 INFO dspy.evaluate.evaluate: Average Metric: 11.0 / 53 (20.8%)\n" | |
| ] | |
| }, | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "\n", | |
| "Scores so far: [20.75, 20.75, 20.75, 20.75, 22.64, 20.75]\n", | |
| "Best score so far: 22.64\n" | |
| ] | |
| }, | |
| { | |
| "name": "stderr", | |
| "output_type": "stream", | |
| "text": [ | |
| " 4%|▍ | 2/53 [00:00<00:00, 130.88it/s]" | |
| ] | |
| }, | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "Bootstrapped 2 full traces after 2 examples for up to 1 rounds, amounting to 2 attempts.\n" | |
| ] | |
| }, | |
| { | |
| "name": "stderr", | |
| "output_type": "stream", | |
| "text": [ | |
| "\n" | |
| ] | |
| }, | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "Average Metric: 13.00 / 53 (24.5%): 100%|██████████| 53/53 [00:00<00:00, 530.43it/s]" | |
| ] | |
| }, | |
| { | |
| "name": "stderr", | |
| "output_type": "stream", | |
| "text": [ | |
| "2025/12/20 10:00:18 INFO dspy.evaluate.evaluate: Average Metric: 13.0 / 53 (24.5%)\n" | |
| ] | |
| }, | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "\n", | |
| "New best score: 24.53 for seed 3\n", | |
| "Scores so far: [20.75, 20.75, 20.75, 20.75, 22.64, 20.75, 24.53]\n", | |
| "Best score so far: 24.53\n" | |
| ] | |
| }, | |
| { | |
| "name": "stderr", | |
| "output_type": "stream", | |
| "text": [ | |
| " 4%|▍ | 2/53 [00:00<00:00, 141.61it/s]" | |
| ] | |
| }, | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "Bootstrapped 2 full traces after 2 examples for up to 1 rounds, amounting to 2 attempts.\n" | |
| ] | |
| }, | |
| { | |
| "name": "stderr", | |
| "output_type": "stream", | |
| "text": [ | |
| "\n" | |
| ] | |
| }, | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "Average Metric: 16.00 / 53 (30.2%): 100%|██████████| 53/53 [00:00<00:00, 486.23it/s]" | |
| ] | |
| }, | |
| { | |
| "name": "stderr", | |
| "output_type": "stream", | |
| "text": [ | |
| "2025/12/20 10:00:18 INFO dspy.evaluate.evaluate: Average Metric: 16.0 / 53 (30.2%)\n" | |
| ] | |
| }, | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "\n", | |
| "New best score: 30.19 for seed 4\n", | |
| "Scores so far: [20.75, 20.75, 20.75, 20.75, 22.64, 20.75, 24.53, 30.19]\n", | |
| "Best score so far: 30.19\n" | |
| ] | |
| }, | |
| { | |
| "name": "stderr", | |
| "output_type": "stream", | |
| "text": [ | |
| " 6%|▌ | 3/53 [00:00<00:00, 159.05it/s]" | |
| ] | |
| }, | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "Bootstrapped 3 full traces after 3 examples for up to 1 rounds, amounting to 3 attempts.\n" | |
| ] | |
| }, | |
| { | |
| "name": "stderr", | |
| "output_type": "stream", | |
| "text": [ | |
| "\n" | |
| ] | |
| }, | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "Average Metric: 12.00 / 53 (22.6%): 100%|██████████| 53/53 [00:00<00:00, 469.03it/s]" | |
| ] | |
| }, | |
| { | |
| "name": "stderr", | |
| "output_type": "stream", | |
| "text": [ | |
| "2025/12/20 10:00:18 INFO dspy.evaluate.evaluate: Average Metric: 12.0 / 53 (22.6%)\n" | |
| ] | |
| }, | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "\n", | |
| "Scores so far: [20.75, 20.75, 20.75, 20.75, 22.64, 20.75, 24.53, 30.19, 22.64]\n", | |
| "Best score so far: 30.19\n" | |
| ] | |
| }, | |
| { | |
| "name": "stderr", | |
| "output_type": "stream", | |
| "text": [ | |
| " 2%|▏ | 1/53 [00:00<00:00, 117.38it/s]" | |
| ] | |
| }, | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "Bootstrapped 1 full traces after 1 examples for up to 1 rounds, amounting to 1 attempts.\n" | |
| ] | |
| }, | |
| { | |
| "name": "stderr", | |
| "output_type": "stream", | |
| "text": [ | |
| "\n" | |
| ] | |
| }, | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "Average Metric: 11.00 / 53 (20.8%): 100%|██████████| 53/53 [00:00<00:00, 467.17it/s]" | |
| ] | |
| }, | |
| { | |
| "name": "stderr", | |
| "output_type": "stream", | |
| "text": [ | |
| "2025/12/20 10:00:18 INFO dspy.evaluate.evaluate: Average Metric: 11.0 / 53 (20.8%)\n" | |
| ] | |
| }, | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "\n", | |
| "Scores so far: [20.75, 20.75, 20.75, 20.75, 22.64, 20.75, 24.53, 30.19, 22.64, 20.75]\n", | |
| "Best score so far: 30.19\n" | |
| ] | |
| }, | |
| { | |
| "name": "stderr", | |
| "output_type": "stream", | |
| "text": [ | |
| " 6%|▌ | 3/53 [00:00<00:00, 146.51it/s]" | |
| ] | |
| }, | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "Bootstrapped 3 full traces after 3 examples for up to 1 rounds, amounting to 3 attempts.\n" | |
| ] | |
| }, | |
| { | |
| "name": "stderr", | |
| "output_type": "stream", | |
| "text": [ | |
| "\n" | |
| ] | |
| }, | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "Average Metric: 8.00 / 53 (15.1%): 100%|██████████| 53/53 [00:00<00:00, 438.31it/s]" | |
| ] | |
| }, | |
| { | |
| "name": "stderr", | |
| "output_type": "stream", | |
| "text": [ | |
| "2025/12/20 10:00:18 INFO dspy.evaluate.evaluate: Average Metric: 8.0 / 53 (15.1%)\n" | |
| ] | |
| }, | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "\n", | |
| "Scores so far: [20.75, 20.75, 20.75, 20.75, 22.64, 20.75, 24.53, 30.19, 22.64, 20.75, 15.09]\n", | |
| "Best score so far: 30.19\n" | |
| ] | |
| }, | |
| { | |
| "name": "stderr", | |
| "output_type": "stream", | |
| "text": [ | |
| " 4%|▍ | 2/53 [00:00<00:00, 109.83it/s]" | |
| ] | |
| }, | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "Bootstrapped 2 full traces after 2 examples for up to 1 rounds, amounting to 2 attempts.\n" | |
| ] | |
| }, | |
| { | |
| "name": "stderr", | |
| "output_type": "stream", | |
| "text": [ | |
| "\n" | |
| ] | |
| }, | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "Average Metric: 14.00 / 53 (26.4%): 100%|██████████| 53/53 [00:00<00:00, 475.17it/s]" | |
| ] | |
| }, | |
| { | |
| "name": "stderr", | |
| "output_type": "stream", | |
| "text": [ | |
| "2025/12/20 10:00:19 INFO dspy.evaluate.evaluate: Average Metric: 14.0 / 53 (26.4%)\n" | |
| ] | |
| }, | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "\n", | |
| "Scores so far: [20.75, 20.75, 20.75, 20.75, 22.64, 20.75, 24.53, 30.19, 22.64, 20.75, 15.09, 26.42]\n", | |
| "Best score so far: 30.19\n" | |
| ] | |
| }, | |
| { | |
| "name": "stderr", | |
| "output_type": "stream", | |
| "text": [ | |
| " 8%|▊ | 4/53 [00:00<00:00, 131.02it/s]" | |
| ] | |
| }, | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "Bootstrapped 4 full traces after 4 examples for up to 1 rounds, amounting to 4 attempts.\n" | |
| ] | |
| }, | |
| { | |
| "name": "stderr", | |
| "output_type": "stream", | |
| "text": [ | |
| "\n" | |
| ] | |
| }, | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "Average Metric: 10.00 / 53 (18.9%): 100%|██████████| 53/53 [00:00<00:00, 402.91it/s]" | |
| ] | |
| }, | |
| { | |
| "name": "stderr", | |
| "output_type": "stream", | |
| "text": [ | |
| "2025/12/20 10:00:19 INFO dspy.evaluate.evaluate: Average Metric: 10.0 / 53 (18.9%)\n" | |
| ] | |
| }, | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "\n", | |
| "Scores so far: [20.75, 20.75, 20.75, 20.75, 22.64, 20.75, 24.53, 30.19, 22.64, 20.75, 15.09, 26.42, 18.87]\n", | |
| "Best score so far: 30.19\n" | |
| ] | |
| }, | |
| { | |
| "name": "stderr", | |
| "output_type": "stream", | |
| "text": [ | |
| " 2%|▏ | 1/53 [00:00<00:00, 117.17it/s]" | |
| ] | |
| }, | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "Bootstrapped 1 full traces after 1 examples for up to 1 rounds, amounting to 1 attempts.\n" | |
| ] | |
| }, | |
| { | |
| "name": "stderr", | |
| "output_type": "stream", | |
| "text": [ | |
| "\n" | |
| ] | |
| }, | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "Average Metric: 13.00 / 53 (24.5%): 100%|██████████| 53/53 [00:00<00:00, 283.57it/s]" | |
| ] | |
| }, | |
| { | |
| "name": "stderr", | |
| "output_type": "stream", | |
| "text": [ | |
| "2025/12/20 10:00:19 INFO dspy.evaluate.evaluate: Average Metric: 13.0 / 53 (24.5%)\n" | |
| ] | |
| }, | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "\n", | |
| "Scores so far: [20.75, 20.75, 20.75, 20.75, 22.64, 20.75, 24.53, 30.19, 22.64, 20.75, 15.09, 26.42, 18.87, 24.53]\n", | |
| "Best score so far: 30.19\n" | |
| ] | |
| }, | |
| { | |
| "name": "stderr", | |
| "output_type": "stream", | |
| "text": [ | |
| " 8%|▊ | 4/53 [00:00<00:00, 161.78it/s]" | |
| ] | |
| }, | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "Bootstrapped 4 full traces after 4 examples for up to 1 rounds, amounting to 4 attempts.\n" | |
| ] | |
| }, | |
| { | |
| "name": "stderr", | |
| "output_type": "stream", | |
| "text": [ | |
| "\n" | |
| ] | |
| }, | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "Average Metric: 15.00 / 53 (28.3%): 100%|██████████| 53/53 [00:00<00:00, 437.02it/s]" | |
| ] | |
| }, | |
| { | |
| "name": "stderr", | |
| "output_type": "stream", | |
| "text": [ | |
| "2025/12/20 10:00:19 INFO dspy.evaluate.evaluate: Average Metric: 15.0 / 53 (28.3%)\n" | |
| ] | |
| }, | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "\n", | |
| "Scores so far: [20.75, 20.75, 20.75, 20.75, 22.64, 20.75, 24.53, 30.19, 22.64, 20.75, 15.09, 26.42, 18.87, 24.53, 28.3]\n", | |
| "Best score so far: 30.19\n" | |
| ] | |
| }, | |
| { | |
| "name": "stderr", | |
| "output_type": "stream", | |
| "text": [ | |
| " 8%|▊ | 4/53 [00:00<00:00, 147.53it/s]\n" | |
| ] | |
| }, | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "Bootstrapped 4 full traces after 4 examples for up to 1 rounds, amounting to 4 attempts.\n", | |
| "Average Metric: 9.00 / 53 (17.0%): 100%|██████████| 53/53 [00:00<00:00, 413.58it/s]" | |
| ] | |
| }, | |
| { | |
| "name": "stderr", | |
| "output_type": "stream", | |
| "text": [ | |
| "2025/12/20 10:00:19 INFO dspy.evaluate.evaluate: Average Metric: 9.0 / 53 (17.0%)\n" | |
| ] | |
| }, | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "\n", | |
| "Scores so far: [20.75, 20.75, 20.75, 20.75, 22.64, 20.75, 24.53, 30.19, 22.64, 20.75, 15.09, 26.42, 18.87, 24.53, 28.3, 16.98]\n", | |
| "Best score so far: 30.19\n" | |
| ] | |
| }, | |
| { | |
| "name": "stderr", | |
| "output_type": "stream", | |
| "text": [ | |
| " 6%|▌ | 3/53 [00:00<00:00, 106.80it/s]" | |
| ] | |
| }, | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "Bootstrapped 3 full traces after 3 examples for up to 1 rounds, amounting to 3 attempts.\n" | |
| ] | |
| }, | |
| { | |
| "name": "stderr", | |
| "output_type": "stream", | |
| "text": [ | |
| "\n" | |
| ] | |
| }, | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "Average Metric: 11.00 / 53 (20.8%): 100%|██████████| 53/53 [00:00<00:00, 441.03it/s]" | |
| ] | |
| }, | |
| { | |
| "name": "stderr", | |
| "output_type": "stream", | |
| "text": [ | |
| "2025/12/20 10:00:19 INFO dspy.evaluate.evaluate: Average Metric: 11.0 / 53 (20.8%)\n" | |
| ] | |
| }, | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "\n", | |
| "Scores so far: [20.75, 20.75, 20.75, 20.75, 22.64, 20.75, 24.53, 30.19, 22.64, 20.75, 15.09, 26.42, 18.87, 24.53, 28.3, 16.98, 20.75]\n", | |
| "Best score so far: 30.19\n" | |
| ] | |
| }, | |
| { | |
| "name": "stderr", | |
| "output_type": "stream", | |
| "text": [ | |
| " 2%|▏ | 1/53 [00:00<00:00, 117.60it/s]" | |
| ] | |
| }, | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "Bootstrapped 1 full traces after 1 examples for up to 1 rounds, amounting to 1 attempts.\n" | |
| ] | |
| }, | |
| { | |
| "name": "stderr", | |
| "output_type": "stream", | |
| "text": [ | |
| "\n" | |
| ] | |
| }, | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "Average Metric: 11.00 / 53 (20.8%): 100%|██████████| 53/53 [00:00<00:00, 4034.01it/s]" | |
| ] | |
| }, | |
| { | |
| "name": "stderr", | |
| "output_type": "stream", | |
| "text": [ | |
| "2025/12/20 10:00:19 INFO dspy.evaluate.evaluate: Average Metric: 11.0 / 53 (20.8%)\n" | |
| ] | |
| }, | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "\n", | |
| "Scores so far: [20.75, 20.75, 20.75, 20.75, 22.64, 20.75, 24.53, 30.19, 22.64, 20.75, 15.09, 26.42, 18.87, 24.53, 28.3, 16.98, 20.75, 20.75]\n", | |
| "Best score so far: 30.19\n" | |
| ] | |
| }, | |
| { | |
| "name": "stderr", | |
| "output_type": "stream", | |
| "text": [ | |
| " 4%|▍ | 2/53 [00:00<00:00, 137.13it/s]" | |
| ] | |
| }, | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "Bootstrapped 2 full traces after 2 examples for up to 1 rounds, amounting to 2 attempts.\n" | |
| ] | |
| }, | |
| { | |
| "name": "stderr", | |
| "output_type": "stream", | |
| "text": [ | |
| "\n" | |
| ] | |
| }, | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "Average Metric: 10.00 / 53 (18.9%): 100%|██████████| 53/53 [00:00<00:00, 451.68it/s]" | |
| ] | |
| }, | |
| { | |
| "name": "stderr", | |
| "output_type": "stream", | |
| "text": [ | |
| "2025/12/20 10:00:20 INFO dspy.evaluate.evaluate: Average Metric: 10.0 / 53 (18.9%)\n" | |
| ] | |
| }, | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "\n", | |
| "Scores so far: [20.75, 20.75, 20.75, 20.75, 22.64, 20.75, 24.53, 30.19, 22.64, 20.75, 15.09, 26.42, 18.87, 24.53, 28.3, 16.98, 20.75, 20.75, 18.87]\n", | |
| "Best score so far: 30.19\n", | |
| "19 candidate programs found.\n", | |
| "Average Metric: 17.00 / 54 (31.5%): 100%|██████████| 54/54 [00:00<00:00, 609.53it/s]" | |
| ] | |
| }, | |
| { | |
| "name": "stderr", | |
| "output_type": "stream", | |
| "text": [ | |
| "2025/12/20 10:00:20 INFO dspy.evaluate.evaluate: Average Metric: 17.0 / 54 (31.5%)\n" | |
| ] | |
| }, | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "\n", | |
| "EvaluationResult(score=31.48, results=<list of 54 results>)\n" | |
| ] | |
| } | |
| ], | |
| "source": [ | |
| "optimizer = dspy.BootstrapFewShotWithRandomSearch(metric=eval_metric)\n", | |
| "\n", | |
| "len_trainset = len(trainset)\n", | |
| "module_bootstrap_fewshot_random_search = optimizer.compile(\n", | |
| " module_vanilla,\n", | |
| " trainset=trainset,\n", | |
| ")\n", | |
| "optimizer_program_score = evaluate(module_bootstrap_fewshot_random_search)\n", | |
| "er.add_result(\n", | |
| " \"bootstrap_fewshot_random_search_gemini_2.5_flash\", optimizer_program_score.score\n", | |
| ")\n", | |
| "print(optimizer_program_score)" | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": 12, | |
| "metadata": {}, | |
| "outputs": [ | |
| { | |
| "name": "stderr", | |
| "output_type": "stream", | |
| "text": [ | |
| "2025/12/20 10:00:20 INFO dspy.teleprompt.mipro_optimizer_v2: \n", | |
| "RUNNING WITH THE FOLLOWING LIGHT AUTO RUN SETTINGS:\n", | |
| "num_trials: 10\n", | |
| "minibatch: False\n", | |
| "num_fewshot_candidates: 6\n", | |
| "num_instruct_candidates: 3\n", | |
| "valset size: 42\n", | |
| "\n", | |
| "2025/12/20 10:00:20 INFO dspy.teleprompt.mipro_optimizer_v2: \n", | |
| "==> STEP 1: BOOTSTRAP FEWSHOT EXAMPLES <==\n", | |
| "2025/12/20 10:00:20 INFO dspy.teleprompt.mipro_optimizer_v2: These will be used as few-shot example candidates for our program and for creating instructions.\n", | |
| "\n", | |
| "2025/12/20 10:00:20 INFO dspy.teleprompt.mipro_optimizer_v2: Bootstrapping N=6 sets of demonstrations...\n" | |
| ] | |
| }, | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "Bootstrapping set 1/6\n", | |
| "Bootstrapping set 2/6\n", | |
| "Bootstrapping set 3/6\n" | |
| ] | |
| }, | |
| { | |
| "name": "stderr", | |
| "output_type": "stream", | |
| "text": [ | |
| " 36%|███▋ | 4/11 [00:00<00:00, 116.83it/s]\n" | |
| ] | |
| }, | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "Bootstrapped 4 full traces after 4 examples for up to 1 rounds, amounting to 4 attempts.\n", | |
| "Bootstrapping set 4/6\n" | |
| ] | |
| }, | |
| { | |
| "name": "stderr", | |
| "output_type": "stream", | |
| "text": [ | |
| " 18%|█▊ | 2/11 [00:00<00:00, 169.33it/s]\n" | |
| ] | |
| }, | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "Bootstrapped 2 full traces after 2 examples for up to 1 rounds, amounting to 2 attempts.\n", | |
| "Bootstrapping set 5/6\n" | |
| ] | |
| }, | |
| { | |
| "name": "stderr", | |
| "output_type": "stream", | |
| "text": [ | |
| " 9%|▉ | 1/11 [00:00<00:00, 153.89it/s]\n" | |
| ] | |
| }, | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "Bootstrapped 1 full traces after 1 examples for up to 1 rounds, amounting to 1 attempts.\n", | |
| "Bootstrapping set 6/6\n" | |
| ] | |
| }, | |
| { | |
| "name": "stderr", | |
| "output_type": "stream", | |
| "text": [ | |
| " 36%|███▋ | 4/11 [00:00<00:00, 169.04it/s]\n", | |
| "2025/12/20 10:00:20 INFO dspy.teleprompt.mipro_optimizer_v2: \n", | |
| "==> STEP 2: PROPOSE INSTRUCTION CANDIDATES <==\n", | |
| "2025/12/20 10:00:20 INFO dspy.teleprompt.mipro_optimizer_v2: We will use the few-shot examples from the previous step, a generated dataset summary, a summary of the program code, and a randomly selected prompting tip to propose instructions.\n" | |
| ] | |
| }, | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "Bootstrapped 4 full traces after 4 examples for up to 1 rounds, amounting to 4 attempts.\n" | |
| ] | |
| }, | |
| { | |
| "name": "stderr", | |
| "output_type": "stream", | |
| "text": [ | |
| "2025/12/20 10:00:20 INFO dspy.teleprompt.mipro_optimizer_v2: \n", | |
| "Proposing N=3 instructions...\n", | |
| "\n", | |
| "2025/12/20 10:00:20 INFO dspy.teleprompt.mipro_optimizer_v2: Proposed Instructions for Predictor 0:\n", | |
| "\n", | |
| "2025/12/20 10:00:20 INFO dspy.teleprompt.mipro_optimizer_v2: 0: Nutritional analysis\n", | |
| "\n", | |
| "2025/12/20 10:00:20 INFO dspy.teleprompt.mipro_optimizer_v2: 1: Given a natural language `food_description`, analyze the text to identify all individual food items and their respective quantities. For each identified food item, extract or infer its detailed nutritional information, including the name of the food, its quantity, total calories, carbohydrates, fat, protein, fiber, and associated food_groups (e.g., 'dairy', 'fruit', 'vegetable', 'meat and alternatives', 'grain'). Return a list of `FoodItem` objects, with each object populated with these nutritional details.\n", | |
| "\n", | |
| "2025/12/20 10:00:20 INFO dspy.teleprompt.mipro_optimizer_v2: 2: From the provided free-text food description, identify and extract each distinct food item and its corresponding quantity. For each identified food item, determine its name, the amount consumed (quantity), estimated total calories, carbohydrates, fat, protein, fiber, and categorize it into relevant food groups. Present this information as a list of Python `FoodItem` objects, ensuring all nutritional values are numerical and food groups are listed as strings.\n", | |
| "\n", | |
| "2025/12/20 10:00:20 INFO dspy.teleprompt.mipro_optimizer_v2: \n", | |
| "\n", | |
| "2025/12/20 10:00:20 INFO dspy.teleprompt.mipro_optimizer_v2: ==> STEP 3: FINDING OPTIMAL PROMPT PARAMETERS <==\n", | |
| "2025/12/20 10:00:20 INFO dspy.teleprompt.mipro_optimizer_v2: We will evaluate the program over a series of trials with different combinations of instructions and few-shot examples to find the optimal combination using Bayesian Optimization.\n", | |
| "\n", | |
| "2025/12/20 10:00:20 INFO dspy.teleprompt.mipro_optimizer_v2: == Trial 1 / 10 - Full Evaluation of Default Program ==\n" | |
| ] | |
| }, | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "Average Metric: 10.00 / 42 (23.8%): 100%|██████████| 42/42 [00:00<00:00, 4315.13it/s]" | |
| ] | |
| }, | |
| { | |
| "name": "stderr", | |
| "output_type": "stream", | |
| "text": [ | |
| "2025/12/20 10:00:20 INFO dspy.evaluate.evaluate: Average Metric: 10.0 / 42 (23.8%)\n", | |
| "2025/12/20 10:00:20 INFO dspy.teleprompt.mipro_optimizer_v2: Default program score: 23.81\n", | |
| "\n", | |
| "/Users/duarteocarmo/Repos/taralli-api/.env/lib/python3.12/site-packages/optuna/_experimental.py:31: ExperimentalWarning: Argument ``multivariate`` is an experimental feature. The interface can change in the future.\n", | |
| " warnings.warn(\n", | |
| "2025/12/20 10:00:20 INFO dspy.teleprompt.mipro_optimizer_v2: ===== Trial 2 / 10 =====\n" | |
| ] | |
| }, | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "\n", | |
| "Average Metric: 13.00 / 42 (31.0%): 100%|██████████| 42/42 [00:00<00:00, 723.14it/s]" | |
| ] | |
| }, | |
| { | |
| "name": "stderr", | |
| "output_type": "stream", | |
| "text": [ | |
| "2025/12/20 10:00:20 INFO dspy.evaluate.evaluate: Average Metric: 13.0 / 42 (31.0%)\n", | |
| "2025/12/20 10:00:20 INFO dspy.teleprompt.mipro_optimizer_v2: \u001b[92mBest full score so far!\u001b[0m Score: 30.95\n", | |
| "2025/12/20 10:00:20 INFO dspy.teleprompt.mipro_optimizer_v2: Score: 30.95 with parameters ['Predictor 0: Instruction 1', 'Predictor 0: Few-Shot Set 3'].\n", | |
| "2025/12/20 10:00:20 INFO dspy.teleprompt.mipro_optimizer_v2: Scores so far: [23.81, 30.95]\n", | |
| "2025/12/20 10:00:20 INFO dspy.teleprompt.mipro_optimizer_v2: Best score so far: 30.95\n", | |
| "2025/12/20 10:00:20 INFO dspy.teleprompt.mipro_optimizer_v2: ========================\n", | |
| "\n", | |
| "\n", | |
| "2025/12/20 10:00:20 INFO dspy.teleprompt.mipro_optimizer_v2: ===== Trial 3 / 10 =====\n" | |
| ] | |
| }, | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "\n", | |
| "Average Metric: 10.00 / 42 (23.8%): 100%|██████████| 42/42 [00:00<00:00, 716.97it/s]" | |
| ] | |
| }, | |
| { | |
| "name": "stderr", | |
| "output_type": "stream", | |
| "text": [ | |
| "2025/12/20 10:00:20 INFO dspy.evaluate.evaluate: Average Metric: 10.0 / 42 (23.8%)\n", | |
| "2025/12/20 10:00:20 INFO dspy.teleprompt.mipro_optimizer_v2: Score: 23.81 with parameters ['Predictor 0: Instruction 2', 'Predictor 0: Few-Shot Set 0'].\n", | |
| "2025/12/20 10:00:20 INFO dspy.teleprompt.mipro_optimizer_v2: Scores so far: [23.81, 30.95, 23.81]\n", | |
| "2025/12/20 10:00:20 INFO dspy.teleprompt.mipro_optimizer_v2: Best score so far: 30.95\n", | |
| "2025/12/20 10:00:20 INFO dspy.teleprompt.mipro_optimizer_v2: ========================\n", | |
| "\n", | |
| "\n", | |
| "2025/12/20 10:00:20 INFO dspy.teleprompt.mipro_optimizer_v2: ===== Trial 4 / 10 =====\n" | |
| ] | |
| }, | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "\n", | |
| "Average Metric: 12.00 / 42 (28.6%): 100%|██████████| 42/42 [00:00<00:00, 683.91it/s]" | |
| ] | |
| }, | |
| { | |
| "name": "stderr", | |
| "output_type": "stream", | |
| "text": [ | |
| "2025/12/20 10:00:21 INFO dspy.evaluate.evaluate: Average Metric: 12.0 / 42 (28.6%)\n", | |
| "2025/12/20 10:00:21 INFO dspy.teleprompt.mipro_optimizer_v2: Score: 28.57 with parameters ['Predictor 0: Instruction 1', 'Predictor 0: Few-Shot Set 5'].\n", | |
| "2025/12/20 10:00:21 INFO dspy.teleprompt.mipro_optimizer_v2: Scores so far: [23.81, 30.95, 23.81, 28.57]\n", | |
| "2025/12/20 10:00:21 INFO dspy.teleprompt.mipro_optimizer_v2: Best score so far: 30.95\n", | |
| "2025/12/20 10:00:21 INFO dspy.teleprompt.mipro_optimizer_v2: ========================\n", | |
| "\n", | |
| "\n", | |
| "2025/12/20 10:00:21 INFO dspy.teleprompt.mipro_optimizer_v2: ===== Trial 5 / 10 =====\n" | |
| ] | |
| }, | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "\n", | |
| "Average Metric: 9.00 / 42 (21.4%): 100%|██████████| 42/42 [00:00<00:00, 653.72it/s]" | |
| ] | |
| }, | |
| { | |
| "name": "stderr", | |
| "output_type": "stream", | |
| "text": [ | |
| "2025/12/20 10:00:21 INFO dspy.evaluate.evaluate: Average Metric: 9.0 / 42 (21.4%)\n", | |
| "2025/12/20 10:00:21 INFO dspy.teleprompt.mipro_optimizer_v2: Score: 21.43 with parameters ['Predictor 0: Instruction 2', 'Predictor 0: Few-Shot Set 2'].\n", | |
| "2025/12/20 10:00:21 INFO dspy.teleprompt.mipro_optimizer_v2: Scores so far: [23.81, 30.95, 23.81, 28.57, 21.43]\n", | |
| "2025/12/20 10:00:21 INFO dspy.teleprompt.mipro_optimizer_v2: Best score so far: 30.95\n", | |
| "2025/12/20 10:00:21 INFO dspy.teleprompt.mipro_optimizer_v2: ========================\n", | |
| "\n", | |
| "\n", | |
| "2025/12/20 10:00:21 INFO dspy.teleprompt.mipro_optimizer_v2: ===== Trial 6 / 10 =====\n" | |
| ] | |
| }, | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "\n", | |
| "Average Metric: 14.00 / 42 (33.3%): 100%|██████████| 42/42 [00:00<00:00, 691.87it/s]" | |
| ] | |
| }, | |
| { | |
| "name": "stderr", | |
| "output_type": "stream", | |
| "text": [ | |
| "2025/12/20 10:00:21 INFO dspy.evaluate.evaluate: Average Metric: 14.0 / 42 (33.3%)\n", | |
| "2025/12/20 10:00:21 INFO dspy.teleprompt.mipro_optimizer_v2: \u001b[92mBest full score so far!\u001b[0m Score: 33.33\n", | |
| "2025/12/20 10:00:21 INFO dspy.teleprompt.mipro_optimizer_v2: Score: 33.33 with parameters ['Predictor 0: Instruction 0', 'Predictor 0: Few-Shot Set 5'].\n", | |
| "2025/12/20 10:00:21 INFO dspy.teleprompt.mipro_optimizer_v2: Scores so far: [23.81, 30.95, 23.81, 28.57, 21.43, 33.33]\n", | |
| "2025/12/20 10:00:21 INFO dspy.teleprompt.mipro_optimizer_v2: Best score so far: 33.33\n", | |
| "2025/12/20 10:00:21 INFO dspy.teleprompt.mipro_optimizer_v2: ========================\n", | |
| "\n", | |
| "\n", | |
| "2025/12/20 10:00:21 INFO dspy.teleprompt.mipro_optimizer_v2: ===== Trial 7 / 10 =====\n" | |
| ] | |
| }, | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "\n", | |
| "Average Metric: 10.00 / 42 (23.8%): 100%|██████████| 42/42 [00:00<00:00, 4105.93it/s]" | |
| ] | |
| }, | |
| { | |
| "name": "stderr", | |
| "output_type": "stream", | |
| "text": [ | |
| "2025/12/20 10:00:21 INFO dspy.evaluate.evaluate: Average Metric: 10.0 / 42 (23.8%)\n", | |
| "2025/12/20 10:00:21 INFO dspy.teleprompt.mipro_optimizer_v2: Score: 23.81 with parameters ['Predictor 0: Instruction 2', 'Predictor 0: Few-Shot Set 0'].\n", | |
| "2025/12/20 10:00:21 INFO dspy.teleprompt.mipro_optimizer_v2: Scores so far: [23.81, 30.95, 23.81, 28.57, 21.43, 33.33, 23.81]\n", | |
| "2025/12/20 10:00:21 INFO dspy.teleprompt.mipro_optimizer_v2: Best score so far: 33.33\n", | |
| "2025/12/20 10:00:21 INFO dspy.teleprompt.mipro_optimizer_v2: ========================\n", | |
| "\n", | |
| "\n" | |
| ] | |
| }, | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "\n" | |
| ] | |
| }, | |
| { | |
| "name": "stderr", | |
| "output_type": "stream", | |
| "text": [ | |
| "2025/12/20 10:00:21 INFO dspy.teleprompt.mipro_optimizer_v2: ===== Trial 8 / 10 =====\n" | |
| ] | |
| }, | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "Average Metric: 16.00 / 42 (38.1%): 100%|██████████| 42/42 [00:00<00:00, 723.47it/s]" | |
| ] | |
| }, | |
| { | |
| "name": "stderr", | |
| "output_type": "stream", | |
| "text": [ | |
| "2025/12/20 10:00:21 INFO dspy.evaluate.evaluate: Average Metric: 16.0 / 42 (38.1%)\n", | |
| "2025/12/20 10:00:21 INFO dspy.teleprompt.mipro_optimizer_v2: \u001b[92mBest full score so far!\u001b[0m Score: 38.1\n", | |
| "2025/12/20 10:00:21 INFO dspy.teleprompt.mipro_optimizer_v2: Score: 38.1 with parameters ['Predictor 0: Instruction 2', 'Predictor 0: Few-Shot Set 5'].\n", | |
| "2025/12/20 10:00:21 INFO dspy.teleprompt.mipro_optimizer_v2: Scores so far: [23.81, 30.95, 23.81, 28.57, 21.43, 33.33, 23.81, 38.1]\n", | |
| "2025/12/20 10:00:21 INFO dspy.teleprompt.mipro_optimizer_v2: Best score so far: 38.1\n", | |
| "2025/12/20 10:00:21 INFO dspy.teleprompt.mipro_optimizer_v2: ========================\n", | |
| "\n", | |
| "\n", | |
| "2025/12/20 10:00:21 INFO dspy.teleprompt.mipro_optimizer_v2: ===== Trial 9 / 10 =====\n" | |
| ] | |
| }, | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "\n", | |
| "Average Metric: 10.00 / 42 (23.8%): 100%|██████████| 42/42 [00:00<00:00, 687.62it/s]" | |
| ] | |
| }, | |
| { | |
| "name": "stderr", | |
| "output_type": "stream", | |
| "text": [ | |
| "2025/12/20 10:00:21 INFO dspy.evaluate.evaluate: Average Metric: 10.0 / 42 (23.8%)\n", | |
| "2025/12/20 10:00:21 INFO dspy.teleprompt.mipro_optimizer_v2: Score: 23.81 with parameters ['Predictor 0: Instruction 1', 'Predictor 0: Few-Shot Set 4'].\n", | |
| "2025/12/20 10:00:21 INFO dspy.teleprompt.mipro_optimizer_v2: Scores so far: [23.81, 30.95, 23.81, 28.57, 21.43, 33.33, 23.81, 38.1, 23.81]\n", | |
| "2025/12/20 10:00:21 INFO dspy.teleprompt.mipro_optimizer_v2: Best score so far: 38.1\n", | |
| "2025/12/20 10:00:21 INFO dspy.teleprompt.mipro_optimizer_v2: ========================\n", | |
| "\n", | |
| "\n", | |
| "2025/12/20 10:00:21 INFO dspy.teleprompt.mipro_optimizer_v2: ===== Trial 10 / 10 =====\n" | |
| ] | |
| }, | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "\n", | |
| "Average Metric: 16.00 / 42 (38.1%): 100%|██████████| 42/42 [00:00<00:00, 4116.00it/s]" | |
| ] | |
| }, | |
| { | |
| "name": "stderr", | |
| "output_type": "stream", | |
| "text": [ | |
| "2025/12/20 10:00:21 INFO dspy.evaluate.evaluate: Average Metric: 16.0 / 42 (38.1%)\n", | |
| "2025/12/20 10:00:21 INFO dspy.teleprompt.mipro_optimizer_v2: Score: 38.1 with parameters ['Predictor 0: Instruction 2', 'Predictor 0: Few-Shot Set 5'].\n", | |
| "2025/12/20 10:00:21 INFO dspy.teleprompt.mipro_optimizer_v2: Scores so far: [23.81, 30.95, 23.81, 28.57, 21.43, 33.33, 23.81, 38.1, 23.81, 38.1]\n", | |
| "2025/12/20 10:00:21 INFO dspy.teleprompt.mipro_optimizer_v2: Best score so far: 38.1\n", | |
| "2025/12/20 10:00:21 INFO dspy.teleprompt.mipro_optimizer_v2: =========================\n", | |
| "\n", | |
| "\n", | |
| "2025/12/20 10:00:21 INFO dspy.teleprompt.mipro_optimizer_v2: ===== Trial 11 / 10 =====\n" | |
| ] | |
| }, | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "\n", | |
| "Average Metric: 10.00 / 42 (23.8%): 100%|██████████| 42/42 [00:00<00:00, 4954.60it/s]" | |
| ] | |
| }, | |
| { | |
| "name": "stderr", | |
| "output_type": "stream", | |
| "text": [ | |
| "2025/12/20 10:00:21 INFO dspy.evaluate.evaluate: Average Metric: 10.0 / 42 (23.8%)\n", | |
| "2025/12/20 10:00:21 INFO dspy.teleprompt.mipro_optimizer_v2: Score: 23.81 with parameters ['Predictor 0: Instruction 2', 'Predictor 0: Few-Shot Set 1'].\n", | |
| "2025/12/20 10:00:21 INFO dspy.teleprompt.mipro_optimizer_v2: Scores so far: [23.81, 30.95, 23.81, 28.57, 21.43, 33.33, 23.81, 38.1, 23.81, 38.1, 23.81]\n", | |
| "2025/12/20 10:00:21 INFO dspy.teleprompt.mipro_optimizer_v2: Best score so far: 38.1\n", | |
| "2025/12/20 10:00:21 INFO dspy.teleprompt.mipro_optimizer_v2: =========================\n", | |
| "\n", | |
| "\n", | |
| "2025/12/20 10:00:21 INFO dspy.teleprompt.mipro_optimizer_v2: Returning best identified program with score 38.1!\n" | |
| ] | |
| }, | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "\n", | |
| "Average Metric: 16.00 / 54 (29.6%): 100%|██████████| 54/54 [00:00<00:00, 578.75it/s]" | |
| ] | |
| }, | |
| { | |
| "name": "stderr", | |
| "output_type": "stream", | |
| "text": [ | |
| "2025/12/20 10:00:21 INFO dspy.evaluate.evaluate: Average Metric: 16.0 / 54 (29.6%)\n" | |
| ] | |
| }, | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "\n", | |
| "EvaluationResult(score=29.63, results=<list of 54 results>)\n" | |
| ] | |
| } | |
| ], | |
| "source": [ | |
| "gemini_3_pro = dspy.LM(\"openrouter/google/gemini-3-pro-preview\", temperature=0.0)\n", | |
| "optimizer = dspy.MIPROv2(\n", | |
| " metric=eval_metric,\n", | |
| " auto=\"light\",\n", | |
| " teacher_settings=dict(lm=gemini_3_pro),\n", | |
| " prompt_model=gemini_2_5_flash,\n", | |
| " num_threads=32,\n", | |
| ")\n", | |
| "\n", | |
| "len_trainset = len(trainset)\n", | |
| "module_mipro_v2 = optimizer.compile(\n", | |
| " module_vanilla, trainset=trainset, max_bootstrapped_demos=4, max_labeled_demos=4\n", | |
| ")\n", | |
| "mipro_optimized_score = evaluate(module_mipro_v2)\n", | |
| "er.add_result(\"mipro_v2_gemini_2.5_flash\", mipro_optimized_score.score)\n", | |
| "print(mipro_optimized_score)" | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": 13, | |
| "metadata": {}, | |
| "outputs": [ | |
| { | |
| "name": "stderr", | |
| "output_type": "stream", | |
| "text": [ | |
| "2025/12/20 10:00:21 INFO dspy.teleprompt.gepa.gepa: Running GEPA for approx 592 metric calls of the program. This amounts to 11.17 full evals on the train set.\n", | |
| "2025/12/20 10:00:21 WARNING dspy.teleprompt.gepa.gepa: No valset provided; Using trainset as valset. This is useful as an inference-time scaling strategy where you want GEPA to find the best solutions for the provided tasks in the trainset, as it makes GEPA overfit prompts to the provided trainset. In order to ensure generalization and perform well on unseen tasks, please provide separate trainset and valset. Provide the smallest valset that is just large enough to match the downstream task distribution, while keeping trainset as large as possible.\n", | |
| "2025/12/20 10:00:21 INFO dspy.teleprompt.gepa.gepa: Using 53 examples for tracking Pareto scores. You can consider using a smaller sample of the valset to allow GEPA to explore more diverse solutions within the same budget. GEPA requires you to provide the smallest valset that is just large enough to match your downstream task distribution, while providing as large trainset as possible.\n", | |
| "GEPA Optimization: 0%| | 0/592 [00:00<?, ?rollouts/s]2025/12/20 10:00:21 INFO dspy.evaluate.evaluate: Average Metric: 11.0 / 53 (20.8%)\n", | |
| "2025/12/20 10:00:21 INFO dspy.teleprompt.gepa.gepa: Iteration 0: Base program full valset score: 0.20754716981132076\n", | |
| "2025/12/20 10:00:21 INFO dspy.teleprompt.gepa.gepa: Iteration 1: Selected program 0 score: 0.20754716981132076\n" | |
| ] | |
| }, | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "Average Metric: 2.00 / 3 (66.7%): 100%|██████████| 3/3 [00:00<00:00, 3968.12it/s]" | |
| ] | |
| }, | |
| { | |
| "name": "stderr", | |
| "output_type": "stream", | |
| "text": [ | |
| "2025/12/20 10:00:21 INFO dspy.evaluate.evaluate: Average Metric: 2.0 / 3 (66.7%)\n", | |
| "2025/12/20 10:00:21 INFO dspy.teleprompt.gepa.gepa: Iteration 1: Proposed new text for self: You are performing a nutritional analysis from a free‑text description of foods and drinks consumed. Parse the description, identify each distinct item and portion, and return ONLY a Python-like list of FoodItem objects with these fields in this exact order:\n", | |
| "- name: string (generic or brand-specific name; include preparation if relevant, e.g., “boiled egg”)\n", | |
| "- quantity: float (number of units/servings consumed; NEVER put grams/mL here)\n", | |
| "- calories: float (kcal per ONE unit/serving as defined by your quantity)\n", | |
| "- carbs: float (grams per ONE unit/serving)\n", | |
| "- fat: float (grams per ONE unit/serving)\n", | |
| "- protein: float (grams per ONE unit/serving)\n", | |
| "- fiber: float (grams per ONE unit/serving)\n", | |
| "- food_groups: list of strings (use canonical groups)\n", | |
| "\n", | |
| "Critical rules and conventions:\n", | |
| "- Quantity is a count of units/servings only. Do not place weights or volumes (e.g., grams, mL) in quantity. The evaluator multiplies per-unit nutrients by quantity; putting grams in quantity will inflate totals.\n", | |
| "- If the user gives a weight/volume (e.g., “50 g egg”, “200 g chicken”, “507 g water”), fold that portion into the per-unit values and set quantity=1.0 for that described portion. You may reflect the portion in the name if helpful (e.g., “boiled egg (50 g)”, “bottled water (507 g)”).\n", | |
| "- If the user specifies a count (e.g., “2 eggs”), use quantity=2.0 and per-unit nutrients for one egg.\n", | |
| "- Keep items in the order mentioned.\n", | |
| "- Use typical/standard nutrition values for common foods and standard sizes; for brand-named items, use known per-serving label values when possible.\n", | |
| "- Units:\n", | |
| " - calories in kcal\n", | |
| " - carbs, fat, protein, fiber in grams\n", | |
| "- Rounding: use reasonable precision with one decimal place (e.g., 78.0 kcal, 6.3 g protein). Use 0.0 when appropriate.\n", | |
| "- Water (tap or bottled): 0.0 for all nutrients; food_groups should be an empty list [].\n", | |
| "\n", | |
| "Food group mapping (use these canonical labels; pick all that apply):\n", | |
| "- fruit\n", | |
| "- vegetable\n", | |
| "- grain\n", | |
| "- dairy\n", | |
| "- meat and alternatives\n", | |
| "For examples: eggs → meat and alternatives; apples → fruit; potato chips can be treated under grain for snack categorization consistency.\n", | |
| "\n", | |
| "Examples to emulate:\n", | |
| "- “protein pancake from MyProtein” → one serving; e.g., 190.0 kcal, 20.0 g carbs, 4.0 g fat, 25.0 g protein, 2.0 g fiber; food_groups might include ['grain','dairy'].\n", | |
| "- “a cup of tap water, a small single-serving bag of potato chips, and a small raw apple” → three items; water with all zeros; chips ~150.0 kcal per bag; apple ~80.0 kcal, 22.0 g carbs, 4.0 g fiber; choose appropriate food_groups.\n", | |
| "- “507 grams of bottled water and a 50-gram boiled egg” → quantity=1.0 for both; water all zeros; egg per 50 g (~71.5–78.0 kcal, ~0.6 g carbs, ~5.3 g fat, ~6.3 g protein), food_groups ['meat and alternatives'].\n", | |
| "\n", | |
| "Output formatting requirements:\n", | |
| "- Return only the list literal of FoodItem(...) objects, no extra text.\n", | |
| "- Ensure every numeric field is a float with a decimal (e.g., 1.0, 0.0).\n", | |
| "- Do not compute or include totals; provide per-unit values aligned with your quantity so the evaluator can multiply by quantity.\n", | |
| "2025/12/20 10:00:21 INFO dspy.evaluate.evaluate: Average Metric: 3.0 / 3 (100.0%)\n", | |
| "2025/12/20 10:00:21 INFO dspy.teleprompt.gepa.gepa: Iteration 1: New subsample score 3 is better than old score 2. Continue to full eval and add to candidate pool.\n" | |
| ] | |
| }, | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "\n" | |
| ] | |
| }, | |
| { | |
| "name": "stderr", | |
| "output_type": "stream", | |
| "text": [ | |
| "2025/12/20 10:00:22 INFO dspy.evaluate.evaluate: Average Metric: 26.0 / 53 (49.1%)\n", | |
| "2025/12/20 10:00:22 INFO dspy.teleprompt.gepa.gepa: Iteration 1: New program is on the linear pareto front\n", | |
| "2025/12/20 10:00:22 INFO dspy.teleprompt.gepa.gepa: Iteration 1: Full valset score for new program: 0.49056603773584906\n", | |
| "2025/12/20 10:00:22 INFO dspy.teleprompt.gepa.gepa: Iteration 1: Full train_val score for new program: 0.49056603773584906\n", | |
| "2025/12/20 10:00:22 INFO dspy.teleprompt.gepa.gepa: Iteration 1: Individual valset scores for new program: [0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0]\n", | |
| "2025/12/20 10:00:22 INFO dspy.teleprompt.gepa.gepa: Iteration 1: New valset pareto front scores: [0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 0, 1, 0, 0, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0]\n", | |
| "2025/12/20 10:00:22 INFO dspy.teleprompt.gepa.gepa: Iteration 1: Full valset pareto front score: 0.5094339622641509\n", | |
| "2025/12/20 10:00:22 INFO dspy.teleprompt.gepa.gepa: Iteration 1: Updated valset pareto front programs: [{0, 1}, {0, 1}, {0, 1}, {1}, {0, 1}, {1}, {0, 1}, {1}, {0, 1}, {1}, {0, 1}, {0, 1}, {0, 1}, {0, 1}, {0, 1}, {0, 1}, {0, 1}, {0, 1}, {1}, {1}, {0, 1}, {1}, {0, 1}, {1}, {0, 1}, {1}, {0}, {0, 1}, {0, 1}, {0, 1}, {0, 1}, {0, 1}, {1}, {1}, {1}, {0, 1}, {1}, {0, 1}, {0, 1}, {0, 1}, {1}, {0, 1}, {0, 1}, {1}, {0, 1}, {0, 1}, {0, 1}, {1}, {0, 1}, {0, 1}, {0, 1}, {0, 1}, {0, 1}]\n", | |
| "2025/12/20 10:00:22 INFO dspy.teleprompt.gepa.gepa: Iteration 1: Best valset aggregate score so far: 0.49056603773584906\n", | |
| "2025/12/20 10:00:22 INFO dspy.teleprompt.gepa.gepa: Iteration 1: Best program as per aggregate score on train_val: 1\n", | |
| "2025/12/20 10:00:22 INFO dspy.teleprompt.gepa.gepa: Iteration 1: Best program as per aggregate score on valset: 1\n", | |
| "2025/12/20 10:00:22 INFO dspy.teleprompt.gepa.gepa: Iteration 1: Best score on valset: 0.49056603773584906\n", | |
| "2025/12/20 10:00:22 INFO dspy.teleprompt.gepa.gepa: Iteration 1: Best score on train_val: 0.49056603773584906\n", | |
| "2025/12/20 10:00:22 INFO dspy.teleprompt.gepa.gepa: Iteration 1: Linear pareto front program index: 1\n", | |
| "2025/12/20 10:00:22 INFO dspy.teleprompt.gepa.gepa: Iteration 1: New program candidate index: 1\n", | |
| "GEPA Optimization: 19%|█▉ | 112/592 [00:00<00:01, 408.54rollouts/s]2025/12/20 10:00:22 INFO dspy.teleprompt.gepa.gepa: Iteration 2: No merge candidates found\n", | |
| "2025/12/20 10:00:22 INFO dspy.teleprompt.gepa.gepa: Iteration 2: Selected program 1 score: 0.49056603773584906\n" | |
| ] | |
| }, | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "Average Metric: 3.00 / 3 (100.0%): 100%|██████████| 3/3 [00:00<00:00, 3677.06it/s]" | |
| ] | |
| }, | |
| { | |
| "name": "stderr", | |
| "output_type": "stream", | |
| "text": [ | |
| "2025/12/20 10:00:22 INFO dspy.evaluate.evaluate: Average Metric: 3.0 / 3 (100.0%)\n", | |
| "2025/12/20 10:00:22 INFO dspy.teleprompt.gepa.gepa: Iteration 2: All subsample scores perfect. Skipping.\n", | |
| "2025/12/20 10:00:22 INFO dspy.teleprompt.gepa.gepa: Iteration 2: Reflective mutation did not propose a new candidate\n", | |
| "2025/12/20 10:00:22 INFO dspy.teleprompt.gepa.gepa: Iteration 3: Selected program 0 score: 0.20754716981132076\n" | |
| ] | |
| }, | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "\n", | |
| "Average Metric: 1.00 / 3 (33.3%): 100%|██████████| 3/3 [00:00<00:00, 5067.62it/s]" | |
| ] | |
| }, | |
| { | |
| "name": "stderr", | |
| "output_type": "stream", | |
| "text": [ | |
| "2025/12/20 10:00:22 INFO dspy.evaluate.evaluate: Average Metric: 1.0 / 3 (33.3%)\n", | |
| "2025/12/20 10:00:22 INFO dspy.teleprompt.gepa.gepa: Iteration 3: Proposed new text for self: You are performing a nutritional analysis from a short free-text food_description. Your job is to parse the described foods and amounts, estimate nutrition, and output a list of FoodItem objects.\n", | |
| "\n", | |
| "Output format:\n", | |
| "- Return ONLY a single Python-style list literal of FoodItem objects, nothing else. Example:\n", | |
| " [FoodItem(name='apple', quantity=1.0, calories=95.0, carbs=25.0, fat=0.3, protein=0.5, fiber=4.4, food_groups=['fruit'])]\n", | |
| "- Each FoodItem has fields:\n", | |
| " - name: string, concise common name (brand if specified).\n", | |
| " - quantity: float, the number of per-unit servings you are using for this item (see quantity rules below).\n", | |
| " - calories: float, calories PER ONE UNIT of quantity.\n", | |
| " - carbs, fat, protein, fiber: floats in grams PER ONE UNIT of quantity.\n", | |
| " - food_groups: list of strings (choose from: 'fruit', 'vegetable', 'grain', 'dairy', 'meat and alternatives', 'legume', 'nut and seed', 'fat and oils', 'sweets and snacks', 'beverage'). If unclear, use [].\n", | |
| "\n", | |
| "Critical rule about quantity and per-unit nutrition (to match evaluation):\n", | |
| "- The evaluator computes totals as sum(quantity * calories). Therefore, calories/macros MUST be per ONE unit of quantity.\n", | |
| "- Easiest safe approach:\n", | |
| " - If the description gives a specific amount (e.g., “100 g yogurt”, “30 g wurst”, “1 slice pizza”), compute the TOTAL nutrition for that described amount and set quantity=1.0 with calories/macros equal to that total for the portion.\n", | |
| " - If the description gives a count of identical units (e.g., “2 eggs”, “15 almonds”), you may set quantity to that count, BUT then calories/macros must be per single unit (e.g., per egg, per almond). Never provide totals alongside quantity>1, or you will double-count.\n", | |
| "- Never set quantity equal to grams while also providing total calories for that gram amount. Either:\n", | |
| " - Convert to a single portion: quantity=1.0 and calories/macros = totals for that gram amount; OR\n", | |
| " - Use quantity in grams AND provide calories/macros per gram (generally avoid this; prefer quantity=1.0 with totals).\n", | |
| "\n", | |
| "Estimation guidance:\n", | |
| "- Use common nutrition references (e.g., USDA averages) to estimate macros for standard foods; for branded items, use typical values if exact data is unknown.\n", | |
| "- Units:\n", | |
| " - calories in kcal,\n", | |
| " - carbs, fat, protein, fiber in grams.\n", | |
| "- Food groups: include the most relevant groups based on the item. Examples:\n", | |
| " - apple -> ['fruit']\n", | |
| " - white bread -> ['grain']\n", | |
| " - yogurt -> ['dairy']\n", | |
| " - chicken, wurst, eggs -> ['meat and alternatives']\n", | |
| " - nuts -> ['nut and seed']\n", | |
| "- If fiber is unknown, estimate or use 0.0.\n", | |
| "\n", | |
| "Handling target calories in the description:\n", | |
| "- If the description includes an explicit or approximate total calorie target (e.g., “~250 cal”, “total of 700 cals”), ensure the sum over all items (sum of quantity*calories) is within ±10% of that target. Adjust portion choices and per-item estimates accordingly, while staying realistic.\n", | |
| "\n", | |
| "Itemization:\n", | |
| "- Parse the description to identify distinct foods and their amounts. If multiple items are named with specific gram amounts, create one FoodItem per described portion with quantity=1.0 and totals for each.\n", | |
| "- Keep names clear and singular/plural as appropriate; either singular with quantity>1 or a descriptive name with quantity=1.0 for the whole portion are both acceptable, provided per-unit nutrition is consistent with quantity.\n", | |
| "\n", | |
| "Quality checks before output:\n", | |
| "- Verify that total calories = sum(quantity * calories) is sensible and, if a target was stated, within ±10% of that target.\n", | |
| "- Ensure macros roughly align with calories (kcal ≈ 4*carbs + 4*protein + 9*fat, allowing for rounding and fiber).\n", | |
| "- Do not include any explanatory text, headings, or keys—only the list of FoodItem(...) objects.\n", | |
| "2025/12/20 10:00:22 INFO dspy.evaluate.evaluate: Average Metric: 2.0 / 3 (66.7%)\n", | |
| "2025/12/20 10:00:22 INFO dspy.teleprompt.gepa.gepa: Iteration 3: New subsample score 2 is better than old score 1. Continue to full eval and add to candidate pool.\n" | |
| ] | |
| }, | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "\n" | |
| ] | |
| }, | |
| { | |
| "name": "stderr", | |
| "output_type": "stream", | |
| "text": [ | |
| "2025/12/20 10:00:22 INFO dspy.evaluate.evaluate: Average Metric: 22.0 / 53 (41.5%)\n", | |
| "2025/12/20 10:00:22 INFO dspy.teleprompt.gepa.gepa: Iteration 3: Full valset score for new program: 0.41509433962264153\n", | |
| "2025/12/20 10:00:22 INFO dspy.teleprompt.gepa.gepa: Iteration 3: Full train_val score for new program: 0.41509433962264153\n", | |
| "2025/12/20 10:00:22 INFO dspy.teleprompt.gepa.gepa: Iteration 3: Individual valset scores for new program: [0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 1, 0, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0]\n", | |
| "2025/12/20 10:00:22 INFO dspy.teleprompt.gepa.gepa: Iteration 3: New valset pareto front scores: [0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 0, 1, 0, 0, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 0, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0]\n", | |
| "2025/12/20 10:00:22 INFO dspy.teleprompt.gepa.gepa: Iteration 3: Full valset pareto front score: 0.5283018867924528\n", | |
| "2025/12/20 10:00:22 INFO dspy.teleprompt.gepa.gepa: Iteration 3: Updated valset pareto front programs: [{0, 1, 2}, {0, 1, 2}, {0, 1, 2}, {1}, {0, 1, 2}, {1, 2}, {0, 1, 2}, {1, 2}, {0, 1, 2}, {1}, {0, 1, 2}, {0, 1, 2}, {0, 1, 2}, {0, 1}, {0, 1, 2}, {0, 1, 2}, {0, 1, 2}, {0, 1, 2}, {1, 2}, {1, 2}, {0, 1, 2}, {1, 2}, {0, 1, 2}, {1, 2}, {0, 1, 2}, {1, 2}, {0, 2}, {0, 1, 2}, {0, 1, 2}, {0, 1, 2}, {0, 1, 2}, {0, 1, 2}, {1}, {1, 2}, {1}, {0, 1, 2}, {1}, {0, 1, 2}, {0, 1, 2}, {0, 1, 2}, {1, 2}, {0, 1, 2}, {0, 1, 2}, {1, 2}, {2}, {0, 1, 2}, {0, 1, 2}, {1, 2}, {0, 1, 2}, {0, 1, 2}, {0, 1, 2}, {0, 1, 2}, {0, 1, 2}]\n", | |
| "2025/12/20 10:00:22 INFO dspy.teleprompt.gepa.gepa: Iteration 3: Best valset aggregate score so far: 0.49056603773584906\n", | |
| "2025/12/20 10:00:22 INFO dspy.teleprompt.gepa.gepa: Iteration 3: Best program as per aggregate score on train_val: 1\n", | |
| "2025/12/20 10:00:22 INFO dspy.teleprompt.gepa.gepa: Iteration 3: Best program as per aggregate score on valset: 1\n", | |
| "2025/12/20 10:00:22 INFO dspy.teleprompt.gepa.gepa: Iteration 3: Best score on valset: 0.49056603773584906\n", | |
| "2025/12/20 10:00:22 INFO dspy.teleprompt.gepa.gepa: Iteration 3: Best score on train_val: 0.49056603773584906\n", | |
| "2025/12/20 10:00:22 INFO dspy.teleprompt.gepa.gepa: Iteration 3: Linear pareto front program index: 1\n", | |
| "2025/12/20 10:00:22 INFO dspy.teleprompt.gepa.gepa: Iteration 3: New program candidate index: 2\n", | |
| "GEPA Optimization: 29%|██▉ | 174/592 [00:00<00:01, 291.63rollouts/s]2025/12/20 10:00:22 INFO dspy.teleprompt.gepa.gepa: Iteration 4: No merge candidates found\n", | |
| "2025/12/20 10:00:22 INFO dspy.teleprompt.gepa.gepa: Iteration 4: Selected program 2 score: 0.41509433962264153\n" | |
| ] | |
| }, | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "Average Metric: 0.00 / 3 (0.0%): 100%|██████████| 3/3 [00:00<00:00, 3268.29it/s]" | |
| ] | |
| }, | |
| { | |
| "name": "stderr", | |
| "output_type": "stream", | |
| "text": [ | |
| "2025/12/20 10:00:22 INFO dspy.evaluate.evaluate: Average Metric: 0.0 / 3 (0.0%)\n", | |
| "2025/12/20 10:00:22 INFO dspy.teleprompt.gepa.gepa: Iteration 4: Proposed new text for self: You are performing a nutritional analysis from a short free-text food_description. Your job is to parse the described foods and amounts, estimate nutrition, and output a list of FoodItem objects.\n", | |
| "\n", | |
| "Output format:\n", | |
| "- Return ONLY a single Python-style list literal of FoodItem objects, nothing else. Example:\n", | |
| " [FoodItem(name='apple', quantity=1.0, calories=95.0, carbs=25.0, fat=0.3, protein=0.5, fiber=4.4, food_groups=['fruit'])]\n", | |
| "- Each FoodItem has fields:\n", | |
| " - name: string, concise common name (brand if specified).\n", | |
| " - quantity: float, the number of per-unit servings you are using for this item (see quantity rules below).\n", | |
| " - calories: float, calories PER ONE UNIT of quantity.\n", | |
| " - carbs, fat, protein, fiber: floats in grams PER ONE UNIT of quantity.\n", | |
| " - food_groups: list of strings (choose from: 'fruit', 'vegetable', 'grain', 'dairy', 'meat and alternatives', 'legume', 'nut and seed', 'fat and oils', 'sweets and snacks', 'beverage'). If unclear, use [].\n", | |
| "\n", | |
| "Critical rule about quantity and per-unit nutrition (to match evaluation):\n", | |
| "- The evaluator computes totals as sum(quantity * calories). Therefore, calories/macros MUST be per ONE unit of quantity.\n", | |
| "- Easiest safe approach:\n", | |
| " - If the description gives a specific amount (e.g., “100 g yogurt”, “30 g wurst”, “1 slice pizza”), compute the TOTAL nutrition for that described amount and set quantity=1.0 with calories/macros equal to that total for the portion.\n", | |
| " - If the description gives a count of identical units (e.g., “2 eggs”, “15 almonds”, “2 little focaccia pieces”), you may set quantity to that count, BUT then calories/macros must be per single unit (e.g., per egg, per almond, per piece). Never provide totals alongside quantity>1, or you will double-count.\n", | |
| "- Never set quantity equal to grams while also providing total calories for that gram amount. Either:\n", | |
| " - Convert to a single portion: quantity=1.0 and calories/macros = totals for that gram amount; OR\n", | |
| " - Use quantity in grams AND provide calories/macros per gram (generally avoid this; prefer quantity=1.0 with totals).\n", | |
| "\n", | |
| "Interpretation and itemization rules:\n", | |
| "- Parse the description to identify distinct foods and their amounts. When amounts are in grams, compute totals proportionally from per-100 g references and set quantity=1.0.\n", | |
| "- Do not invent or add separate items unless clearly present. Treat toppings, phrases like “with X,” or descriptors like “on a medium crust” as part of the same item unless the text clearly indicates an additional, separate serving.\n", | |
| " - Example: “100 g cheese pizza with fruit on a medium crust” is a single 100 g portion of pizza that includes fruit as a topping—do NOT add a separate “fruit” item.\n", | |
| "- If modifiers indicate small portions (e.g., “little focaccia pieces”), choose a conservative per-piece size and nutrition consistent with the descriptor.\n", | |
| "- Keep names clear; use singular names with quantity>1 when items are identical units, otherwise describe the whole portion with quantity=1.0.\n", | |
| "\n", | |
| "Estimation guidance and default reference values (tuned to match evaluation baselines):\n", | |
| "- Prefer realistic, conservative estimates when portions are unspecified to avoid overestimation.\n", | |
| "- Use these per-100 g baselines when applicable; scale to the described grams. Adjust macros so kcal ≈ 4*carbs + 4*protein + 9*fat (allow reasonable rounding).\n", | |
| "\n", | |
| "Common baselines:\n", | |
| "- Cheese pizza, general (includes crust and cheese; toppings like fruit are part of this if described):\n", | |
| " - ≈ 238 kcal/100 g; carbs ≈ 30 g, fat ≈ 9 g, protein ≈ 10 g, fiber ≈ 2 g per 100 g.\n", | |
| "- Stracciatella (fresh cheese, dairy component of burrata):\n", | |
| " - ≈ 300 kcal/100 g; carbs ≈ 2 g, fat ≈ 27 g, protein ≈ 12 g, fiber 0 g per 100 g.\n", | |
| " - If portion unspecified in a dish (e.g., appetizer), assume a modest 40–60 g unless context implies more.\n", | |
| "- Confit grapes (grapes cooked in syrup; typically a small garnish):\n", | |
| " - Small garnish ≈ 30–60 kcal total per tablespoon or two; mostly carbs; fiber low (≈0–1 g).\n", | |
| "- Focaccia, “little piece” (small bite-sized piece ≈ 15–20 g each):\n", | |
| " - ≈ 45–60 kcal per piece; carbs ≈ 8–12 g, fat ≈ 1–2 g, protein ≈ 1–2 g, fiber ≈ 0.3–0.6 g per piece.\n", | |
| " - If text says “2 little pieces,” set quantity=2 with per-piece nutrition.\n", | |
| "- Common seabream, stewed/cooked:\n", | |
| " - ≈ 90 kcal/100 g; carbs 0 g, fat ≈ 2 g, protein ≈ 18 g per 100 g.\n", | |
| "- Matabala, boiled (starchy root; very low energy density when boiled):\n", | |
| " - ≈ 30–40 kcal/100 g (use 30 kcal/100 g if unspecified); carbs ≈ 7–9 g, fat ≈ 0.1 g, protein ≈ 0.5–1 g, fiber ≈ 1–2 g per 100 g.\n", | |
| "- Rice with vegetables, cooked mixed:\n", | |
| " - ≈ 85–95 kcal/100 g baseline; carbs ≈ 18–20 g, fat ≈ 0.8–1.2 g, protein ≈ 2–3 g, fiber ≈ 1 g per 100 g.\n", | |
| "\n", | |
| "Food group mapping examples:\n", | |
| "- stracciatella, yogurt -> ['dairy']\n", | |
| "- white bread, focaccia, rice, pizza crust -> ['grain']\n", | |
| "- chicken, fish (seabream), eggs, wurst -> ['meat and alternatives']\n", | |
| "- grapes (fresh or confit) -> ['fruit']\n", | |
| "- vegetables (in mixed rice) -> include 'vegetable' alongside 'grain' when appropriate\n", | |
| "- nuts -> ['nut and seed']\n", | |
| "\n", | |
| "Handling target calories in the description:\n", | |
| "- If the description includes an explicit or approximate total calorie target (e.g., “~250 cal”, “total of 700 cals”), ensure the sum over all items (sum of quantity*calories) is within ±10% of that target. Adjust portion assumptions and per-item estimates accordingly, while staying realistic.\n", | |
| "\n", | |
| "Quality checks before output:\n", | |
| "- Verify that total calories = sum(quantity * calories) is sensible and, if a target was stated, within ±10% of that target.\n", | |
| "- Ensure macros roughly align with calories (kcal ≈ 4*carbs + 4*protein + 9*fat, allowing for rounding and fiber).\n", | |
| "- Do not include any explanatory text, headings, or keys—only the list of FoodItem(...) objects.\n", | |
| "\n", | |
| "Implementation tips:\n", | |
| "- For gram-specified amounts: compute totals by (grams/100) * per-100 g baseline; set quantity=1.0.\n", | |
| "- For counted items (pieces, slices): set quantity to the count; provide per-unit nutrition, not totals.\n", | |
| "- When descriptors imply toppings (“with fruit”, “on a medium crust”), treat as part of the main item unless a separate serving is clearly indicated—do not add an extra line-item just for the topping.\n", | |
| "- When portion sizes are unspecified for multi-component dishes (e.g., cheese + garnish + bread), assume modest, appetizer-sized amounts so the total remains realistic and not inflated.\n", | |
| "2025/12/20 10:00:22 INFO dspy.evaluate.evaluate: Average Metric: 2.0 / 3 (66.7%)\n", | |
| "2025/12/20 10:00:22 INFO dspy.teleprompt.gepa.gepa: Iteration 4: New subsample score 2 is better than old score 0. Continue to full eval and add to candidate pool.\n" | |
| ] | |
| }, | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "\n" | |
| ] | |
| }, | |
| { | |
| "name": "stderr", | |
| "output_type": "stream", | |
| "text": [ | |
| "2025/12/20 10:00:22 INFO dspy.evaluate.evaluate: Average Metric: 29.0 / 53 (54.7%)\n", | |
| "2025/12/20 10:00:22 INFO dspy.teleprompt.gepa.gepa: Iteration 4: New program is on the linear pareto front\n", | |
| "2025/12/20 10:00:22 INFO dspy.teleprompt.gepa.gepa: Iteration 4: Full valset score for new program: 0.5471698113207547\n", | |
| "2025/12/20 10:00:22 INFO dspy.teleprompt.gepa.gepa: Iteration 4: Full train_val score for new program: 0.5471698113207547\n", | |
| "2025/12/20 10:00:22 INFO dspy.teleprompt.gepa.gepa: Iteration 4: Individual valset scores for new program: [1, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 0, 1, 0, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 1, 0, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1]\n", | |
| "2025/12/20 10:00:22 INFO dspy.teleprompt.gepa.gepa: Iteration 4: New valset pareto front scores: [1, 0, 0, 1, 0, 1, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1]\n", | |
| "2025/12/20 10:00:22 INFO dspy.teleprompt.gepa.gepa: Iteration 4: Full valset pareto front score: 0.660377358490566\n", | |
| "2025/12/20 10:00:22 INFO dspy.teleprompt.gepa.gepa: Iteration 4: Updated valset pareto front programs: [{3}, {0, 1, 2, 3}, {0, 1, 2, 3}, {1}, {0, 1, 2, 3}, {1, 2}, {3}, {1, 2, 3}, {0, 1, 2, 3}, {1, 3}, {0, 1, 2, 3}, {0, 1, 2, 3}, {0, 1, 2, 3}, {0, 1}, {0, 1, 2, 3}, {0, 1, 2, 3}, {0, 1, 2, 3}, {3}, {1, 2, 3}, {1, 2, 3}, {0, 1, 2, 3}, {1, 2, 3}, {0, 1, 2, 3}, {1, 2, 3}, {0, 1, 2, 3}, {1, 2, 3}, {0, 2, 3}, {0, 1, 2, 3}, {0, 1, 2, 3}, {0, 1, 2, 3}, {0, 1, 2, 3}, {3}, {1}, {1, 2, 3}, {1}, {3}, {1, 3}, {0, 1, 2, 3}, {0, 1, 2, 3}, {0, 1, 2, 3}, {1, 2, 3}, {0, 1, 2, 3}, {0, 1, 2, 3}, {1, 2, 3}, {2}, {0, 1, 2, 3}, {0, 1, 2, 3}, {1, 2, 3}, {3}, {0, 1, 2, 3}, {0, 1, 2, 3}, {0, 1, 2, 3}, {3}]\n", | |
| "2025/12/20 10:00:22 INFO dspy.teleprompt.gepa.gepa: Iteration 4: Best valset aggregate score so far: 0.5471698113207547\n", | |
| "2025/12/20 10:00:22 INFO dspy.teleprompt.gepa.gepa: Iteration 4: Best program as per aggregate score on train_val: 3\n", | |
| "2025/12/20 10:00:22 INFO dspy.teleprompt.gepa.gepa: Iteration 4: Best program as per aggregate score on valset: 3\n", | |
| "2025/12/20 10:00:22 INFO dspy.teleprompt.gepa.gepa: Iteration 4: Best score on valset: 0.5471698113207547\n", | |
| "2025/12/20 10:00:22 INFO dspy.teleprompt.gepa.gepa: Iteration 4: Best score on train_val: 0.5471698113207547\n", | |
| "2025/12/20 10:00:22 INFO dspy.teleprompt.gepa.gepa: Iteration 4: Linear pareto front program index: 3\n", | |
| "2025/12/20 10:00:22 INFO dspy.teleprompt.gepa.gepa: Iteration 4: New program candidate index: 3\n", | |
| "GEPA Optimization: 39%|███▉ | 233/592 [00:00<00:01, 233.86rollouts/s]2025/12/20 10:00:22 INFO dspy.teleprompt.gepa.gepa: Iteration 5: No merge candidates found\n", | |
| "2025/12/20 10:00:22 INFO dspy.teleprompt.gepa.gepa: Iteration 5: Selected program 3 score: 0.5471698113207547\n" | |
| ] | |
| }, | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "Average Metric: 1.00 / 3 (33.3%): 100%|██████████| 3/3 [00:00<00:00, 4183.15it/s]" | |
| ] | |
| }, | |
| { | |
| "name": "stderr", | |
| "output_type": "stream", | |
| "text": [ | |
| "2025/12/20 10:00:22 INFO dspy.evaluate.evaluate: Average Metric: 1.0 / 3 (33.3%)\n", | |
| "2025/12/20 10:00:22 INFO dspy.teleprompt.gepa.gepa: Iteration 5: Proposed new text for self: You are performing a nutritional analysis from a short free-text food_description. Your job is to parse the described foods and amounts, estimate nutrition, and output a list of FoodItem objects.\n", | |
| "\n", | |
| "Input:\n", | |
| "- A single string field (food_description) describing foods eaten and amounts.\n", | |
| "\n", | |
| "Output:\n", | |
| "- Return ONLY a single Python-style list literal of FoodItem objects, nothing else. Example:\n", | |
| " [FoodItem(name='apple', quantity=1.0, calories=95.0, carbs=25.0, fat=0.3, protein=0.5, fiber=4.4, food_groups=['fruit'])]\n", | |
| "\n", | |
| "FoodItem fields:\n", | |
| "- name: string, concise common name (brand if specified).\n", | |
| "- quantity: float, the number of per-unit servings you are using for this item (see quantity rules).\n", | |
| "- calories: float, calories PER ONE UNIT of quantity.\n", | |
| "- carbs, fat, protein, fiber: floats in grams PER ONE UNIT of quantity.\n", | |
| "- food_groups: list of strings (choose from: 'fruit', 'vegetable', 'grain', 'dairy', 'meat and alternatives', 'legume', 'nut and seed', 'fat and oils', 'sweets and snacks', 'beverage'). If unclear, use [].\n", | |
| "\n", | |
| "Critical rule about quantity and per-unit nutrition:\n", | |
| "- The evaluator computes totals as sum(quantity * calories). Therefore, calories and macros MUST be per ONE unit of quantity.\n", | |
| "- Safe approaches:\n", | |
| " - If the description gives a specific portion in grams/ounces/cups/etc. (e.g., “190 g bread”, “1 slice pizza”, “2 cups soup”), compute the TOTAL nutrition for that described portion and set quantity=1.0 with calories/macros equal to that total for the portion.\n", | |
| " - If the description gives a count of identical units (e.g., “2 eggs”, “15 almonds”, “2 little focaccia pieces”), you may set quantity to that count, BUT then calories/macros must be per single unit (e.g., per egg, per almond, per piece). Never provide totals alongside quantity>1, or it will double-count.\n", | |
| "- Never set quantity equal to grams while also providing total calories for that gram amount. Either:\n", | |
| " - Convert to a single portion: quantity=1.0 with calories/macros = totals for that gram amount; OR\n", | |
| " - Use quantity in grams AND provide calories/macros per gram (generally avoid this; prefer quantity=1.0 with totals).\n", | |
| "\n", | |
| "Interpretation and itemization rules:\n", | |
| "- Parse the description to identify distinct foods and their amounts. When amounts are in grams, compute totals proportionally from per-100 g references and set quantity=1.0.\n", | |
| "- Do not invent or add separate items unless clearly present. Treat toppings or descriptors (“with X,” “on a medium crust,” “with fruit”) as part of the same item unless the text clearly indicates an additional, separate serving.\n", | |
| "- If modifiers indicate small pieces (e.g., “little piece”), choose a conservative per-piece size and nutrition consistent with the descriptor.\n", | |
| "- Keep names clear; use singular names with quantity>1 when items are identical units; otherwise describe the whole portion with quantity=1.0.\n", | |
| "\n", | |
| "Estimation guidance and baselines (tuned to match evaluation):\n", | |
| "- Prefer realistic, conservative estimates when portions are unspecified to avoid overestimation.\n", | |
| "- Use these per-100 g baselines when applicable; scale to the described grams. Adjust macros so kcal ≈ 4*carbs + 4*protein + 9*fat (allow reasonable rounding). Include fiber where reasonable.\n", | |
| "\n", | |
| "Common baselines:\n", | |
| "- Cheese pizza, general (includes crust and cheese; toppings are part of this if described):\n", | |
| " - ≈ 238 kcal/100 g; carbs ≈ 30 g, fat ≈ 9 g, protein ≈ 10 g, fiber ≈ 2 g per 100 g.\n", | |
| "- Stracciatella (fresh cheese, dairy component of burrata):\n", | |
| " - ≈ 300 kcal/100 g; carbs ≈ 2 g, fat ≈ 27 g, protein ≈ 12 g, fiber 0 g per 100 g.\n", | |
| " - If portion unspecified in a dish, assume a modest 40–60 g unless context implies more.\n", | |
| "- Confit grapes (grapes cooked in syrup; typically a small garnish):\n", | |
| " - Small garnish ≈ 30–60 kcal total per tablespoon or two; mostly carbs; fiber ≈ 0–1 g.\n", | |
| "- Focaccia, “little piece” (small bite-sized piece ≈ 15–20 g each):\n", | |
| " - ≈ 45–60 kcal per piece; carbs ≈ 8–12 g, fat ≈ 1–2 g, protein ≈ 1–2 g, fiber ≈ 0.3–0.6 g per piece.\n", | |
| " - If text says “2 little pieces,” set quantity=2 with per-piece nutrition.\n", | |
| "- Seabream, stewed/cooked:\n", | |
| " - ≈ 90 kcal/100 g; carbs 0 g, fat ≈ 2 g, protein ≈ 18 g per 100 g.\n", | |
| "- Matabala, boiled (starchy root; very low energy density when boiled):\n", | |
| " - ≈ 30 kcal/100 g; carbs ≈ 7–8 g, fat ≈ 0.1 g, protein ≈ 0.5–1 g, fiber ≈ 1–2 g per 100 g.\n", | |
| "- Rice with vegetables, cooked mixed:\n", | |
| " - ≈ 90 kcal/100 g; carbs ≈ 19 g, fat ≈ 1.0 g, protein ≈ 2.5 g, fiber ≈ 1 g per 100 g.\n", | |
| "\n", | |
| "Additional baselines (from prior tasks; use these to match evaluation expectations):\n", | |
| "- Leavened corn and wheat flour bread (cornbread-like/enriched leavened bread):\n", | |
| " - ≈ 350 kcal/100 g; carbs ≈ 57 g, fat ≈ 10 g, protein ≈ 8 g, fiber ≈ 3 g per 100 g.\n", | |
| " - Use this higher bread baseline when “corn and wheat flour” or “cornbread-like” is specified; use 240–270 kcal/100 g only for plain white/whole-wheat bread without corn/enrichment cues.\n", | |
| "- Spiced butter (seasoned butter; treat as butter):\n", | |
| " - ≈ 720–730 kcal/100 g; carbs ≈ 1 g, fat ≈ 81 g, protein ≈ 1 g, fiber 0 g per 100 g.\n", | |
| "- Fish broth/stock:\n", | |
| " - ≈ 10–12 kcal/100 g; carbs 0 g, fat ≈ 0.2–0.3 g, protein ≈ 1–2 g, fiber 0 g per 100 g.\n", | |
| "- Barley flour porridge (thick cooked porridge from barley flour; water-based unless otherwise stated):\n", | |
| " - ≈ 170 kcal/100 g; carbs ≈ 35 g, fat ≈ 1.5 g, protein ≈ 3.5 g, fiber ≈ 3 g per 100 g.\n", | |
| " - Use this higher porridge baseline for “flour porridge” to reflect denser energy than watery grain porridges.\n", | |
| "- Fried ripe plantains (maduros-style; use conservative oil uptake):\n", | |
| " - ≈ 133 kcal/100 g; carbs ≈ 26 g, fat ≈ 3 g, protein ≈ 1.3 g, fiber ≈ 2.2 g per 100 g.\n", | |
| " - Apply this lower baseline to avoid overestimating large portions unless explicit heavy oil use is stated.\n", | |
| "- Pecans:\n", | |
| " - One handful ≈ 28–30 g. Per 28 g: ≈ 196 kcal; carbs ≈ 3.9 g, fat ≈ 20.4 g, protein ≈ 2.6 g, fiber ≈ 2.7 g.\n", | |
| " - Per 100 g reference ≈ 691 kcal; carbs ≈ 14 g, fat ≈ 72 g, protein ≈ 9 g, fiber ≈ 10 g.\n", | |
| "- Water:\n", | |
| " - 0 kcal; carbs 0 g, fat 0 g, protein 0 g, fiber 0 g.\n", | |
| "\n", | |
| "Food group mapping guidance:\n", | |
| "- Breads, focaccia, rice, pizza crust, porridge (grain-based) -> ['grain'].\n", | |
| "- Cheese, yogurt, stracciatella -> ['dairy'].\n", | |
| "- Fish, chicken, eggs, broth made from animal protein -> ['meat and alternatives'].\n", | |
| "- Butter (including spiced butter) and oils -> ['fat and oils'].\n", | |
| "- Grapes (fresh or confit), plantains -> ['fruit'].\n", | |
| "- Mixed rice with vegetables -> include both ['grain', 'vegetable'] when appropriate.\n", | |
| "- Nuts and seeds (e.g., pecans) -> ['nut and seed'].\n", | |
| "- Water, tea, coffee (unsweetened) -> ['beverage'].\n", | |
| "\n", | |
| "Handling target calories in the description:\n", | |
| "- If the description includes an explicit or approximate total calorie target (e.g., “~250 cal”, “total of 700 cals”), ensure the sum over all items (sum of quantity*calories) is within ±10% of that target. Adjust portion assumptions and per-item estimates accordingly, while staying realistic.\n", | |
| "\n", | |
| "Portion and unit heuristics:\n", | |
| "- For gram-specified amounts: compute totals by (grams/100) * per-100 g baseline; set quantity=1.0.\n", | |
| "- For counted items (pieces, slices): set quantity to the count; provide per-unit nutrition, not totals.\n", | |
| "- For “a handful of nuts”: assume ≈ 28–30 g unless otherwise specified; set quantity=1.0 with totals for that portion.\n", | |
| "- For “a glass of water”: treat as 0 kcal beverage; set quantity=1.0.\n", | |
| "\n", | |
| "Quality checks before output:\n", | |
| "- Verify total calories = sum(quantity * calories) is sensible and, if a target was stated, within ±10% of that target.\n", | |
| "- Ensure macros roughly align with calories (kcal ≈ 4*carbs + 4*protein + 9*fat, allowing for rounding and fiber).\n", | |
| "- Choose food_groups from the allowed list; avoid leaving [] when a clear mapping exists (e.g., pecans -> ['nut and seed'], butter -> ['fat and oils'], water -> ['beverage']).\n", | |
| "- Do not include any explanatory text—only the list of FoodItem(...) objects.\n", | |
| "\n", | |
| "Implementation tips:\n", | |
| "- Use the baselines above when the exact item matches or is strongly implied; scale by the described grams.\n", | |
| "- When ambiguity exists, choose conservative but realistic portions and energy densities that avoid overestimation while remaining consistent with the baselines and descriptors.\n", | |
| "- Keep numeric precision reasonable (typically one decimal place is sufficient).\n", | |
| "2025/12/20 10:00:22 INFO dspy.evaluate.evaluate: Average Metric: 3.0 / 3 (100.0%)\n", | |
| "2025/12/20 10:00:22 INFO dspy.teleprompt.gepa.gepa: Iteration 5: New subsample score 3 is better than old score 1. Continue to full eval and add to candidate pool.\n" | |
| ] | |
| }, | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "\n" | |
| ] | |
| }, | |
| { | |
| "name": "stderr", | |
| "output_type": "stream", | |
| "text": [ | |
| "2025/12/20 10:00:23 INFO dspy.evaluate.evaluate: Average Metric: 26.0 / 53 (49.1%)\n", | |
| "2025/12/20 10:00:23 INFO dspy.teleprompt.gepa.gepa: Iteration 5: Full valset score for new program: 0.49056603773584906\n", | |
| "2025/12/20 10:00:23 INFO dspy.teleprompt.gepa.gepa: Iteration 5: Full train_val score for new program: 0.49056603773584906\n", | |
| "2025/12/20 10:00:23 INFO dspy.teleprompt.gepa.gepa: Iteration 5: Individual valset scores for new program: [1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0, 0, 1, 0, 1, 0, 1, 1, 1, 0, 0, 1, 0, 0, 0, 0, 1]\n", | |
| "2025/12/20 10:00:23 INFO dspy.teleprompt.gepa.gepa: Iteration 5: New valset pareto front scores: [1, 0, 0, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1]\n", | |
| "2025/12/20 10:00:23 INFO dspy.teleprompt.gepa.gepa: Iteration 5: Full valset pareto front score: 0.6981132075471698\n", | |
| "2025/12/20 10:00:23 INFO dspy.teleprompt.gepa.gepa: Iteration 5: Updated valset pareto front programs: [{3, 4}, {0, 1, 2, 3, 4}, {0, 1, 2, 3, 4}, {1}, {4}, {1, 2}, {3}, {1, 2, 3, 4}, {0, 1, 2, 3, 4}, {1, 3}, {0, 1, 2, 3, 4}, {0, 1, 2, 3, 4}, {0, 1, 2, 3, 4}, {0, 1, 4}, {0, 1, 2, 3, 4}, {0, 1, 2, 3, 4}, {0, 1, 2, 3, 4}, {3}, {1, 2, 3, 4}, {1, 2, 3, 4}, {0, 1, 2, 3, 4}, {1, 2, 3, 4}, {0, 1, 2, 3, 4}, {1, 2, 3, 4}, {0, 1, 2, 3, 4}, {1, 2, 3, 4}, {0, 2, 3}, {0, 1, 2, 3, 4}, {0, 1, 2, 3, 4}, {0, 1, 2, 3, 4}, {0, 1, 2, 3, 4}, {3}, {1}, {1, 2, 3, 4}, {1, 4}, {3, 4}, {1, 3}, {0, 1, 2, 3, 4}, {0, 1, 2, 3, 4}, {0, 1, 2, 3, 4}, {1, 2, 3, 4}, {0, 1, 2, 3}, {4}, {1, 2, 3, 4}, {2, 4}, {0, 1, 2, 3, 4}, {0, 1, 2, 3, 4}, {1, 2, 3, 4}, {3}, {0, 1, 2, 3, 4}, {0, 1, 2, 3, 4}, {0, 1, 2, 3, 4}, {3, 4}]\n", | |
| "2025/12/20 10:00:23 INFO dspy.teleprompt.gepa.gepa: Iteration 5: Best valset aggregate score so far: 0.5471698113207547\n", | |
| "2025/12/20 10:00:23 INFO dspy.teleprompt.gepa.gepa: Iteration 5: Best program as per aggregate score on train_val: 3\n", | |
| "2025/12/20 10:00:23 INFO dspy.teleprompt.gepa.gepa: Iteration 5: Best program as per aggregate score on valset: 3\n", | |
| "2025/12/20 10:00:23 INFO dspy.teleprompt.gepa.gepa: Iteration 5: Best score on valset: 0.5471698113207547\n", | |
| "2025/12/20 10:00:23 INFO dspy.teleprompt.gepa.gepa: Iteration 5: Best score on train_val: 0.5471698113207547\n", | |
| "2025/12/20 10:00:23 INFO dspy.teleprompt.gepa.gepa: Iteration 5: Linear pareto front program index: 3\n", | |
| "2025/12/20 10:00:23 INFO dspy.teleprompt.gepa.gepa: Iteration 5: New program candidate index: 4\n", | |
| "GEPA Optimization: 49%|████▉ | 292/592 [00:01<00:01, 230.43rollouts/s]2025/12/20 10:00:23 INFO dspy.teleprompt.gepa.gepa: Iteration 6: No merge candidates found\n", | |
| "2025/12/20 10:00:23 INFO dspy.teleprompt.gepa.gepa: Iteration 6: Selected program 3 score: 0.5471698113207547\n" | |
| ] | |
| }, | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "Average Metric: 1.00 / 3 (33.3%): 100%|██████████| 3/3 [00:00<00:00, 2817.49it/s]" | |
| ] | |
| }, | |
| { | |
| "name": "stderr", | |
| "output_type": "stream", | |
| "text": [ | |
| "2025/12/20 10:00:23 INFO dspy.evaluate.evaluate: Average Metric: 1.0 / 3 (33.3%)\n", | |
| "2025/12/20 10:00:23 INFO dspy.teleprompt.gepa.gepa: Iteration 6: Proposed new text for self: You are performing a nutritional analysis from a short free-text food_description. Your job is to parse the described foods and amounts, estimate nutrition, and output a list of FoodItem objects.\n", | |
| "\n", | |
| "Output format:\n", | |
| "- Return ONLY a single Python-style list literal of FoodItem objects, nothing else. Example:\n", | |
| " [FoodItem(name='apple', quantity=1.0, calories=95.0, carbs=25.0, fat=0.3, protein=0.5, fiber=4.4, food_groups=['fruit'])]\n", | |
| "- Each FoodItem has fields:\n", | |
| " - name: string, concise common name (brand if specified).\n", | |
| " - quantity: float, the number of per-unit servings you are using for this item (see quantity rules below).\n", | |
| " - calories: float, calories PER ONE UNIT of quantity.\n", | |
| " - carbs, fat, protein, fiber: floats in grams PER ONE UNIT of quantity.\n", | |
| " - food_groups: list of strings (choose from: 'fruit', 'vegetable', 'grain', 'dairy', 'meat and alternatives', 'legume', 'nut and seed', 'fat and oils', 'sweets and snacks', 'beverage'). If unclear, use [].\n", | |
| "\n", | |
| "Critical rule about quantity and per-unit nutrition:\n", | |
| "- The evaluator computes totals as sum(quantity * calories). Therefore, calories/macros MUST be per ONE unit of quantity.\n", | |
| "- Safe approach:\n", | |
| " - If the description gives a specific amount (e.g., “100 g yogurt”, “30 g wurst”, “1 slice pizza”), compute the TOTAL nutrition for that described amount and set quantity=1.0 with calories/macros equal to that total for the portion.\n", | |
| " - If the description gives a count of identical units (e.g., “2 eggs”, “15 almonds”, “2 little focaccia pieces”), you may set quantity to that count, BUT then calories/macros must be per single unit (e.g., per egg, per almond, per piece). Never provide totals alongside quantity>1.\n", | |
| "- Never set quantity equal to grams while also providing total calories for that gram amount. Either:\n", | |
| " - Convert to a single portion: quantity=1.0 and calories/macros = totals for that gram amount; OR\n", | |
| " - Use quantity in grams AND provide calories/macros per gram (generally avoid; prefer quantity=1.0 with totals).\n", | |
| "\n", | |
| "Interpretation and itemization rules:\n", | |
| "- Parse the description to identify distinct foods and their amounts. When amounts are in grams, compute totals proportionally from per-100 g references and set quantity=1.0.\n", | |
| "- Do not invent or add separate items unless clearly present. Treat toppings, phrases like “with X,” or descriptors like “on a medium crust” as part of the same item unless the text indicates a separate serving.\n", | |
| "- If modifiers indicate small portions (e.g., “little focaccia pieces”), choose a conservative per-piece size consistent with the descriptor.\n", | |
| "- Keep names clear; use singular names with quantity>1 when items are identical units, otherwise describe the whole portion with quantity=1.0.\n", | |
| "\n", | |
| "Estimation guidance:\n", | |
| "- Prefer realistic, conservative estimates when portions are unspecified to avoid overestimation, but do not undercount obvious mains or full bowls (e.g., a large gram-specified soup should not be treated like plain broth).\n", | |
| "- Use per-100 g baselines and scale to the described grams. Adjust macros so kcal ≈ 4*carbs + 4*protein + 9*fat (allow reasonable rounding).\n", | |
| "- Packaging cues:\n", | |
| " - “a bag” of snacks typically implies a single-serve bag. For dried fruit snacks, assume 1.25–1.5 oz (35–43 g) unless otherwise specified.\n", | |
| " - “small coffee” ~8–12 fl oz.\n", | |
| " - “tablespoon” ~15–20 g for jams/spreads; use 20 g for jam unless otherwise stated.\n", | |
| "- Beverages: include as items (e.g., water, coffee). Water is 0 kcal.\n", | |
| "\n", | |
| "Category baselines (per 100 g unless noted), including domain-specific items seen in tasks:\n", | |
| "- Water: 0 kcal; macros 0; food_groups=['beverage'].\n", | |
| "- Brewed coffee, black (small cup): 2–5 kcal per serving (8–12 fl oz), carbs/fat/protein ~0 g; food_groups=['beverage'].\n", | |
| "- Flavored coffee creamer, single-serve cup/pod: per pod (10–15 mL): ≈30–45 kcal; carbs ≈4–6 g; fat ≈1.5–2.5 g; protein ≈0 g; food_groups=['dairy'].\n", | |
| "- Regular plain bagel (typical “regular” size ≈ 90–105 g): ≈270–300 kcal; carbs ≈53–58 g; fat ≈1–2 g; protein ≈9–11 g; fiber ≈2–3 g; food_groups=['grain'].\n", | |
| "- Jam/jelly (per tablespoon ≈ 20 g): ≈50 kcal; carbs ≈13 g; fat 0 g; protein 0 g; fiber ≈0–0.5 g; food_groups=['fruit'].\n", | |
| "- Grapes, red/green: ≈69 kcal/100 g; carbs ≈18 g; fat ≈0.2 g; protein ≈0.7 g; fiber ≈0.9 g; food_groups=['fruit'].\n", | |
| "- Rice, cooked:\n", | |
| " - White rice, general: ≈130 kcal/100 g; carbs ≈28 g; fat ≈0.3 g; protein ≈2.4 g; fiber ≈0.3 g.\n", | |
| " - Glutinous (sticky) rice, steamed/plain (no coconut milk): tends to be denser; use ≈130–150 kcal/100 g by default when unspecified. If clearly “plain cooked,” you may use ≈100–120 kcal/100 g; choose the higher end if the portion is a main staple to avoid underestimation.\n", | |
| "- Fish and soups:\n", | |
| " - Fish, white (cooked): ≈90–120 kcal/100 g; carbs 0 g; fat ≈2–5 g; protein ≈18–22 g.\n", | |
| " - Fish broth soups vary widely. For a clearly gram-specified full bowl (e.g., >250 g) of a regional fish broth soup (e.g., striped snakehead fish broth soup), assume a substantive soup with fish pieces and some fat: ≈100–130 kcal/100 g unless context specifies “clear/light broth.” Use the higher end for hearty/rich soups.\n", | |
| "- Dried apple snacks (“a bag” single-serve): assume ≈40 g per bag; ≈140 kcal per bag; carbs ≈34 g; fat ≈0–1 g; protein ≈0.5 g; fiber ≈3–5 g; food_groups=['fruit'].\n", | |
| "\n", | |
| "Additional common baselines from prior tasks (use when applicable):\n", | |
| "- Cheese pizza, general: ≈238 kcal/100 g; carbs ≈30 g; fat ≈9 g; protein ≈10 g; fiber ≈2 g.\n", | |
| "- Stracciatella (fresh cheese): ≈300 kcal/100 g; carbs ≈2 g; fat ≈27 g; protein ≈12 g; fiber 0 g.\n", | |
| "- Confit grapes (small garnish): 30–60 kcal total per tablespoon or two; mostly carbs; fiber ≈0–1 g.\n", | |
| "- Focaccia, “little piece” (≈15–20 g each): ≈45–60 kcal per piece; carbs ≈8–12 g, fat ≈1–2 g, protein ≈1–2 g, fiber ≈0.3–0.6 g per piece.\n", | |
| "- Common seabream, cooked: ≈90 kcal/100 g; carbs 0 g; fat ≈2 g; protein ≈18 g.\n", | |
| "- Matabala, boiled: ≈30 kcal/100 g; carbs ≈7–9 g; fat ≈0.1 g; protein ≈0.5–1 g; fiber ≈1–2 g.\n", | |
| "- Rice with vegetables, cooked mixed: ≈90 kcal/100 g; carbs ≈18–20 g; fat ≈0.8–1.2 g; protein ≈2–3 g; fiber ≈1 g.\n", | |
| "\n", | |
| "Food group mapping guidance:\n", | |
| "- dairy: milk, yogurt, stracciatella, coffee creamer\n", | |
| "- grain: bread, bagel, rice, pizza crust, focaccia\n", | |
| "- meat and alternatives: fish (e.g., snakehead, seabream), chicken, eggs, wurst\n", | |
| "- fruit: grapes (fresh or confit), jams/jellies, dried apple snacks\n", | |
| "- beverage: water, coffee, tea, other drinks\n", | |
| "- Include additional groups (vegetable, legume, nut and seed, fat and oils, sweets and snacks) when clearly applicable.\n", | |
| "\n", | |
| "Handling explicit target calories:\n", | |
| "- If the description includes an explicit or approximate total calorie target (e.g., “~250 cal”, “total of 700 cals”), ensure the sum over all items is within ±10% of that target. Adjust portion assumptions and per-item estimates accordingly, while staying realistic.\n", | |
| "\n", | |
| "Quality checks before output:\n", | |
| "- Verify that total calories = sum(quantity * calories) is sensible, and (if a target was stated) within ±10% of that target.\n", | |
| "- Ensure macros roughly align with calories (kcal ≈ 4*carbs + 4*protein + 9*fat).\n", | |
| "- For gram-specified amounts: compute totals by (grams/100) * per-100 g baseline; set quantity=1.0.\n", | |
| "- For counted items (pieces, slices): set quantity to the count; provide per-unit nutrition, not totals.\n", | |
| "- When portion sizes are unspecified for multi-component dishes or packaged snacks (“a bag”), assume modest, single-serve amounts consistent with the item category (e.g., dried fruit bag ≈ 40 g ≈ 140 kcal) so totals are not underestimated.\n", | |
| "\n", | |
| "Implementation tips:\n", | |
| "- Use higher-end realistic baselines for main staples and full bowls (e.g., sticky rice, hearty fish soups) to avoid undercounting when grams are large.\n", | |
| "- Treat add-ins (e.g., coffee creamer) as separate line items if they are separate components.\n", | |
| "- Do not include any explanatory text—return only the list of FoodItem(...) objects.\n", | |
| "2025/12/20 10:00:23 INFO dspy.evaluate.evaluate: Average Metric: 2.0 / 3 (66.7%)\n", | |
| "2025/12/20 10:00:23 INFO dspy.teleprompt.gepa.gepa: Iteration 6: New subsample score 2 is better than old score 1. Continue to full eval and add to candidate pool.\n" | |
| ] | |
| }, | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "\n" | |
| ] | |
| }, | |
| { | |
| "name": "stderr", | |
| "output_type": "stream", | |
| "text": [ | |
| "2025/12/20 10:00:23 WARNING dspy.clients.lm: LM response was truncated due to exceeding max_tokens=None. You can inspect the latest LM interactions with `dspy.inspect_history()`. To avoid truncation, consider passing a larger max_tokens when setting up dspy.LM. You may also consider increasing the temperature (currently 0.0) if the reason for truncation is repetition.\n", | |
| "2025/12/20 10:00:23 WARNING dspy.adapters.json_adapter: Failed to use structured output format, falling back to JSON mode.\n", | |
| "2025/12/20 10:00:23 ERROR dspy.utils.parallelizer: Error for Example({'food_description': '30\\u202fg protein‑powder isolate (unflavoured, Star Nutrition) with 1 tbsp organic crunchy peanut butter, 200\\u202fml Oatly milk and 100\\u202fml filtered coffee', 'food_groups': ['dairy', 'meat and alternatives'], 'total_calories': 325.0, 'source': 'golden_dataset'}) (input_keys={'food_description'}): 2 validation errors for list[FoodItem]\n", | |
| "1.food_groups.0\n", | |
| " Input should be 'dairy', 'meat and alternatives', 'grain', 'fruit' or 'vegetable' [type=literal_error, input_value='nut and seed', input_type=str]\n", | |
| " For further information visit https://errors.pydantic.dev/2.11/v/literal_error\n", | |
| "3.food_groups.0\n", | |
| " Input should be 'dairy', 'meat and alternatives', 'grain', 'fruit' or 'vegetable' [type=literal_error, input_value='beverage', input_type=str]\n", | |
| " For further information visit https://errors.pydantic.dev/2.11/v/literal_error\n", | |
| "Traceback (most recent call last):\n", | |
| " File \"/Users/duarteocarmo/Repos/taralli-api/.env/lib/python3.12/site-packages/dspy/utils/parallelizer.py\", line 57, in safe_func\n", | |
| " return user_function(item)\n", | |
| " ^^^^^^^^^^^^^^^^^^^\n", | |
| " File \"/Users/duarteocarmo/Repos/taralli-api/.env/lib/python3.12/site-packages/dspy/evaluate/evaluate.py\", line 172, in process_item\n", | |
| " prediction = program(**example.inputs())\n", | |
| " ^^^^^^^^^^^^^^^^^^^^^^^^^^^\n", | |
| " File \"/Users/duarteocarmo/Repos/taralli-api/.env/lib/python3.12/site-packages/dspy/predict/predict.py\", line 103, in __call__\n", | |
| " return super().__call__(**kwargs)\n", | |
| " ^^^^^^^^^^^^^^^^^^^^^^^^^^\n", | |
| " File \"/Users/duarteocarmo/Repos/taralli-api/.env/lib/python3.12/site-packages/dspy/utils/callback.py\", line 326, in sync_wrapper\n", | |
| " return fn(instance, *args, **kwargs)\n", | |
| " ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n", | |
| " File \"/Users/duarteocarmo/Repos/taralli-api/.env/lib/python3.12/site-packages/dspy/primitives/module.py\", line 81, in __call__\n", | |
| " return self.forward(*args, **kwargs)\n", | |
| " ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n", | |
| " File \"/Users/duarteocarmo/Repos/taralli-api/.env/lib/python3.12/site-packages/dspy/predict/predict.py\", line 192, in forward\n", | |
| " completions = adapter(lm, lm_kwargs=config, signature=signature, demos=demos, inputs=kwargs)\n", | |
| " ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n", | |
| " File \"/Users/duarteocarmo/Repos/taralli-api/.env/lib/python3.12/site-packages/dspy/adapters/chat_adapter.py\", line 47, in __call__\n", | |
| " return JSONAdapter()(lm, lm_kwargs, signature, demos, inputs)\n", | |
| " ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n", | |
| " File \"/Users/duarteocarmo/Repos/taralli-api/.env/lib/python3.12/site-packages/dspy/adapters/json_adapter.py\", line 86, in __call__\n", | |
| " return super().__call__(lm, lm_kwargs, signature, demos, inputs)\n", | |
| " ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n", | |
| " File \"/Users/duarteocarmo/Repos/taralli-api/.env/lib/python3.12/site-packages/dspy/adapters/chat_adapter.py\", line 46, in __call__\n", | |
| " raise e\n", | |
| " File \"/Users/duarteocarmo/Repos/taralli-api/.env/lib/python3.12/site-packages/dspy/adapters/chat_adapter.py\", line 38, in __call__\n", | |
| " return super().__call__(lm, lm_kwargs, signature, demos, inputs)\n", | |
| " ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n", | |
| " File \"/Users/duarteocarmo/Repos/taralli-api/.env/lib/python3.12/site-packages/dspy/adapters/base.py\", line 199, in __call__\n", | |
| " return self._call_postprocess(processed_signature, signature, outputs, lm)\n", | |
| " ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n", | |
| " File \"/Users/duarteocarmo/Repos/taralli-api/.env/lib/python3.12/site-packages/dspy/adapters/base.py\", line 135, in _call_postprocess\n", | |
| " value = self.parse(processed_signature, text)\n", | |
| " ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n", | |
| " File \"/Users/duarteocarmo/Repos/taralli-api/.env/lib/python3.12/site-packages/dspy/utils/callback.py\", line 326, in sync_wrapper\n", | |
| " return fn(instance, *args, **kwargs)\n", | |
| " ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n", | |
| " File \"/Users/duarteocarmo/Repos/taralli-api/.env/lib/python3.12/site-packages/dspy/adapters/json_adapter.py\", line 173, in parse\n", | |
| " fields[k] = parse_value(v, signature.output_fields[k].annotation)\n", | |
| " ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n", | |
| " File \"/Users/duarteocarmo/Repos/taralli-api/.env/lib/python3.12/site-packages/dspy/adapters/utils.py\", line 164, in parse_value\n", | |
| " return TypeAdapter(annotation).validate_python(value)\n", | |
| " ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n", | |
| " File \"/Users/duarteocarmo/Repos/taralli-api/.env/lib/python3.12/site-packages/pydantic/type_adapter.py\", line 421, in validate_python\n", | |
| " return self.validator.validate_python(\n", | |
| " ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n", | |
| "pydantic_core._pydantic_core.ValidationError: 2 validation errors for list[FoodItem]\n", | |
| "1.food_groups.0\n", | |
| " Input should be 'dairy', 'meat and alternatives', 'grain', 'fruit' or 'vegetable' [type=literal_error, input_value='nut and seed', input_type=str]\n", | |
| " For further information visit https://errors.pydantic.dev/2.11/v/literal_error\n", | |
| "3.food_groups.0\n", | |
| " Input should be 'dairy', 'meat and alternatives', 'grain', 'fruit' or 'vegetable' [type=literal_error, input_value='beverage', input_type=str]\n", | |
| " For further information visit https://errors.pydantic.dev/2.11/v/literal_error\n", | |
| "\n", | |
| "2025/12/20 10:00:23 INFO dspy.evaluate.evaluate: Average Metric: 26.0 / 53 (49.1%)\n", | |
| "2025/12/20 10:00:23 INFO dspy.teleprompt.gepa.gepa: Iteration 6: Full valset score for new program: 0.49056603773584906\n", | |
| "2025/12/20 10:00:23 INFO dspy.teleprompt.gepa.gepa: Iteration 6: Full train_val score for new program: 0.49056603773584906\n", | |
| "2025/12/20 10:00:23 INFO dspy.teleprompt.gepa.gepa: Iteration 6: Individual valset scores for new program: [1, 0, 0, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 1, 0, 1, 0, 1, 1, 0, 1, 1, 1, 1, 0, 0, 1, 0, 1, 1, 1, 1, 0, 0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 0, 0.0, 0, 0, 0, 0, 1]\n", | |
| "2025/12/20 10:00:23 INFO dspy.teleprompt.gepa.gepa: Iteration 6: New valset pareto front scores: [1, 0, 0, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 0, 1, 0, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 0, 0, 1]\n", | |
| "2025/12/20 10:00:23 INFO dspy.teleprompt.gepa.gepa: Iteration 6: Full valset pareto front score: 0.7169811320754716\n", | |
| "2025/12/20 10:00:23 INFO dspy.teleprompt.gepa.gepa: Iteration 6: Updated valset pareto front programs: [{3, 4, 5}, {0, 1, 2, 3, 4, 5}, {0, 1, 2, 3, 4, 5}, {1}, {4, 5}, {1, 2, 5}, {3}, {1, 2, 3, 4, 5}, {0, 1, 2, 3, 4, 5}, {1, 3}, {0, 1, 2, 3, 4, 5}, {0, 1, 2, 3, 4, 5}, {0, 1, 2, 3, 4, 5}, {0, 1, 4}, {0, 1, 2, 3, 4, 5}, {0, 1, 2, 3, 4, 5}, {0, 1, 2, 3, 4, 5}, {3}, {1, 2, 3, 4, 5}, {1, 2, 3, 4}, {0, 1, 2, 3, 4, 5}, {1, 2, 3, 4, 5}, {0, 1, 2, 3, 4, 5}, {1, 2, 3, 4, 5}, {0, 1, 2, 3, 4, 5}, {1, 2, 3, 4, 5}, {0, 2, 3, 5}, {0, 1, 2, 3, 4, 5}, {0, 1, 2, 3, 4, 5}, {0, 1, 2, 3, 4, 5}, {0, 1, 2, 3, 4, 5}, {3, 5}, {1, 5}, {1, 2, 3, 4, 5}, {1, 4, 5}, {3, 4}, {1, 3}, {0, 1, 2, 3, 4, 5}, {0, 1, 2, 3, 4, 5}, {0, 1, 2, 3, 4, 5}, {1, 2, 3, 4, 5}, {0, 1, 2, 3}, {4}, {1, 2, 3, 4, 5}, {2, 4, 5}, {5}, {0, 1, 2, 3, 4, 5}, {1, 2, 3, 4}, {3}, {0, 1, 2, 3, 4, 5}, {0, 1, 2, 3, 4, 5}, {0, 1, 2, 3, 4, 5}, {3, 4, 5}]\n", | |
| "2025/12/20 10:00:23 INFO dspy.teleprompt.gepa.gepa: Iteration 6: Best valset aggregate score so far: 0.5471698113207547\n", | |
| "2025/12/20 10:00:23 INFO dspy.teleprompt.gepa.gepa: Iteration 6: Best program as per aggregate score on train_val: 3\n", | |
| "2025/12/20 10:00:23 INFO dspy.teleprompt.gepa.gepa: Iteration 6: Best program as per aggregate score on valset: 3\n", | |
| "2025/12/20 10:00:23 INFO dspy.teleprompt.gepa.gepa: Iteration 6: Best score on valset: 0.5471698113207547\n", | |
| "2025/12/20 10:00:23 INFO dspy.teleprompt.gepa.gepa: Iteration 6: Best score on train_val: 0.5471698113207547\n", | |
| "2025/12/20 10:00:23 INFO dspy.teleprompt.gepa.gepa: Iteration 6: Linear pareto front program index: 3\n", | |
| "2025/12/20 10:00:23 INFO dspy.teleprompt.gepa.gepa: Iteration 6: New program candidate index: 5\n", | |
| "GEPA Optimization: 59%|█████▉ | 351/592 [00:01<00:01, 223.32rollouts/s]2025/12/20 10:00:23 INFO dspy.teleprompt.gepa.gepa: Iteration 7: No merge candidates found\n", | |
| "2025/12/20 10:00:23 INFO dspy.teleprompt.gepa.gepa: Iteration 7: Selected program 4 score: 0.49056603773584906\n" | |
| ] | |
| }, | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "Average Metric: 1.00 / 3 (33.3%): 100%|██████████| 3/3 [00:00<00:00, 2021.68it/s]" | |
| ] | |
| }, | |
| { | |
| "name": "stderr", | |
| "output_type": "stream", | |
| "text": [ | |
| "2025/12/20 10:00:23 INFO dspy.evaluate.evaluate: Average Metric: 1.0 / 3 (33.3%)\n", | |
| "2025/12/20 10:00:23 INFO dspy.teleprompt.gepa.gepa: Iteration 7: Proposed new text for self: You are performing a nutritional analysis from a short free-text food_description. Your job is to parse the described foods and amounts, estimate nutrition, and output a list of FoodItem objects.\n", | |
| "\n", | |
| "Input:\n", | |
| "- A single string field (food_description) describing foods eaten and amounts.\n", | |
| "\n", | |
| "Output:\n", | |
| "- Return ONLY a single Python-style list literal of FoodItem objects, nothing else. Example:\n", | |
| " [FoodItem(name='apple', quantity=1.0, calories=95.0, carbs=25.0, fat=0.3, protein=0.5, fiber=4.4, food_groups=['fruit'])]\n", | |
| "\n", | |
| "FoodItem fields:\n", | |
| "- name: string, concise common name (include brand if specified; e.g., \"Oatly oat milk\").\n", | |
| "- quantity: float, the number of per-unit servings you are using for this item (see quantity rules).\n", | |
| "- calories: float, calories PER ONE UNIT of quantity.\n", | |
| "- carbs, fat, protein, fiber: floats in grams PER ONE UNIT of quantity.\n", | |
| "- food_groups: list of strings (choose from: 'fruit', 'vegetable', 'grain', 'dairy', 'meat and alternatives', 'legume', 'nut and seed', 'fat and oils', 'sweets and snacks', 'beverage'). If unclear, use [].\n", | |
| "\n", | |
| "Critical rule about quantity and per-unit nutrition:\n", | |
| "- The evaluator computes totals as sum(quantity * calories). Therefore, calories and macros MUST be per ONE unit of quantity.\n", | |
| "- Safe approaches:\n", | |
| " - If the description gives a specific portion in grams/ounces/cups/etc. (e.g., “190 g bread”, “1 slice pizza”, “2 cups soup”), compute the TOTAL nutrition for that described portion and set quantity=1.0 with calories/macros equal to that total for the portion.\n", | |
| " - If the description gives a count of identical units (e.g., “2 eggs”, “15 almonds”, “2 little focaccia pieces”), you may set quantity to that count, BUT then calories/macros must be per single unit (e.g., per egg, per almond, per piece). Never provide totals alongside quantity>1, or it will double-count.\n", | |
| " - Fractions of a single unit are allowed (e.g., quantity=0.2 for “0.2 of a pastry”), but then calories/macros must be per whole unit of that item.\n", | |
| "- Never set quantity equal to grams while also providing total calories for that gram amount. Either:\n", | |
| " - Convert to a single portion: quantity=1.0 with calories/macros = totals for that gram amount; OR\n", | |
| " - Use quantity in grams AND provide calories/macros per gram (generally avoid this; prefer quantity=1.0 with totals).\n", | |
| "\n", | |
| "Interpretation and itemization rules:\n", | |
| "- Parse the description to identify distinct foods and their amounts. When amounts are in grams or milliliters, compute totals proportionally from per-100 g or per-100 ml references and set quantity=1.0.\n", | |
| "- Do not invent or add separate items unless clearly present. Treat toppings or descriptors (“with X,” “on a medium crust,” “with fruit”) as part of the same item unless the text clearly indicates an additional, separate serving.\n", | |
| "- If modifiers indicate small pieces (e.g., “little piece”), choose a conservative per-piece size and nutrition consistent with the descriptor.\n", | |
| "- Keep names clear; use singular names with quantity>1 when items are identical units; otherwise describe the whole portion with quantity=1.0.\n", | |
| "- Preserve brand names when given (e.g., “Oatly oat milk”, “Star Nutrition protein isolate”).\n", | |
| "\n", | |
| "Estimation guidance and baselines (tuned to match evaluation):\n", | |
| "- Prefer realistic, conservative estimates when portions are unspecified to avoid overestimation.\n", | |
| "- Ensure macros align with calories (kcal ≈ 4*carbs + 4*protein + 9*fat, allowing for rounding). Include fiber where reasonable.\n", | |
| "\n", | |
| "Common baselines:\n", | |
| "- Cheese pizza, general (includes crust and cheese; toppings are part of this if described):\n", | |
| " - ≈ 238 kcal/100 g; carbs ≈ 30 g, fat ≈ 9 g, protein ≈ 10 g, fiber ≈ 2 g per 100 g.\n", | |
| "- Stracciatella (fresh cheese, dairy component of burrata):\n", | |
| " - ≈ 300 kcal/100 g; carbs ≈ 2 g, fat ≈ 27 g, protein ≈ 12 g, fiber 0 g per 100 g.\n", | |
| " - If portion unspecified in a dish, assume a modest 40–60 g unless context implies more.\n", | |
| "- Confit grapes (grapes cooked in syrup; typically a small garnish):\n", | |
| " - Small garnish ≈ 30–60 kcal total per tablespoon or two; mostly carbs; fiber ≈ 0–1 g.\n", | |
| "- Focaccia, “little piece” (small bite-sized piece ≈ 15–20 g each):\n", | |
| " - ≈ 45–60 kcal per piece; carbs ≈ 8–12 g, fat ≈ 1–2 g, protein ≈ 1–2 g, fiber ≈ 0.3–0.6 g per piece.\n", | |
| " - If text says “2 little pieces,” set quantity=2 with per-piece nutrition.\n", | |
| "- Seabream, stewed/cooked:\n", | |
| " - ≈ 90 kcal/100 g; carbs 0 g, fat ≈ 2 g, protein ≈ 18 g per 100 g.\n", | |
| "- Matabala, boiled (starchy root; very low energy density when boiled):\n", | |
| " - ≈ 30 kcal/100 g; carbs ≈ 7–8 g, fat ≈ 0.1 g, protein ≈ 0.5–1 g, fiber ≈ 1–2 g per 100 g.\n", | |
| "- Rice with vegetables, cooked mixed:\n", | |
| " - ≈ 90 kcal/100 g; carbs ≈ 19 g, fat ≈ 1.0 g, protein ≈ 2.5 g, fiber ≈ 1 g per 100 g.\n", | |
| "\n", | |
| "Additional baselines (from prior tasks; use these to match evaluation expectations):\n", | |
| "- Leavened corn and wheat flour bread (cornbread-like/enriched leavened bread):\n", | |
| " - ≈ 350 kcal/100 g; carbs ≈ 57 g, fat ≈ 10 g, protein ≈ 8 g, fiber ≈ 3 g per 100 g.\n", | |
| "- Spiced butter (seasoned butter; treat as butter):\n", | |
| " - ≈ 720–730 kcal/100 g; carbs ≈ 1 g, fat ≈ 81 g, protein ≈ 1 g, fiber 0 g per 100 g.\n", | |
| "- Fish broth/stock:\n", | |
| " - ≈ 10–12 kcal/100 g; carbs 0 g, fat ≈ 0.2–0.3 g, protein ≈ 1–2 g, fiber 0 g per 100 g.\n", | |
| "- Barley flour porridge (thick cooked porridge from barley flour; water-based unless otherwise stated):\n", | |
| " - ≈ 170 kcal/100 g; carbs ≈ 35 g, fat ≈ 1.5 g, protein ≈ 3.5 g, fiber ≈ 3 g per 100 g.\n", | |
| "- Fried ripe plantains (maduros-style; use conservative oil uptake):\n", | |
| " - ≈ 133 kcal/100 g; carbs ≈ 26 g, fat ≈ 3 g, protein ≈ 1.3 g, fiber ≈ 2.2 g per 100 g.\n", | |
| "- Pecans:\n", | |
| " - One handful ≈ 28–30 g. Per 28 g: ≈ 196 kcal; carbs ≈ 3.9 g, fat ≈ 20.4 g, protein ≈ 2.6 g, fiber ≈ 2.7 g.\n", | |
| " - Per 100 g reference ≈ 691 kcal; carbs ≈ 14 g, fat ≈ 72 g, protein ≈ 9 g, fiber ≈ 10 g.\n", | |
| "- Water:\n", | |
| " - 0 kcal; carbs 0 g, fat 0 g, protein 0 g, fiber 0 g.\n", | |
| "\n", | |
| "Baselines inferred from examples (apply these to stay within expected answers):\n", | |
| "- Pain au chocolat (one standard pastry ≈ 65–75 g):\n", | |
| " - ≈ 300 kcal per pastry; carbs ≈ 33 g, fat ≈ 17 g, protein ≈ 6 g, fiber ≈ 1.5 g per pastry.\n", | |
| " - For “0.2 of a pain au chocolat,” either set quantity=0.2 with these per-pastry values, or set quantity=1.0 with totals for the 20% portion—do not double-count.\n", | |
| "- Durum falafel wrap (whole wrap with falafel, veg, typical sauces; “healthy place” still similar energy):\n", | |
| " - ≈ 550 kcal per wrap; carbs ≈ 60 g, fat ≈ 24 g, protein ≈ 16 g, fiber ≈ 8 g per wrap.\n", | |
| " - Treat as a single mixed item; do not split into separate tortilla/falafel/sauce unless clearly separate servings.\n", | |
| "- French fries, per piece:\n", | |
| " - ≈ 15 kcal per fry; carbs ≈ 1.8 g, fat ≈ 0.7 g, protein ≈ 0.2 g, fiber ≈ 0.2 g per fry.\n", | |
| " - If given a count (e.g., “10 fries”), set quantity to that count and keep per-fry nutrition.\n", | |
| "- Peanut butter (1 tablespoon ≈ 16 g):\n", | |
| " - ≈ 95 kcal per tbsp; carbs ≈ 3.5 g, fat ≈ 8.0 g, protein ≈ 4.0 g, fiber ≈ 1.5 g per tbsp.\n", | |
| "- Protein powder isolate (e.g., unflavoured whey/plant isolate; brand like Star Nutrition):\n", | |
| " - Per 30 g scoop: ≈ 110–120 kcal; carbs ≈ 1–3 g, fat ≈ 0.5–1.5 g, protein ≈ 25 g, fiber ≈ 0 g per 30 g.\n", | |
| "- Oat milk (e.g., Oatly; assume Barista unless specified):\n", | |
| " - ≈ 60 kcal/100 ml; carbs ≈ 6.5–7 g, fat ≈ 3.5 g, protein ≈ 1.0 g, fiber ≈ 0–1 g per 100 ml.\n", | |
| " - For 200 ml, compute totals and set quantity=1.0 with those totals.\n", | |
| " - Plant milks are beverages, not dairy, for food_groups.\n", | |
| "- Coffee, unsweetened black:\n", | |
| " - ≈ 2 kcal/100 ml; negligible macros; set as a beverage.\n", | |
| "\n", | |
| "Food group mapping guidance:\n", | |
| "- Breads, focaccia, rice, pizza crust, porridge (grain-based) -> ['grain'].\n", | |
| "- Cheese, yogurt, stracciatella -> ['dairy'].\n", | |
| "- Fish, chicken, eggs, protein powders/supplements -> ['meat and alternatives'].\n", | |
| "- Falafel, beans, lentils, tofu, tempeh -> ['legume'].\n", | |
| "- Peanut butter, nuts, seeds -> ['nut and seed'].\n", | |
| "- Butter and oils -> ['fat and oils'].\n", | |
| "- Grapes (fresh or confit), plantains -> ['fruit'].\n", | |
| "- Mixed items like “rice with vegetables” -> include both ['grain', 'vegetable'] when appropriate.\n", | |
| "- Water, tea, coffee, plant milks -> ['beverage'].\n", | |
| "- Avoid labeling plant milks as 'dairy'.\n", | |
| "\n", | |
| "Handling target calories in the description:\n", | |
| "- If the description includes an explicit or approximate total calorie target (e.g., “~250 cal”, “total of 700 cals”), ensure the sum over all items (sum of quantity*calories) is within ±10% of that target. Adjust portion assumptions and per-item estimates accordingly, while staying realistic.\n", | |
| "\n", | |
| "Portion and unit heuristics:\n", | |
| "- For gram- or milliliter-specified amounts: compute totals by (amount/100) * per-100 baseline; set quantity=1.0 with those totals.\n", | |
| "- For counted items (pieces, slices): set quantity to the count; provide per-unit nutrition, not totals.\n", | |
| "- For “a handful of nuts”: assume ≈ 28–30 g unless otherwise specified; set quantity=1.0 with totals for that portion.\n", | |
| "- For fractional units (e.g., “0.2 of a pastry”), you may set quantity to the fraction with per-whole-unit nutrition.\n", | |
| "- For “a glass of water”: treat as 0 kcal beverage; set quantity=1.0.\n", | |
| "\n", | |
| "Quality checks before output:\n", | |
| "- Verify total calories = sum(quantity * calories) is sensible and, if a target was stated, within ±10% of that target.\n", | |
| "- Ensure macros roughly align with calories (kcal ≈ 4*carbs + 4*protein + 9*fat, allowing for rounding and fiber).\n", | |
| "- Choose food_groups from the allowed list; avoid leaving [] when a clear mapping exists (e.g., falafel -> ['legume'], peanut butter -> ['nut and seed'], oat milk -> ['beverage']).\n", | |
| "- Do not include any explanatory text—only the list of FoodItem(...) objects.\n", | |
| "\n", | |
| "Implementation tips:\n", | |
| "- Use the baselines above when the exact item matches or is strongly implied; scale by the described grams/ml.\n", | |
| "- When ambiguity exists, choose conservative but realistic portions and energy densities that avoid overestimation while remaining consistent with the baselines and descriptors (e.g., “healthy place” does not imply a dramatically lower-calorie wrap).\n", | |
| "- Keep numeric precision reasonable (typically one decimal place is sufficient).\n", | |
| "2025/12/20 10:00:23 INFO dspy.evaluate.evaluate: Average Metric: 3.0 / 3 (100.0%)\n", | |
| "2025/12/20 10:00:23 INFO dspy.teleprompt.gepa.gepa: Iteration 7: New subsample score 3 is better than old score 1. Continue to full eval and add to candidate pool.\n" | |
| ] | |
| }, | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "\n" | |
| ] | |
| }, | |
| { | |
| "name": "stderr", | |
| "output_type": "stream", | |
| "text": [ | |
| "2025/12/20 10:00:23 INFO dspy.evaluate.evaluate: Average Metric: 33.0 / 53 (62.3%)\n", | |
| "2025/12/20 10:00:23 INFO dspy.teleprompt.gepa.gepa: Iteration 7: New program is on the linear pareto front\n", | |
| "2025/12/20 10:00:23 INFO dspy.teleprompt.gepa.gepa: Iteration 7: Full valset score for new program: 0.6226415094339622\n", | |
| "2025/12/20 10:00:23 INFO dspy.teleprompt.gepa.gepa: Iteration 7: Full train_val score for new program: 0.6226415094339622\n", | |
| "2025/12/20 10:00:23 INFO dspy.teleprompt.gepa.gepa: Iteration 7: Individual valset scores for new program: [1, 0, 0, 0, 1, 1, 1, 1, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 0, 0, 0, 0, 1]\n", | |
| "2025/12/20 10:00:23 INFO dspy.teleprompt.gepa.gepa: Iteration 7: New valset pareto front scores: [1, 0, 0, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1]\n", | |
| "2025/12/20 10:00:23 INFO dspy.teleprompt.gepa.gepa: Iteration 7: Full valset pareto front score: 0.7735849056603774\n", | |
| "2025/12/20 10:00:23 INFO dspy.teleprompt.gepa.gepa: Iteration 7: Updated valset pareto front programs: [{3, 4, 5, 6}, {0, 1, 2, 3, 4, 5, 6}, {0, 1, 2, 3, 4, 5, 6}, {1}, {4, 5, 6}, {1, 2, 5, 6}, {3, 6}, {1, 2, 3, 4, 5, 6}, {0, 1, 2, 3, 4, 5, 6}, {1, 3, 6}, {0, 1, 2, 3, 4, 5, 6}, {0, 1, 2, 3, 4, 5, 6}, {0, 1, 2, 3, 4, 5, 6}, {0, 1, 4}, {0, 1, 2, 3, 4, 5, 6}, {0, 1, 2, 3, 4, 5, 6}, {0, 1, 2, 3, 4, 5, 6}, {3}, {1, 2, 3, 4, 5, 6}, {1, 2, 3, 4}, {0, 1, 2, 3, 4, 5, 6}, {1, 2, 3, 4, 5, 6}, {0, 1, 2, 3, 4, 5, 6}, {1, 2, 3, 4, 5, 6}, {0, 1, 2, 3, 4, 5, 6}, {1, 2, 3, 4, 5, 6}, {0, 2, 3, 5, 6}, {6}, {6}, {0, 1, 2, 3, 4, 5, 6}, {0, 1, 2, 3, 4, 5, 6}, {3, 5}, {1, 5}, {1, 2, 3, 4, 5, 6}, {1, 4, 5, 6}, {3, 4, 6}, {1, 3, 6}, {0, 1, 2, 3, 4, 5, 6}, {0, 1, 2, 3, 4, 5, 6}, {0, 1, 2, 3, 4, 5, 6}, {1, 2, 3, 4, 5, 6}, {0, 1, 2, 3, 6}, {4, 6}, {1, 2, 3, 4, 5, 6}, {2, 4, 5, 6}, {5}, {6}, {1, 2, 3, 4, 6}, {3}, {0, 1, 2, 3, 4, 5, 6}, {0, 1, 2, 3, 4, 5, 6}, {0, 1, 2, 3, 4, 5, 6}, {3, 4, 5, 6}]\n", | |
| "2025/12/20 10:00:23 INFO dspy.teleprompt.gepa.gepa: Iteration 7: Best valset aggregate score so far: 0.6226415094339622\n", | |
| "2025/12/20 10:00:23 INFO dspy.teleprompt.gepa.gepa: Iteration 7: Best program as per aggregate score on train_val: 6\n", | |
| "2025/12/20 10:00:23 INFO dspy.teleprompt.gepa.gepa: Iteration 7: Best program as per aggregate score on valset: 6\n", | |
| "2025/12/20 10:00:23 INFO dspy.teleprompt.gepa.gepa: Iteration 7: Best score on valset: 0.6226415094339622\n", | |
| "2025/12/20 10:00:23 INFO dspy.teleprompt.gepa.gepa: Iteration 7: Best score on train_val: 0.6226415094339622\n", | |
| "2025/12/20 10:00:23 INFO dspy.teleprompt.gepa.gepa: Iteration 7: Linear pareto front program index: 6\n", | |
| "2025/12/20 10:00:23 INFO dspy.teleprompt.gepa.gepa: Iteration 7: New program candidate index: 6\n", | |
| "GEPA Optimization: 69%|██████▉ | 410/592 [00:01<00:00, 215.99rollouts/s]2025/12/20 10:00:23 INFO dspy.teleprompt.gepa.gepa: Iteration 8: No merge candidates found\n", | |
| "2025/12/20 10:00:23 INFO dspy.teleprompt.gepa.gepa: Iteration 8: Selected program 5 score: 0.49056603773584906\n" | |
| ] | |
| }, | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "Average Metric: 0.00 / 3 (0.0%): 100%|██████████| 3/3 [00:00<00:00, 3528.58it/s]" | |
| ] | |
| }, | |
| { | |
| "name": "stderr", | |
| "output_type": "stream", | |
| "text": [ | |
| "2025/12/20 10:00:23 INFO dspy.evaluate.evaluate: Average Metric: 0.0 / 3 (0.0%)\n", | |
| "2025/12/20 10:00:23 INFO dspy.teleprompt.gepa.gepa: Iteration 8: Proposed new text for self: You are performing a nutritional analysis from a short free-text food_description. Your job is to parse the described foods and amounts, estimate nutrition, and output a list of FoodItem objects.\n", | |
| "\n", | |
| "Output format:\n", | |
| "- Return ONLY a single Python-style list literal of FoodItem objects, nothing else. Example:\n", | |
| " [FoodItem(name='apple', quantity=1.0, calories=95.0, carbs=25.0, fat=0.3, protein=0.5, fiber=4.4, food_groups=['fruit'])]\n", | |
| "- Each FoodItem has fields:\n", | |
| " - name: string, concise common name (include brand if specified; e.g., 'Careca palmier' or 'palmier do Careca').\n", | |
| " - quantity: float, the number of per-unit servings you are using for this item (see quantity rules below).\n", | |
| " - calories: float, calories PER ONE UNIT of quantity.\n", | |
| " - carbs, fat, protein, fiber: floats in grams PER ONE UNIT of quantity.\n", | |
| " - food_groups: list of strings (choose from: 'fruit', 'vegetable', 'grain', 'dairy', 'meat and alternatives', 'legume', 'nut and seed', 'fat and oils', 'sweets and snacks', 'beverage'). If unclear, use [].\n", | |
| "\n", | |
| "Critical rule about quantity and per-unit nutrition:\n", | |
| "- The evaluator computes totals as sum(quantity * calories). Therefore, calories/macros MUST be per ONE unit of quantity.\n", | |
| "- Safe approach:\n", | |
| " - If the description gives a specific portion or amount (e.g., “100 g yogurt”, “1 slice pizza”, “half sandwich”), compute the TOTAL nutrition for that described portion and set quantity=1.0 with calories/macros equal to that total for the portion.\n", | |
| " - If the description gives a count of identical units (e.g., “2 eggs”, “15 almonds”, “2 little focaccia pieces”), you may set quantity to that count, BUT then calories/macros must be per single unit (e.g., per egg, per almond, per piece). Never provide totals alongside quantity>1.\n", | |
| "- Do NOT set quantity equal to grams while also providing total calories for that gram amount. Either:\n", | |
| " - Convert to a single portion: quantity=1.0 and calories/macros = totals for that gram amount; OR\n", | |
| " - Use quantity in grams AND provide calories/macros per gram (generally avoid; prefer quantity=1.0 with totals).\n", | |
| "- Fractions like “half/mezzo/meia/mezza”: treat as a specific described portion. Set quantity=1.0 and provide the totals for that half portion. Avoid fractional quantities for these cases.\n", | |
| "\n", | |
| "Interpretation and itemization rules:\n", | |
| "- Parse the description to identify distinct foods and their amounts. When amounts are in grams, compute totals proportionally from per-100 g references and set quantity=1.0.\n", | |
| "- Do not invent or add separate items unless clearly present. Treat toppings, phrases like “with X,” or descriptors like “on a medium crust” as part of the same item unless the text indicates a separate serving.\n", | |
| "- If modifiers indicate small portions (e.g., “little focaccia pieces”), choose a conservative per-piece size consistent with the descriptor.\n", | |
| "- Keep names clear; use singular names with quantity>1 when items are identical units, otherwise describe the whole portion with quantity=1.0.\n", | |
| "- Language cues:\n", | |
| " - “um/uma” (PT), “uno/una” (IT/ES) = 1 unit.\n", | |
| " - “mezzo/mezza” (IT), “meio/meia” (PT) = half; treat as a single half-portion with quantity=1.0.\n", | |
| " - Brand/place indicators like “do/da/de [Brand]” (PT) should be kept in the name as brand context.\n", | |
| "\n", | |
| "Estimation guidance:\n", | |
| "- Prefer realistic, conservative estimates when portions are unspecified to avoid overestimation, but do not undercount obvious mains or full bowls (e.g., a large gram-specified soup should not be treated like plain broth).\n", | |
| "- Use per-100 g baselines and scale to the described grams. Adjust macros so kcal ≈ 4*carbs + 4*protein + 9*fat (allow reasonable rounding).\n", | |
| "- Packaging cues:\n", | |
| " - “a bag” of snacks typically implies a single-serve bag. For dried fruit snacks, assume 1.25–1.5 oz (35–43 g) unless otherwise specified.\n", | |
| " - “small coffee” ~8–12 fl oz.\n", | |
| " - “tablespoon” ~15–20 g for jams/spreads; use 20 g for jam unless otherwise stated.\n", | |
| "- Beverages: include as items (e.g., water, coffee). Water is 0 kcal.\n", | |
| "\n", | |
| "Category baselines (per 100 g unless noted). Use these when applicable; scale by grams if provided, or choose reasonable single-serving totals if not. When a portion is clearly a main (e.g., 500 g pasta, large hearty soup), prefer the higher end of the range to avoid underestimation.\n", | |
| "\n", | |
| "Core/common:\n", | |
| "- Water: 0 kcal; macros 0; food_groups=['beverage'].\n", | |
| "- Brewed coffee, black (small cup): 2–5 kcal per serving (8–12 fl oz); carbs/fat/protein ~0 g; food_groups=['beverage'].\n", | |
| "- Flavored coffee creamer, single-serve cup/pod (10–15 mL): ≈30–45 kcal; carbs ≈4–6 g; fat ≈1.5–2.5 g; protein ≈0 g; food_groups=['dairy'].\n", | |
| "- Regular plain bagel (≈90–105 g): ≈270–300 kcal; carbs ≈53–58 g; fat ≈1–2 g; protein ≈9–11 g; fiber ≈2–3 g; food_groups=['grain'].\n", | |
| "- Jam/jelly (per tablespoon ≈20 g): ≈50 kcal; carbs ≈13 g; fat 0 g; protein 0 g; fiber ≈0–0.5 g; food_groups=['fruit'].\n", | |
| "- Grapes, red/green: ≈69 kcal/100 g; carbs ≈18 g; fat ≈0.2 g; protein ≈0.7 g; fiber ≈0.9 g; food_groups=['fruit'].\n", | |
| "- Rice, cooked:\n", | |
| " - White rice, general: ≈130 kcal/100 g; carbs ≈28 g; fat ≈0.3 g; protein ≈2.4 g; fiber ≈0.3 g; food_groups=['grain'].\n", | |
| " - Glutinous (sticky) rice, steamed/plain (no coconut milk): ≈130–150 kcal/100 g by default when unspecified. If clearly “plain cooked,” you may use ≈100–120 kcal/100 g; choose the higher end if the portion is a main staple.\n", | |
| "- Fish and soups:\n", | |
| " - Fish, white (cooked): ≈90–120 kcal/100 g; carbs 0 g; fat ≈2–5 g; protein ≈18–22 g; food_groups=['meat and alternatives'].\n", | |
| " - Fish broth soups (hearty regional bowls, e.g., striped snakehead fish broth soup): ≈100–130 kcal/100 g unless context specifies “clear/light broth.” Use the higher end for hearty/rich soups.\n", | |
| "\n", | |
| "Additional baselines seen in tasks:\n", | |
| "- Cheese pizza, general: ≈238 kcal/100 g; carbs ≈30 g; fat ≈9 g; protein ≈10 g; fiber ≈2 g; food_groups=['grain'].\n", | |
| "- Stracciatella (fresh cheese): ≈300 kcal/100 g; carbs ≈2 g; fat ≈27 g; protein ≈12 g; fiber 0 g; food_groups=['dairy'].\n", | |
| "- Confit grapes (small garnish): 30–60 kcal total per tablespoon or two; mostly carbs; fiber ≈0–1 g; food_groups=['fruit'].\n", | |
| "- Focaccia, “little piece” (≈15–20 g each): ≈45–60 kcal per piece; carbs ≈8–12 g, fat ≈1–2 g, protein ≈1–2 g, fiber ≈0.3–0.6 g per piece; food_groups=['grain'].\n", | |
| "- Common seabream, cooked: ≈90 kcal/100 g; carbs 0 g; fat ≈2 g; protein ≈18 g; food_groups=['meat and alternatives'].\n", | |
| "- Matabala, boiled: ≈30 kcal/100 g; carbs ≈7–9 g; fat ≈0.1 g; protein ≈0.5–1 g; fiber ≈1–2 g; food_groups=['vegetable'].\n", | |
| "- Rice with vegetables, cooked mixed: ≈90 kcal/100 g; carbs ≈18–20 g; fat ≈0.8–1.2 g; protein ≈2–3 g; fiber ≈1 g; food_groups=['grain', 'vegetable'].\n", | |
| "\n", | |
| "New domain-specific baselines and clarifications (to improve accuracy on prior misses):\n", | |
| "- Palmier pastry (elephant ear; sweet puff pastry, typical bakery piece 50–60 g):\n", | |
| " - Typical single pastry total: ≈220–260 kcal; use 220 kcal when unspecified to be conservative for standard bakery items.\n", | |
| " - Macros per pastry (total): carbs ≈25–30 g; fat ≈12–15 g; protein ≈2–4 g; fiber ≈0.5–1.5 g.\n", | |
| " - Food group: use ['sweets and snacks'] for sweet pastries (not just 'grain').\n", | |
| " - Branding/place (e.g., “do Careca”): include brand in name (e.g., 'palmier do Careca').\n", | |
| "- Pasta with pesto and tomatoes (cooked mixed dish):\n", | |
| " - Use ≈180–200 kcal/100 g by default (pesto significantly raises fat density versus plain pasta).\n", | |
| " - Per 100 g macros: carbs ≈23–26 g; fat ≈7–10 g; protein ≈4–6 g; fiber ≈1.5–2.5 g.\n", | |
| " - For large gram-specified portions (e.g., 500 g), set quantity=1.0 and compute totals; 500 g should land ≈900–1000 kcal.\n", | |
| " - Food groups: ['grain', 'vegetable', 'fat and oils'].\n", | |
| "- Panino caprese (mozzarella, tomato, basil on bread; “panino” size varies by bakery):\n", | |
| " - Whole panino typical total: ≈520–620 kcal; carbs ≈55–65 g; fat ≈22–30 g; protein ≈20–30 g; fiber ≈3–5 g.\n", | |
| " - “Mezzo/mezza” (half) panino: treat as a single half-portion with quantity=1.0 and ≈260–320 kcal (half the whole).\n", | |
| " - Food groups: ['grain', 'dairy', 'vegetable'].\n", | |
| "\n", | |
| "Food group mapping guidance:\n", | |
| "- dairy: milk, yogurt, cheeses (e.g., mozzarella, stracciatella), coffee creamer\n", | |
| "- grain: bread, bagel, rice, pizza crust, focaccia, pasta\n", | |
| "- meat and alternatives: fish (e.g., snakehead, seabream), chicken, eggs, wurst\n", | |
| "- fruit: fresh fruit (e.g., grapes), jams/jellies, dried fruit snacks\n", | |
| "- sweets and snacks: sweet pastries (e.g., palmier), candies, chips\n", | |
| "- beverage: water, coffee, tea, other drinks\n", | |
| "- Include additional groups (vegetable, legume, nut and seed, fat and oils) when clearly applicable.\n", | |
| "\n", | |
| "Handling explicit target calories:\n", | |
| "- If the description includes an explicit or approximate total calorie target (e.g., “~250 cal”, “total of 700 cals”), ensure the sum over all items is within ±10% of that target. Adjust portion assumptions and per-item estimates accordingly, while staying realistic.\n", | |
| "\n", | |
| "Quality checks before output:\n", | |
| "- Verify that total calories = sum(quantity * calories) is sensible, and (if a target was stated) within ±10% of that target.\n", | |
| "- Ensure macros roughly align with calories (kcal ≈ 4*carbs + 4*protein + 9*fat).\n", | |
| "- For gram-specified amounts: compute totals by (grams/100) * per-100 g baseline; set quantity=1.0.\n", | |
| "- For counted items (pieces, slices): set quantity to the count; provide per-unit nutrition, not totals.\n", | |
| "- For fractional portions (half/mezzo/etc.): set quantity=1.0 with totals for that fraction; do not use quantity fractions.\n", | |
| "- When portion sizes are unspecified for multi-component dishes or packaged snacks (“a bag”), assume modest, single-serve amounts consistent with the item category (e.g., dried fruit bag ≈ 40 g ≈ 140 kcal) so totals are not underestimated.\n", | |
| "\n", | |
| "Implementation tips:\n", | |
| "- Use higher-end realistic baselines for main staples and full bowls (e.g., sticky rice, hearty fish soups, pesto pasta) to avoid undercounting when grams are large.\n", | |
| "- Treat add-ins (e.g., coffee creamer) as separate line items if they are separate components.\n", | |
| "- Keep names concise but include brand/place when given (e.g., 'palmier do Careca').\n", | |
| "- Do not include any explanatory text—return only the list of FoodItem(...) objects.\n", | |
| "2025/12/20 10:00:23 WARNING dspy.clients.lm: LM response was truncated due to exceeding max_tokens=None. You can inspect the latest LM interactions with `dspy.inspect_history()`. To avoid truncation, consider passing a larger max_tokens when setting up dspy.LM. You may also consider increasing the temperature (currently 0.0) if the reason for truncation is repetition.\n", | |
| "2025/12/20 10:00:23 INFO dspy.evaluate.evaluate: Average Metric: 3.0 / 3 (100.0%)\n", | |
| "2025/12/20 10:00:23 INFO dspy.teleprompt.gepa.gepa: Iteration 8: New subsample score 3 is better than old score 0. Continue to full eval and add to candidate pool.\n", | |
| "2025/12/20 10:00:23 WARNING dspy.clients.lm: LM response was truncated due to exceeding max_tokens=None. You can inspect the latest LM interactions with `dspy.inspect_history()`. To avoid truncation, consider passing a larger max_tokens when setting up dspy.LM. You may also consider increasing the temperature (currently 0.0) if the reason for truncation is repetition.\n" | |
| ] | |
| }, | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "\n" | |
| ] | |
| }, | |
| { | |
| "name": "stderr", | |
| "output_type": "stream", | |
| "text": [ | |
| "2025/12/20 10:00:23 WARNING dspy.clients.lm: LM response was truncated due to exceeding max_tokens=None. You can inspect the latest LM interactions with `dspy.inspect_history()`. To avoid truncation, consider passing a larger max_tokens when setting up dspy.LM. You may also consider increasing the temperature (currently 0.0) if the reason for truncation is repetition.\n", | |
| "2025/12/20 10:00:23 WARNING dspy.clients.lm: LM response was truncated due to exceeding max_tokens=None. You can inspect the latest LM interactions with `dspy.inspect_history()`. To avoid truncation, consider passing a larger max_tokens when setting up dspy.LM. You may also consider increasing the temperature (currently 0.0) if the reason for truncation is repetition.\n", | |
| "2025/12/20 10:00:24 WARNING dspy.adapters.json_adapter: Failed to use structured output format, falling back to JSON mode.\n", | |
| "2025/12/20 10:00:24 ERROR dspy.utils.parallelizer: Error for Example({'food_description': '30\\u202fg protein‑powder isolate (unflavoured, Star Nutrition) with 1 tbsp organic crunchy peanut butter, 200\\u202fml Oatly milk and 100\\u202fml filtered coffee', 'food_groups': ['dairy', 'meat and alternatives'], 'total_calories': 325.0, 'source': 'golden_dataset'}) (input_keys={'food_description'}): 3 validation errors for list[FoodItem]\n", | |
| "1.food_groups.0\n", | |
| " Input should be 'dairy', 'meat and alternatives', 'grain', 'fruit' or 'vegetable' [type=literal_error, input_value='nut and seed', input_type=str]\n", | |
| " For further information visit https://errors.pydantic.dev/2.11/v/literal_error\n", | |
| "2.food_groups.0\n", | |
| " Input should be 'dairy', 'meat and alternatives', 'grain', 'fruit' or 'vegetable' [type=literal_error, input_value='beverage', input_type=str]\n", | |
| " For further information visit https://errors.pydantic.dev/2.11/v/literal_error\n", | |
| "3.food_groups.0\n", | |
| " Input should be 'dairy', 'meat and alternatives', 'grain', 'fruit' or 'vegetable' [type=literal_error, input_value='beverage', input_type=str]\n", | |
| " For further information visit https://errors.pydantic.dev/2.11/v/literal_error\n", | |
| "Traceback (most recent call last):\n", | |
| " File \"/Users/duarteocarmo/Repos/taralli-api/.env/lib/python3.12/site-packages/dspy/utils/parallelizer.py\", line 57, in safe_func\n", | |
| " return user_function(item)\n", | |
| " ^^^^^^^^^^^^^^^^^^^\n", | |
| " File \"/Users/duarteocarmo/Repos/taralli-api/.env/lib/python3.12/site-packages/dspy/evaluate/evaluate.py\", line 172, in process_item\n", | |
| " prediction = program(**example.inputs())\n", | |
| " ^^^^^^^^^^^^^^^^^^^^^^^^^^^\n", | |
| " File \"/Users/duarteocarmo/Repos/taralli-api/.env/lib/python3.12/site-packages/dspy/predict/predict.py\", line 103, in __call__\n", | |
| " return super().__call__(**kwargs)\n", | |
| " ^^^^^^^^^^^^^^^^^^^^^^^^^^\n", | |
| " File \"/Users/duarteocarmo/Repos/taralli-api/.env/lib/python3.12/site-packages/dspy/utils/callback.py\", line 326, in sync_wrapper\n", | |
| " return fn(instance, *args, **kwargs)\n", | |
| " ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n", | |
| " File \"/Users/duarteocarmo/Repos/taralli-api/.env/lib/python3.12/site-packages/dspy/primitives/module.py\", line 81, in __call__\n", | |
| " return self.forward(*args, **kwargs)\n", | |
| " ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n", | |
| " File \"/Users/duarteocarmo/Repos/taralli-api/.env/lib/python3.12/site-packages/dspy/predict/predict.py\", line 192, in forward\n", | |
| " completions = adapter(lm, lm_kwargs=config, signature=signature, demos=demos, inputs=kwargs)\n", | |
| " ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n", | |
| " File \"/Users/duarteocarmo/Repos/taralli-api/.env/lib/python3.12/site-packages/dspy/adapters/chat_adapter.py\", line 47, in __call__\n", | |
| " return JSONAdapter()(lm, lm_kwargs, signature, demos, inputs)\n", | |
| " ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n", | |
| " File \"/Users/duarteocarmo/Repos/taralli-api/.env/lib/python3.12/site-packages/dspy/adapters/json_adapter.py\", line 86, in __call__\n", | |
| " return super().__call__(lm, lm_kwargs, signature, demos, inputs)\n", | |
| " ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n", | |
| " File \"/Users/duarteocarmo/Repos/taralli-api/.env/lib/python3.12/site-packages/dspy/adapters/chat_adapter.py\", line 46, in __call__\n", | |
| " raise e\n", | |
| " File \"/Users/duarteocarmo/Repos/taralli-api/.env/lib/python3.12/site-packages/dspy/adapters/chat_adapter.py\", line 38, in __call__\n", | |
| " return super().__call__(lm, lm_kwargs, signature, demos, inputs)\n", | |
| " ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n", | |
| " File \"/Users/duarteocarmo/Repos/taralli-api/.env/lib/python3.12/site-packages/dspy/adapters/base.py\", line 199, in __call__\n", | |
| " return self._call_postprocess(processed_signature, signature, outputs, lm)\n", | |
| " ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n", | |
| " File \"/Users/duarteocarmo/Repos/taralli-api/.env/lib/python3.12/site-packages/dspy/adapters/base.py\", line 135, in _call_postprocess\n", | |
| " value = self.parse(processed_signature, text)\n", | |
| " ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n", | |
| " File \"/Users/duarteocarmo/Repos/taralli-api/.env/lib/python3.12/site-packages/dspy/utils/callback.py\", line 326, in sync_wrapper\n", | |
| " return fn(instance, *args, **kwargs)\n", | |
| " ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n", | |
| " File \"/Users/duarteocarmo/Repos/taralli-api/.env/lib/python3.12/site-packages/dspy/adapters/json_adapter.py\", line 173, in parse\n", | |
| " fields[k] = parse_value(v, signature.output_fields[k].annotation)\n", | |
| " ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n", | |
| " File \"/Users/duarteocarmo/Repos/taralli-api/.env/lib/python3.12/site-packages/dspy/adapters/utils.py\", line 164, in parse_value\n", | |
| " return TypeAdapter(annotation).validate_python(value)\n", | |
| " ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n", | |
| " File \"/Users/duarteocarmo/Repos/taralli-api/.env/lib/python3.12/site-packages/pydantic/type_adapter.py\", line 421, in validate_python\n", | |
| " return self.validator.validate_python(\n", | |
| " ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n", | |
| "pydantic_core._pydantic_core.ValidationError: 3 validation errors for list[FoodItem]\n", | |
| "1.food_groups.0\n", | |
| " Input should be 'dairy', 'meat and alternatives', 'grain', 'fruit' or 'vegetable' [type=literal_error, input_value='nut and seed', input_type=str]\n", | |
| " For further information visit https://errors.pydantic.dev/2.11/v/literal_error\n", | |
| "2.food_groups.0\n", | |
| " Input should be 'dairy', 'meat and alternatives', 'grain', 'fruit' or 'vegetable' [type=literal_error, input_value='beverage', input_type=str]\n", | |
| " For further information visit https://errors.pydantic.dev/2.11/v/literal_error\n", | |
| "3.food_groups.0\n", | |
| " Input should be 'dairy', 'meat and alternatives', 'grain', 'fruit' or 'vegetable' [type=literal_error, input_value='beverage', input_type=str]\n", | |
| " For further information visit https://errors.pydantic.dev/2.11/v/literal_error\n", | |
| "\n", | |
| "2025/12/20 10:00:24 INFO dspy.evaluate.evaluate: Average Metric: 29.0 / 53 (54.7%)\n", | |
| "2025/12/20 10:00:24 INFO dspy.teleprompt.gepa.gepa: Iteration 8: Full valset score for new program: 0.5471698113207547\n", | |
| "2025/12/20 10:00:24 INFO dspy.teleprompt.gepa.gepa: Iteration 8: Full train_val score for new program: 0.5471698113207547\n", | |
| "2025/12/20 10:00:24 INFO dspy.teleprompt.gepa.gepa: Iteration 8: Individual valset scores for new program: [1, 0, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 1, 0, 1, 1, 0, 0, 0.0, 0, 0, 0, 0, 0]\n", | |
| "2025/12/20 10:00:24 INFO dspy.teleprompt.gepa.gepa: Iteration 8: New valset pareto front scores: [1, 0, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1]\n", | |
| "2025/12/20 10:00:24 INFO dspy.teleprompt.gepa.gepa: Iteration 8: Full valset pareto front score: 0.7924528301886793\n", | |
| "2025/12/20 10:00:24 INFO dspy.teleprompt.gepa.gepa: Iteration 8: Updated valset pareto front programs: [{3, 4, 5, 6, 7}, {0, 1, 2, 3, 4, 5, 6, 7}, {0, 1, 2, 3, 4, 5, 6, 7}, {1, 7}, {4, 5, 6, 7}, {1, 2, 5, 6, 7}, {3, 6, 7}, {1, 2, 3, 4, 5, 6, 7}, {0, 1, 2, 3, 4, 5, 6, 7}, {1, 3, 6, 7}, {0, 1, 2, 3, 4, 5, 6, 7}, {7}, {0, 1, 2, 3, 4, 5, 6, 7}, {0, 1, 4, 7}, {0, 1, 2, 3, 4, 5, 6, 7}, {0, 1, 2, 3, 4, 5, 6, 7}, {0, 1, 2, 3, 4, 5, 6, 7}, {3, 7}, {1, 2, 3, 4, 5, 6, 7}, {1, 2, 3, 4}, {0, 1, 2, 3, 4, 5, 6, 7}, {1, 2, 3, 4, 5, 6, 7}, {0, 1, 2, 3, 4, 5, 6, 7}, {1, 2, 3, 4, 5, 6, 7}, {0, 1, 2, 3, 4, 5, 6, 7}, {1, 2, 3, 4, 5, 6, 7}, {0, 2, 3, 5, 6, 7}, {6, 7}, {6}, {0, 1, 2, 3, 4, 5, 6, 7}, {0, 1, 2, 3, 4, 5, 6, 7}, {3, 5}, {1, 5}, {1, 2, 3, 4, 5, 6, 7}, {1, 4, 5, 6}, {3, 4, 6}, {1, 3, 6}, {0, 1, 2, 3, 4, 5, 6, 7}, {0, 1, 2, 3, 4, 5, 6, 7}, {0, 1, 2, 3, 4, 5, 6, 7}, {1, 2, 3, 4, 5, 6, 7}, {0, 1, 2, 3, 6, 7}, {4, 6}, {1, 2, 3, 4, 5, 6, 7}, {2, 4, 5, 6, 7}, {5}, {6}, {1, 2, 3, 4, 6}, {3}, {0, 1, 2, 3, 4, 5, 6, 7}, {0, 1, 2, 3, 4, 5, 6, 7}, {0, 1, 2, 3, 4, 5, 6, 7}, {3, 4, 5, 6}]\n", | |
| "2025/12/20 10:00:24 INFO dspy.teleprompt.gepa.gepa: Iteration 8: Best valset aggregate score so far: 0.6226415094339622\n", | |
| "2025/12/20 10:00:24 INFO dspy.teleprompt.gepa.gepa: Iteration 8: Best program as per aggregate score on train_val: 6\n", | |
| "2025/12/20 10:00:24 INFO dspy.teleprompt.gepa.gepa: Iteration 8: Best program as per aggregate score on valset: 6\n", | |
| "2025/12/20 10:00:24 INFO dspy.teleprompt.gepa.gepa: Iteration 8: Best score on valset: 0.6226415094339622\n", | |
| "2025/12/20 10:00:24 INFO dspy.teleprompt.gepa.gepa: Iteration 8: Best score on train_val: 0.6226415094339622\n", | |
| "2025/12/20 10:00:24 INFO dspy.teleprompt.gepa.gepa: Iteration 8: Linear pareto front program index: 6\n", | |
| "2025/12/20 10:00:24 INFO dspy.teleprompt.gepa.gepa: Iteration 8: New program candidate index: 7\n", | |
| "GEPA Optimization: 79%|███████▉ | 469/592 [00:02<00:00, 174.80rollouts/s]2025/12/20 10:00:24 INFO dspy.teleprompt.gepa.gepa: Iteration 9: No merge candidates found\n", | |
| "2025/12/20 10:00:24 INFO dspy.teleprompt.gepa.gepa: Iteration 9: Selected program 6 score: 0.6226415094339622\n" | |
| ] | |
| }, | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "Average Metric: 3.00 / 3 (100.0%): 100%|██████████| 3/3 [00:00<00:00, 3200.94it/s]" | |
| ] | |
| }, | |
| { | |
| "name": "stderr", | |
| "output_type": "stream", | |
| "text": [ | |
| "2025/12/20 10:00:24 INFO dspy.evaluate.evaluate: Average Metric: 3.0 / 3 (100.0%)\n", | |
| "2025/12/20 10:00:24 INFO dspy.teleprompt.gepa.gepa: Iteration 9: All subsample scores perfect. Skipping.\n", | |
| "2025/12/20 10:00:24 INFO dspy.teleprompt.gepa.gepa: Iteration 9: Reflective mutation did not propose a new candidate\n", | |
| "2025/12/20 10:00:24 INFO dspy.teleprompt.gepa.gepa: Iteration 10: Selected program 7 score: 0.5471698113207547\n" | |
| ] | |
| }, | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "\n", | |
| "Average Metric: 1.00 / 3 (33.3%): 100%|██████████| 3/3 [00:00<00:00, 5180.28it/s]" | |
| ] | |
| }, | |
| { | |
| "name": "stderr", | |
| "output_type": "stream", | |
| "text": [ | |
| "2025/12/20 10:00:24 INFO dspy.evaluate.evaluate: Average Metric: 1.0 / 3 (33.3%)\n", | |
| "2025/12/20 10:00:24 INFO dspy.teleprompt.gepa.gepa: Iteration 10: Proposed new text for self: You are performing a nutritional analysis from a short free-text food_description. Parse the described foods and amounts, estimate nutrition, and output a list of FoodItem objects.\n", | |
| "\n", | |
| "Output format (strict):\n", | |
| "- Return ONLY a single Python-style list literal of FoodItem objects, nothing else. Example:\n", | |
| " [FoodItem(name='apple', quantity=1.0, calories=95.0, carbs=25.0, fat=0.3, protein=0.5, fiber=4.4, food_groups=['fruit'])]\n", | |
| "- FoodItem fields:\n", | |
| " - name: concise common name (include brand/place if specified; e.g., 'palmier do Careca').\n", | |
| " - quantity: float = number of per-unit servings you are using for this item (see quantity rules).\n", | |
| " - calories: float = kcal PER ONE UNIT of quantity.\n", | |
| " - carbs, fat, protein, fiber: floats in grams PER ONE UNIT of quantity.\n", | |
| " - food_groups: list of strings chosen from:\n", | |
| " ['fruit', 'vegetable', 'grain', 'dairy', 'meat and alternatives', 'legume', 'nut and seed', 'fat and oils', 'sweets and snacks', 'beverage'].\n", | |
| " If unclear, use [].\n", | |
| "\n", | |
| "Critical rule about quantity and per-unit nutrition:\n", | |
| "- The evaluator totals calories as sum(quantity * calories). Therefore, calories/macros MUST be per ONE unit of quantity.\n", | |
| "- If a specific portion or amount is given (e.g., “100 g yogurt”, “1 slice pizza”, “half sandwich”), compute the TOTAL for that described portion and set quantity=1.0 with calories/macros equal to that total for the portion.\n", | |
| "- If a count of identical units is given (e.g., “2 eggs”, “15 almonds”, “2 little focaccia pieces”), you may set quantity to that count, BUT then calories/macros must be per single unit (not totals). Never mix totals with quantity>1.\n", | |
| "- Do NOT set quantity equal to grams while also providing totals for that gram amount. Either:\n", | |
| " - Convert to a single portion: quantity=1.0 and set totals for that gram amount; OR\n", | |
| " - Use quantity in grams AND provide calories/macros per gram (generally avoid; prefer quantity=1.0 with totals).\n", | |
| "- Fractions like “half/mezzo/meia/mezza”: treat as a single half-portion. Set quantity=1.0 and provide the totals for that half portion (avoid fractional quantities).\n", | |
| "\n", | |
| "Interpretation and itemization rules:\n", | |
| "- Parse the description to identify distinct foods and their amounts. When amounts are in grams, compute totals proportionally from per-100 g references and set quantity=1.0.\n", | |
| "- Do not invent or add separate items unless clearly present. Toppings/modifiers (“with X,” “on a medium crust”) belong to the same item unless the text indicates a separate serving.\n", | |
| "- For vague size descriptors (e.g., “little focaccia pieces”), choose a conservative per-piece size consistent with the descriptor.\n", | |
| "- Keep names clear; use singular with quantity>1 for identical units, otherwise describe the whole portion with quantity=1.0.\n", | |
| "- Language cues:\n", | |
| " - “um/uma” (PT), “uno/una” (IT/ES) = 1 unit.\n", | |
| " - “mezzo/mezza” (IT), “meio/meia” (PT) = half; treat as one half-portion with quantity=1.0.\n", | |
| " - Keep brand/place indicators (e.g., “do/da/de [Brand]”) in the name.\n", | |
| "\n", | |
| "Estimation guidance:\n", | |
| "- Prefer realistic, conservative estimates when portions are unspecified to avoid overestimation, but do not undercount obvious mains or full bowls. Use per-100 g baselines and scale by grams if provided. Adjust macros so kcal ≈ 4*carbs + 4*protein + 9*fat (allow rounding).\n", | |
| "- Packaging/serving cues:\n", | |
| " - “a bag” of snacks: assume single-serve.\n", | |
| " - Dried fruit bag: ≈35–43 g (≈140–170 kcal).\n", | |
| " - “small coffee” ≈8–12 fl oz.\n", | |
| " - “tablespoon” ≈15–20 g for jams/spreads; use 20 g for jam unless otherwise stated.\n", | |
| "- Beverages: include as items (e.g., water, coffee, soda). Water is 0 kcal. For sugar-sweetened sodas, assume a standard can (12 fl oz/355 mL) when unspecified unless context indicates otherwise.\n", | |
| "\n", | |
| "Category baselines (per 100 g unless noted). Use these when applicable; scale by grams if provided, or choose reasonable single-serving totals if not. Use higher-end realistic baselines for main staples/hearty bowls.\n", | |
| "\n", | |
| "Core/common:\n", | |
| "- Water: 0 kcal; macros 0; food_groups=['beverage'].\n", | |
| "- Brewed coffee, black (small cup 8–12 fl oz): 2–5 kcal; carbs/fat/protein ~0 g; food_groups=['beverage'].\n", | |
| "- Flavored coffee creamer, single-serve cup/pod (10–15 mL): ≈30–45 kcal; carbs ≈4–6 g; fat ≈1.5–2.5 g; protein ≈0 g; food_groups=['dairy'].\n", | |
| "- Regular plain bagel (≈90–105 g): ≈270–300 kcal; carbs ≈53–58 g; fat ≈1–2 g; protein ≈9–11 g; fiber ≈2–3 g; food_groups=['grain'].\n", | |
| "- Jam/jelly (per tablespoon ≈20 g): ≈50 kcal; carbs ≈13 g; fat 0 g; protein 0 g; fiber ≈0–0.5 g; food_groups=['fruit'].\n", | |
| "- Grapes, red/green: ≈69 kcal/100 g; carbs ≈18 g; fat ≈0.2 g; protein ≈0.7 g; fiber ≈0.9 g; food_groups=['fruit'].\n", | |
| "- Rice, cooked:\n", | |
| " - White rice: ≈130 kcal/100 g; carbs ≈28 g; fat ≈0.3 g; protein ≈2.4 g; fiber ≈0.3 g; food_groups=['grain'].\n", | |
| " - Glutinous (sticky) rice, steamed/plain: ≈130–150 kcal/100 g by default when unspecified (use higher end if a main staple).\n", | |
| "- Fish and soups:\n", | |
| " - Fish, white (cooked): ≈90–120 kcal/100 g; carbs 0 g; fat ≈2–5 g; protein ≈18–22 g; food_groups=['meat and alternatives'].\n", | |
| " - Hearty fish broth soups (e.g., striped snakehead fish broth soup): ≈100–130 kcal/100 g unless clearly “light/clear.”\n", | |
| "- Cheese pizza, general: ≈238 kcal/100 g; carbs ≈30 g; fat ≈9 g; protein ≈10 g; fiber ≈2 g; food_groups=['grain'].\n", | |
| "- Stracciatella (fresh cheese): ≈300 kcal/100 g; carbs ≈2 g; fat ≈27 g; protein ≈12 g; fiber 0 g; food_groups=['dairy'].\n", | |
| "- Confit grapes (small garnish): 30–60 kcal total per tablespoon or two; mostly carbs; fiber ≈0–1 g; food_groups=['fruit'].\n", | |
| "- Focaccia, “little piece” (≈15–20 g each): ≈45–60 kcal per piece; carbs ≈8–12 g; fat ≈1–2 g; protein ≈1–2 g; fiber ≈0.3–0.6 g; food_groups=['grain'].\n", | |
| "- Common seabream, cooked: ≈90 kcal/100 g; carbs 0 g; fat ≈2 g; protein ≈18 g; food_groups=['meat and alternatives'].\n", | |
| "- Matabala, boiled: ≈30 kcal/100 g; carbs ≈7–9 g; fat ≈0.1 g; protein ≈0.5–1 g; fiber ≈1–2 g; food_groups=['vegetable'].\n", | |
| "- Rice with vegetables, cooked mixed: ≈90 kcal/100 g; carbs ≈18–20 g; fat ≈0.8–1.2 g; protein ≈2–3 g; fiber ≈1 g; food_groups=['grain', 'vegetable'].\n", | |
| "- Palmier pastry (elephant ear; 50–60 g typical single pastry total): ≈220–260 kcal (use 220 kcal if unspecified to be conservative); carbs ≈25–30 g; fat ≈12–15 g; protein ≈2–4 g; fiber ≈0.5–1.5 g; food_groups=['sweets and snacks'].\n", | |
| "- Pasta with pesto and tomatoes (cooked mixed dish): ≈180–200 kcal/100 g; carbs ≈23–26 g; fat ≈7–10 g; protein ≈4–6 g; fiber ≈1.5–2.5 g; food_groups=['grain', 'vegetable', 'fat and oils'].\n", | |
| "- Panino caprese (whole): ≈520–620 kcal; carbs ≈55–65 g; fat ≈22–30 g; protein ≈20–30 g; fiber ≈3–5 g; ['grain', 'dairy', 'vegetable'].\n", | |
| " - “Mezzo/mezza” (half): treat as single half-portion with ≈260–320 kcal; set quantity=1.0.\n", | |
| "\n", | |
| "Additional baselines and clarifications (derived from prior tasks/errors):\n", | |
| "- Mixed stew with chicken, potatoes, vegetables: ≈85–95 kcal/100 g (use ≈90 kcal/100 g by default unless clearly creamy/rich). Per 100 g macros: carbs ≈8–10 g; fat ≈3–4 g; protein ≈5–7 g; fiber ≈1–2 g; food_groups=['meat and alternatives', 'vegetable'].\n", | |
| " - Example: 300 g → ≈270 kcal total; set quantity=1.0 with totals for that portion.\n", | |
| "- Takeout kebab sandwich on baguette (doner-style meat + bread + veg + sauces): typical whole sandwich total ≈550–650 kcal when unspecified; carbs ≈50–60 g; fat ≈22–30 g; protein ≈25–35 g; fiber ≈4–6 g; food_groups=['grain', 'meat and alternatives', 'vegetable'].\n", | |
| "- Fries (takeout/fast-food):\n", | |
| " - Per 100 g ≈300–320 kcal; carbs ≈40 g; fat ≈15–17 g; protein ≈3–4 g; fiber ≈3–4 g; food_groups=['vegetable'].\n", | |
| " - When unspecified in a combo, assume a small/regular single-serve: ≈85–100 g (≈250–320 kcal).\n", | |
| "- Soda (sugar-sweetened; e.g., mango soda): standard can (12 fl oz/355 mL) ≈140–160 kcal; carbs ≈35–41 g; fat 0 g; protein 0 g; fiber 0 g; food_groups=['beverage'].\n", | |
| " - Do NOT tag sodas/soft drinks as 'fruit' or 'vegetable' even if fruit-flavored; use only ['beverage'].\n", | |
| "- Toast with cheese and butter (per toast/slice): ≈170–200 kcal; carbs ≈16–20 g; fat ≈9–11 g; protein ≈6–8 g; fiber ≈1–2 g; food_groups=['grain', 'dairy'].\n", | |
| " - For “2 toasts with cheese and butter,” you may set quantity=2.0 with per-toast nutrition.\n", | |
| "\n", | |
| "Food group mapping guidance:\n", | |
| "- dairy: milk, yogurt, cheeses (mozzarella, stracciatella), coffee creamer.\n", | |
| "- grain: bread, bagel, rice, pizza crust, focaccia, pasta.\n", | |
| "- meat and alternatives: fish (e.g., snakehead, seabream), chicken, eggs, wurst, kebab meat.\n", | |
| "- fruit: fresh fruit, jams/jellies, dried fruit snacks (not sodas).\n", | |
| "- sweets and snacks: sweet pastries (e.g., palmier), candies, chips.\n", | |
| "- beverage: water, coffee/tea, sodas and soft drinks, juices.\n", | |
| "- Include additional groups (vegetable, legume, nut and seed, fat and oils) when clearly applicable.\n", | |
| "\n", | |
| "Handling explicit target calories:\n", | |
| "- If the description includes an explicit/approximate total calorie target (e.g., “~250 cal”, “total of 700 cals”), ensure the sum over all items is within ±10% of that target. Adjust portion assumptions and per-item estimates accordingly, while staying realistic.\n", | |
| "\n", | |
| "Quality checks before output:\n", | |
| "- Verify totals make sense: sum(quantity * calories) should be sensible and (if a target was stated) within ±10% of the target.\n", | |
| "- Ensure macros roughly align with calories (kcal ≈ 4*carbs + 4*protein + 9*fat).\n", | |
| "- For gram-specified amounts: compute totals by (grams/100) * per-100 g baseline; set quantity=1.0.\n", | |
| "- For counted items (pieces, slices): set quantity to the count; provide per-unit nutrition, not totals.\n", | |
| "- For fractional portions (half/mezzo/etc.): set quantity=1.0 with totals for that fraction.\n", | |
| "- When portions are unspecified for takeout combos or packaged snacks, assume modest, single-serve amounts (e.g., small fries, 12-oz soda, typical single sandwich) to avoid overestimation.\n", | |
| "\n", | |
| "Implementation tips:\n", | |
| "- Use higher-end realistic baselines for main staples and hearty bowls (e.g., sticky rice, pesto pasta, hearty soups).\n", | |
| "- Treat separate add-ins (e.g., coffee creamer) as separate items.\n", | |
| "- Keep names concise but include brand/place when given (e.g., 'palmier do Careca').\n", | |
| "- Do not include any explanatory text—return only the list of FoodItem(...) objects.\n", | |
| "2025/12/20 10:00:24 INFO dspy.evaluate.evaluate: Average Metric: 3.0 / 3 (100.0%)\n", | |
| "2025/12/20 10:00:24 INFO dspy.teleprompt.gepa.gepa: Iteration 10: New subsample score 3 is better than old score 1. Continue to full eval and add to candidate pool.\n", | |
| "2025/12/20 10:00:24 WARNING dspy.clients.lm: LM response was truncated due to exceeding max_tokens=None. You can inspect the latest LM interactions with `dspy.inspect_history()`. To avoid truncation, consider passing a larger max_tokens when setting up dspy.LM. You may also consider increasing the temperature (currently 0.0) if the reason for truncation is repetition.\n" | |
| ] | |
| }, | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "\n" | |
| ] | |
| }, | |
| { | |
| "name": "stderr", | |
| "output_type": "stream", | |
| "text": [ | |
| "2025/12/20 10:00:24 WARNING dspy.clients.lm: LM response was truncated due to exceeding max_tokens=None. You can inspect the latest LM interactions with `dspy.inspect_history()`. To avoid truncation, consider passing a larger max_tokens when setting up dspy.LM. You may also consider increasing the temperature (currently 0.0) if the reason for truncation is repetition.\n", | |
| "2025/12/20 10:00:24 INFO dspy.evaluate.evaluate: Average Metric: 35.0 / 53 (66.0%)\n", | |
| "2025/12/20 10:00:24 INFO dspy.teleprompt.gepa.gepa: Iteration 10: New program is on the linear pareto front\n", | |
| "2025/12/20 10:00:24 INFO dspy.teleprompt.gepa.gepa: Iteration 10: Full valset score for new program: 0.660377358490566\n", | |
| "2025/12/20 10:00:24 INFO dspy.teleprompt.gepa.gepa: Iteration 10: Full train_val score for new program: 0.660377358490566\n", | |
| "2025/12/20 10:00:24 INFO dspy.teleprompt.gepa.gepa: Iteration 10: Individual valset scores for new program: [1, 0, 0, 1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 0, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1, 0, 1]\n", | |
| "2025/12/20 10:00:24 INFO dspy.teleprompt.gepa.gepa: Iteration 10: New valset pareto front scores: [1, 0, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1]\n", | |
| "2025/12/20 10:00:24 INFO dspy.teleprompt.gepa.gepa: Iteration 10: Full valset pareto front score: 0.8113207547169812\n", | |
| "2025/12/20 10:00:24 INFO dspy.teleprompt.gepa.gepa: Iteration 10: Updated valset pareto front programs: [{3, 4, 5, 6, 7, 8}, {0, 1, 2, 3, 4, 5, 6, 7, 8}, {0, 1, 2, 3, 4, 5, 6, 7, 8}, {8, 1, 7}, {4, 5, 6, 7}, {1, 2, 5, 6, 7, 8}, {8, 3, 6, 7}, {1, 2, 3, 4, 5, 6, 7, 8}, {0, 1, 2, 3, 4, 5, 6, 7, 8}, {1, 3, 6, 7, 8}, {0, 1, 2, 3, 4, 5, 6, 7, 8}, {8, 7}, {0, 1, 2, 3, 4, 5, 6, 7, 8}, {0, 1, 4, 7, 8}, {0, 1, 2, 3, 4, 5, 6, 7, 8}, {0, 1, 2, 3, 4, 5, 6, 7, 8}, {0, 1, 2, 3, 4, 5, 6, 7, 8}, {8, 3, 7}, {1, 2, 3, 4, 5, 6, 7, 8}, {1, 2, 3, 4}, {0, 1, 2, 3, 4, 5, 6, 7, 8}, {1, 2, 3, 4, 5, 6, 7, 8}, {0, 1, 2, 3, 4, 5, 6, 7, 8}, {1, 2, 3, 4, 5, 6, 7, 8}, {0, 1, 2, 3, 4, 5, 6, 7, 8}, {1, 2, 3, 4, 5, 6, 7, 8}, {0, 2, 3, 5, 6, 7, 8}, {8, 6, 7}, {8, 6}, {0, 1, 2, 3, 4, 5, 6, 7, 8}, {0, 1, 2, 3, 4, 5, 6, 7, 8}, {8, 3, 5}, {1, 5}, {1, 2, 3, 4, 5, 6, 7, 8}, {1, 4, 5, 6, 8}, {8, 3, 4, 6}, {8, 1, 3, 6}, {0, 1, 2, 3, 4, 5, 6, 7, 8}, {0, 1, 2, 3, 4, 5, 6, 7, 8}, {0, 1, 2, 3, 4, 5, 6, 7, 8}, {1, 2, 3, 4, 5, 6, 7, 8}, {0, 1, 2, 3, 6, 7}, {4, 6}, {1, 2, 3, 4, 5, 6, 7, 8}, {2, 4, 5, 6, 7, 8}, {5}, {6}, {1, 2, 3, 4, 6, 8}, {3}, {0, 1, 2, 3, 4, 5, 6, 7, 8}, {8}, {0, 1, 2, 3, 4, 5, 6, 7, 8}, {3, 4, 5, 6, 8}]\n", | |
| "2025/12/20 10:00:24 INFO dspy.teleprompt.gepa.gepa: Iteration 10: Best valset aggregate score so far: 0.660377358490566\n", | |
| "2025/12/20 10:00:24 INFO dspy.teleprompt.gepa.gepa: Iteration 10: Best program as per aggregate score on train_val: 8\n", | |
| "2025/12/20 10:00:24 INFO dspy.teleprompt.gepa.gepa: Iteration 10: Best program as per aggregate score on valset: 8\n", | |
| "2025/12/20 10:00:24 INFO dspy.teleprompt.gepa.gepa: Iteration 10: Best score on valset: 0.660377358490566\n", | |
| "2025/12/20 10:00:24 INFO dspy.teleprompt.gepa.gepa: Iteration 10: Best score on train_val: 0.660377358490566\n", | |
| "2025/12/20 10:00:24 INFO dspy.teleprompt.gepa.gepa: Iteration 10: Linear pareto front program index: 8\n", | |
| "2025/12/20 10:00:24 INFO dspy.teleprompt.gepa.gepa: Iteration 10: New program candidate index: 8\n", | |
| "GEPA Optimization: 90%|████████▉ | 531/592 [00:02<00:00, 167.84rollouts/s]2025/12/20 10:00:24 INFO dspy.teleprompt.gepa.gepa: Iteration 11: No merge candidates found\n", | |
| "2025/12/20 10:00:24 INFO dspy.teleprompt.gepa.gepa: Iteration 11: Selected program 5 score: 0.49056603773584906\n" | |
| ] | |
| }, | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "Average Metric: 2.00 / 3 (66.7%): 100%|██████████| 3/3 [00:00<00:00, 2405.91it/s] " | |
| ] | |
| }, | |
| { | |
| "name": "stderr", | |
| "output_type": "stream", | |
| "text": [ | |
| "2025/12/20 10:00:24 INFO dspy.evaluate.evaluate: Average Metric: 2.0 / 3 (66.7%)\n", | |
| "2025/12/20 10:00:24 INFO dspy.teleprompt.gepa.gepa: Iteration 11: Proposed new text for self: You are performing a nutritional analysis from a short free-text food_description. Your job is to parse the described foods and amounts, estimate nutrition, and output a list of FoodItem objects.\n", | |
| "\n", | |
| "Output format:\n", | |
| "- Return ONLY a single Python-style list literal of FoodItem objects, nothing else. Example:\n", | |
| " [FoodItem(name='apple', quantity=1.0, calories=95.0, carbs=25.0, fat=0.3, protein=0.5, fiber=4.4, food_groups=['fruit'])]\n", | |
| "- Each FoodItem has fields:\n", | |
| " - name: string, concise common name (brand if specified).\n", | |
| " - quantity: float, the number of per-unit servings you are using for this item (see quantity rules below).\n", | |
| " - calories: float, calories PER ONE UNIT of quantity.\n", | |
| " - carbs, fat, protein, fiber: floats in grams PER ONE UNIT of quantity.\n", | |
| " - food_groups: list of strings (choose from: 'fruit', 'vegetable', 'grain', 'dairy', 'meat and alternatives', 'legume', 'nut and seed', 'fat and oils', 'sweets and snacks', 'beverage'). If unclear, use [].\n", | |
| "\n", | |
| "Critical rule about quantity and per-unit nutrition:\n", | |
| "- The evaluator computes totals as sum(quantity * calories). Therefore, calories/macros MUST be per ONE unit of quantity.\n", | |
| "- Safe approach:\n", | |
| " - If the description gives a specific amount (e.g., “100 g yogurt”, “30 g wurst”, “1 slice pizza”), compute the TOTAL nutrition for that described amount and set quantity=1.0 with calories/macros equal to that total for the portion.\n", | |
| " - If the description gives a count of identical units (e.g., “2 eggs”, “15 almonds”, “2 little focaccia pieces”, “2 cookies”), you may set quantity to that count, BUT then calories/macros must be per single unit (e.g., per egg, per almond, per piece, per cookie). Never provide totals alongside quantity>1.\n", | |
| "- Never set quantity equal to grams while also providing total calories for that gram amount. Either:\n", | |
| " - Convert to a single portion: quantity=1.0 and calories/macros = totals for that gram amount; OR\n", | |
| " - Use quantity in grams AND provide calories/macros per gram (generally avoid; prefer quantity=1.0 with totals).\n", | |
| "\n", | |
| "Interpretation and itemization rules:\n", | |
| "- Parse the description to identify distinct foods and their amounts. When amounts are in grams, compute totals proportionally from per-100 g references and set quantity=1.0.\n", | |
| "- Do not invent or add separate items unless clearly present. Treat toppings, phrases like “with X,” flavors (e.g., “rosemary flavored”), or descriptors like “on a medium crust” as part of the same item unless the text indicates a separate serving.\n", | |
| "- If modifiers indicate small portions (e.g., “little focaccia pieces”), choose a conservative per-piece size consistent with the descriptor.\n", | |
| "- Keep names clear; use singular names with quantity>1 when items are identical units; otherwise describe the whole portion with quantity=1.0.\n", | |
| "- Include beverages as items (e.g., water, coffee). Water is 0 kcal.\n", | |
| "\n", | |
| "Estimation guidance:\n", | |
| "- Prefer realistic, conservative estimates when portions are unspecified to avoid overestimation, but do not undercount obvious mains or full bowls (e.g., a large gram-specified soup should not be treated like plain broth).\n", | |
| "- Use per-100 g baselines and scale to the described grams. Adjust macros so kcal ≈ 4*carbs + 4*protein + 9*fat (allow reasonable rounding).\n", | |
| "- Packaging/portion cues:\n", | |
| " - “a bag” of dried fruit snacks typically implies a single-serve bag ≈40 g.\n", | |
| " - “small coffee” ~8–12 fl oz.\n", | |
| " - “tablespoon” ~15–20 g for jams/spreads; use 20 g for jam unless otherwise stated.\n", | |
| " - “a handful” (including other languages, e.g., PT: “uma mão de”) of small dry snacks like toasts/crackers typically ≈30–40 g; use ≈35 g by default for seasoned toasts/crackers to avoid underestimation.\n", | |
| "- Be language-aware for common terms (e.g., PT: “tostas com sabor a rosmaninho” = rosemary-flavored toasts/crackers; “uma mão” = a handful).\n", | |
| "\n", | |
| "Category baselines (per 100 g unless noted), including domain-specific items seen in tasks:\n", | |
| "- Water: 0 kcal; macros 0; food_groups=['beverage'].\n", | |
| "- Brewed coffee, black (small cup): 2–5 kcal per serving (8–12 fl oz), carbs/fat/protein ~0 g; food_groups=['beverage'].\n", | |
| "- Flavored coffee creamer, single-serve cup/pod (10–15 mL): ≈30–45 kcal; carbs ≈4–6 g; fat ≈1.5–2.5 g; protein ≈0 g; food_groups=['dairy'].\n", | |
| "- Regular plain bagel (typical “regular” size ≈ 90–105 g): ≈270–300 kcal; carbs ≈53–58 g; fat ≈1–2 g; protein ≈9–11 g; fiber ≈2–3 g; food_groups=['grain'].\n", | |
| "- Jam/jelly (per tablespoon ≈20 g): ≈50 kcal; carbs ≈13 g; fat 0 g; protein 0 g; fiber ≈0–0.5 g; food_groups=['fruit'].\n", | |
| "- Grapes, red/green: ≈69 kcal/100 g; carbs ≈18 g; fat ≈0.2 g; protein ≈0.7 g; fiber ≈0.9 g; food_groups=['fruit'].\n", | |
| "- Rice, cooked:\n", | |
| " - White rice, general: ≈130 kcal/100 g; carbs ≈28 g; fat ≈0.3 g; protein ≈2.4 g; fiber ≈0.3 g.\n", | |
| " - Glutinous (sticky) rice, steamed/plain (no coconut milk): ≈130–150 kcal/100 g by default when unspecified. If clearly “plain cooked,” you may use ≈100–120 kcal/100 g; choose the higher end if the portion is a main staple to avoid underestimation.\n", | |
| "- Fish and soups:\n", | |
| " - Fish, white (cooked): ≈90–120 kcal/100 g; carbs 0 g; fat ≈2–5 g; protein ≈18–22 g.\n", | |
| " - Fish broth soups (e.g., striped snakehead fish broth soup): for gram-specified full bowls (>250 g), assume a substantive soup: ≈100–130 kcal/100 g unless “clear/light broth” is specified. Use the higher end for hearty/rich soups.\n", | |
| "- Dried apple snacks (“a bag” single-serve): assume ≈40 g per bag; ≈140 kcal per bag; carbs ≈34 g; fat ≈0–1 g; protein ≈0.5 g; fiber ≈3–5 g; food_groups=['fruit'].\n", | |
| "- Cheese pizza, general: ≈238 kcal/100 g; carbs ≈30 g; fat ≈9 g; protein ≈10 g; fiber ≈2 g.\n", | |
| "- Stracciatella (fresh cheese): ≈300 kcal/100 g; carbs ≈2 g; fat ≈27 g; protein ≈12 g; fiber 0 g.\n", | |
| "- Confit grapes (small garnish): 30–60 kcal total per tablespoon or two; mostly carbs; fiber ≈0–1 g.\n", | |
| "- Focaccia, “little piece” (≈15–20 g each): ≈45–60 kcal per piece; carbs ≈8–12 g, fat ≈1–2 g, protein ≈1–2 g, fiber ≈0.3–0.6 g per piece.\n", | |
| "- Common seabream, cooked: ≈90 kcal/100 g; carbs 0 g; fat ≈2 g; protein ≈18 g.\n", | |
| "- Matabala, boiled: ≈30 kcal/100 g; carbs ≈7–9 g; fat ≈0.1 g; protein ≈0.5–1 g; fiber ≈1–2 g.\n", | |
| "- Rice with vegetables, cooked mixed: ≈90 kcal/100 g; carbs ≈18–20 g; fat ≈0.8–1.2 g; protein ≈2–3 g; fiber ≈1 g.\n", | |
| "- Cookies (typical medium cookie ≈14 g): ≈70–80 kcal per cookie; carbs ≈9–11 g; fat ≈3–4 g; protein ≈1 g; fiber ≈0.3–0.5 g; food_groups=['sweets and snacks'].\n", | |
| "- Crackers/toasts/bagel chips, plain or flavored (e.g., rosemary toasts): ≈400–450 kcal/100 g; food_groups=['grain'].\n", | |
| " - “A handful” of small toasts/crackers: assume ≈35 g total (≈140–160 kcal using the above density).\n", | |
| "- Fruit-flavored drink powder (sugary beverage mix, dry powder only): ≈370–390 kcal/100 g; carbs ≈95–98 g; fat ≈0 g; protein ≈0 g; fiber ≈0 g; food_groups=['sweets and snacks'].\n", | |
| " - When mixed with water, count the powder as the caloric item and water as 0 kcal beverage; do not double-count the mixture.\n", | |
| "\n", | |
| "Food group mapping guidance:\n", | |
| "- dairy: milk, yogurt, stracciatella, coffee creamer\n", | |
| "- grain: bread, bagel, rice, pizza crust, focaccia, crackers/toasts\n", | |
| "- meat and alternatives: fish (e.g., snakehead, seabream), chicken, eggs, wurst\n", | |
| "- fruit: grapes (fresh or confit), jams/jellies, dried apple snacks\n", | |
| "- beverage: water, coffee, tea, other drinks (include water explicitly as beverage)\n", | |
| "- sweets and snacks: cookies, candies, sugary drink powders/snack-like sweets\n", | |
| "- Include additional groups (vegetable, legume, nut and seed, fat and oils) when clearly applicable.\n", | |
| "\n", | |
| "Handling explicit target calories:\n", | |
| "- If the description includes an explicit or approximate total calorie target (e.g., “~250 cal”, “total of 700 cals”), ensure the sum over all items is within ±10% of that target. Adjust portion assumptions and per-item estimates accordingly, while staying realistic.\n", | |
| "\n", | |
| "Quality checks before output:\n", | |
| "- Verify that total calories = sum(quantity * calories) is sensible, and (if a target was stated) within ±10% of that target.\n", | |
| "- Ensure macros roughly align with calories (kcal ≈ 4*carbs + 4*protein + 9*fat).\n", | |
| "- For gram-specified amounts: compute totals by (grams/100) * per-100 g baseline; set quantity=1.0.\n", | |
| "- For counted items (pieces, slices): set quantity to the count; provide per-unit nutrition, not totals.\n", | |
| "- When portion sizes are unspecified for multi-component dishes or packaged snacks (“a bag”, “a handful”), assume modest, single-serve amounts consistent with the item category (e.g., dried fruit bag ≈40 g ≈140 kcal; handful of toasts/crackers ≈35 g ≈150 kcal) so totals are not underestimated.\n", | |
| "\n", | |
| "Implementation tips:\n", | |
| "- Use higher-end realistic baselines for main staples and full bowls (e.g., sticky rice, hearty fish soups) to avoid undercounting when grams are large.\n", | |
| "- Treat add-ins (e.g., coffee creamer) as separate line items if they are separate components.\n", | |
| "- Do not include any explanatory text—return only the list of FoodItem(...) objects.\n", | |
| "2025/12/20 10:00:24 INFO dspy.evaluate.evaluate: Average Metric: 3.0 / 3 (100.0%)\n", | |
| "2025/12/20 10:00:24 INFO dspy.teleprompt.gepa.gepa: Iteration 11: New subsample score 3 is better than old score 2. Continue to full eval and add to candidate pool.\n" | |
| ] | |
| }, | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "\n" | |
| ] | |
| }, | |
| { | |
| "name": "stderr", | |
| "output_type": "stream", | |
| "text": [ | |
| "2025/12/20 10:00:24 WARNING dspy.adapters.json_adapter: Failed to use structured output format, falling back to JSON mode.\n", | |
| "2025/12/20 10:00:24 WARNING dspy.clients.lm: LM response was truncated due to exceeding max_tokens=None. You can inspect the latest LM interactions with `dspy.inspect_history()`. To avoid truncation, consider passing a larger max_tokens when setting up dspy.LM. You may also consider increasing the temperature (currently 0.0) if the reason for truncation is repetition.\n", | |
| "2025/12/20 10:00:24 WARNING dspy.adapters.json_adapter: Failed to use structured output format, falling back to JSON mode.\n", | |
| "2025/12/20 10:00:24 ERROR dspy.utils.parallelizer: Error for Example({'food_description': '30\\u202fg protein‑powder isolate (unflavoured, Star Nutrition) with 1 tbsp organic crunchy peanut butter, 200\\u202fml Oatly milk and 100\\u202fml filtered coffee', 'food_groups': ['dairy', 'meat and alternatives'], 'total_calories': 325.0, 'source': 'golden_dataset'}) (input_keys={'food_description'}): 2 validation errors for list[FoodItem]\n", | |
| "1.food_groups.0\n", | |
| " Input should be 'dairy', 'meat and alternatives', 'grain', 'fruit' or 'vegetable' [type=literal_error, input_value='nut and seed', input_type=str]\n", | |
| " For further information visit https://errors.pydantic.dev/2.11/v/literal_error\n", | |
| "3.food_groups.0\n", | |
| " Input should be 'dairy', 'meat and alternatives', 'grain', 'fruit' or 'vegetable' [type=literal_error, input_value='beverage', input_type=str]\n", | |
| " For further information visit https://errors.pydantic.dev/2.11/v/literal_error\n", | |
| "Traceback (most recent call last):\n", | |
| " File \"/Users/duarteocarmo/Repos/taralli-api/.env/lib/python3.12/site-packages/dspy/utils/parallelizer.py\", line 57, in safe_func\n", | |
| " return user_function(item)\n", | |
| " ^^^^^^^^^^^^^^^^^^^\n", | |
| " File \"/Users/duarteocarmo/Repos/taralli-api/.env/lib/python3.12/site-packages/dspy/evaluate/evaluate.py\", line 172, in process_item\n", | |
| " prediction = program(**example.inputs())\n", | |
| " ^^^^^^^^^^^^^^^^^^^^^^^^^^^\n", | |
| " File \"/Users/duarteocarmo/Repos/taralli-api/.env/lib/python3.12/site-packages/dspy/predict/predict.py\", line 103, in __call__\n", | |
| " return super().__call__(**kwargs)\n", | |
| " ^^^^^^^^^^^^^^^^^^^^^^^^^^\n", | |
| " File \"/Users/duarteocarmo/Repos/taralli-api/.env/lib/python3.12/site-packages/dspy/utils/callback.py\", line 326, in sync_wrapper\n", | |
| " return fn(instance, *args, **kwargs)\n", | |
| " ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n", | |
| " File \"/Users/duarteocarmo/Repos/taralli-api/.env/lib/python3.12/site-packages/dspy/primitives/module.py\", line 81, in __call__\n", | |
| " return self.forward(*args, **kwargs)\n", | |
| " ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n", | |
| " File \"/Users/duarteocarmo/Repos/taralli-api/.env/lib/python3.12/site-packages/dspy/predict/predict.py\", line 192, in forward\n", | |
| " completions = adapter(lm, lm_kwargs=config, signature=signature, demos=demos, inputs=kwargs)\n", | |
| " ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n", | |
| " File \"/Users/duarteocarmo/Repos/taralli-api/.env/lib/python3.12/site-packages/dspy/adapters/chat_adapter.py\", line 47, in __call__\n", | |
| " return JSONAdapter()(lm, lm_kwargs, signature, demos, inputs)\n", | |
| " ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n", | |
| " File \"/Users/duarteocarmo/Repos/taralli-api/.env/lib/python3.12/site-packages/dspy/adapters/json_adapter.py\", line 86, in __call__\n", | |
| " return super().__call__(lm, lm_kwargs, signature, demos, inputs)\n", | |
| " ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n", | |
| " File \"/Users/duarteocarmo/Repos/taralli-api/.env/lib/python3.12/site-packages/dspy/adapters/chat_adapter.py\", line 46, in __call__\n", | |
| " raise e\n", | |
| " File \"/Users/duarteocarmo/Repos/taralli-api/.env/lib/python3.12/site-packages/dspy/adapters/chat_adapter.py\", line 38, in __call__\n", | |
| " return super().__call__(lm, lm_kwargs, signature, demos, inputs)\n", | |
| " ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n", | |
| " File \"/Users/duarteocarmo/Repos/taralli-api/.env/lib/python3.12/site-packages/dspy/adapters/base.py\", line 199, in __call__\n", | |
| " return self._call_postprocess(processed_signature, signature, outputs, lm)\n", | |
| " ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n", | |
| " File \"/Users/duarteocarmo/Repos/taralli-api/.env/lib/python3.12/site-packages/dspy/adapters/base.py\", line 135, in _call_postprocess\n", | |
| " value = self.parse(processed_signature, text)\n", | |
| " ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n", | |
| " File \"/Users/duarteocarmo/Repos/taralli-api/.env/lib/python3.12/site-packages/dspy/utils/callback.py\", line 326, in sync_wrapper\n", | |
| " return fn(instance, *args, **kwargs)\n", | |
| " ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n", | |
| " File \"/Users/duarteocarmo/Repos/taralli-api/.env/lib/python3.12/site-packages/dspy/adapters/json_adapter.py\", line 173, in parse\n", | |
| " fields[k] = parse_value(v, signature.output_fields[k].annotation)\n", | |
| " ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n", | |
| " File \"/Users/duarteocarmo/Repos/taralli-api/.env/lib/python3.12/site-packages/dspy/adapters/utils.py\", line 164, in parse_value\n", | |
| " return TypeAdapter(annotation).validate_python(value)\n", | |
| " ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n", | |
| " File \"/Users/duarteocarmo/Repos/taralli-api/.env/lib/python3.12/site-packages/pydantic/type_adapter.py\", line 421, in validate_python\n", | |
| " return self.validator.validate_python(\n", | |
| " ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n", | |
| "pydantic_core._pydantic_core.ValidationError: 2 validation errors for list[FoodItem]\n", | |
| "1.food_groups.0\n", | |
| " Input should be 'dairy', 'meat and alternatives', 'grain', 'fruit' or 'vegetable' [type=literal_error, input_value='nut and seed', input_type=str]\n", | |
| " For further information visit https://errors.pydantic.dev/2.11/v/literal_error\n", | |
| "3.food_groups.0\n", | |
| " Input should be 'dairy', 'meat and alternatives', 'grain', 'fruit' or 'vegetable' [type=literal_error, input_value='beverage', input_type=str]\n", | |
| " For further information visit https://errors.pydantic.dev/2.11/v/literal_error\n", | |
| "\n", | |
| "2025/12/20 10:00:24 INFO dspy.evaluate.evaluate: Average Metric: 30.0 / 53 (56.6%)\n", | |
| "2025/12/20 10:00:24 INFO dspy.teleprompt.gepa.gepa: Iteration 11: Full valset score for new program: 0.5660377358490566\n", | |
| "2025/12/20 10:00:24 INFO dspy.teleprompt.gepa.gepa: Iteration 11: Full train_val score for new program: 0.5660377358490566\n", | |
| "2025/12/20 10:00:24 INFO dspy.teleprompt.gepa.gepa: Iteration 11: Individual valset scores for new program: [1, 0, 0, 0, 1, 1, 0, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 0, 1, 0, 1, 1, 0, 1, 1, 1, 1, 0, 0, 1, 0, 0, 1, 1, 0, 1, 1, 0, 1, 0, 1, 1, 0, 1, 1, 1, 0, 0.0, 1, 0, 0, 0, 1]\n", | |
| "2025/12/20 10:00:24 INFO dspy.teleprompt.gepa.gepa: Iteration 11: New valset pareto front scores: [1, 0, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1]\n", | |
| "2025/12/20 10:00:24 INFO dspy.teleprompt.gepa.gepa: Iteration 11: Full valset pareto front score: 0.8113207547169812\n", | |
| "2025/12/20 10:00:24 INFO dspy.teleprompt.gepa.gepa: Iteration 11: Updated valset pareto front programs: [{3, 4, 5, 6, 7, 8, 9}, {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}, {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}, {8, 1, 7}, {4, 5, 6, 7, 9}, {1, 2, 5, 6, 7, 8, 9}, {8, 3, 6, 7}, {1, 2, 3, 4, 5, 6, 7, 8, 9}, {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}, {1, 3, 6, 7, 8, 9}, {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}, {8, 7}, {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}, {0, 1, 4, 7, 8, 9}, {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}, {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}, {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}, {8, 3, 7}, {1, 2, 3, 4, 5, 6, 7, 8, 9}, {1, 2, 3, 4}, {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}, {1, 2, 3, 4, 5, 6, 7, 8, 9}, {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}, {1, 2, 3, 4, 5, 6, 7, 8, 9}, {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}, {1, 2, 3, 4, 5, 6, 7, 8, 9}, {0, 2, 3, 5, 6, 7, 8, 9}, {8, 6, 7}, {8, 6}, {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}, {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}, {8, 3, 5}, {1, 5, 9}, {1, 2, 3, 4, 5, 6, 7, 8, 9}, {1, 4, 5, 6, 8}, {3, 4, 6, 8, 9}, {1, 3, 6, 8, 9}, {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}, {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}, {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}, {1, 2, 3, 4, 5, 6, 7, 8, 9}, {0, 1, 2, 3, 6, 7, 9}, {4, 6}, {1, 2, 3, 4, 5, 6, 7, 8, 9}, {2, 4, 5, 6, 7, 8, 9}, {9, 5}, {6}, {1, 2, 3, 4, 6, 8}, {9, 3}, {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}, {8}, {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}, {3, 4, 5, 6, 8, 9}]\n", | |
| "2025/12/20 10:00:24 INFO dspy.teleprompt.gepa.gepa: Iteration 11: Best valset aggregate score so far: 0.660377358490566\n", | |
| "2025/12/20 10:00:24 INFO dspy.teleprompt.gepa.gepa: Iteration 11: Best program as per aggregate score on train_val: 8\n", | |
| "2025/12/20 10:00:24 INFO dspy.teleprompt.gepa.gepa: Iteration 11: Best program as per aggregate score on valset: 8\n", | |
| "2025/12/20 10:00:24 INFO dspy.teleprompt.gepa.gepa: Iteration 11: Best score on valset: 0.660377358490566\n", | |
| "2025/12/20 10:00:24 INFO dspy.teleprompt.gepa.gepa: Iteration 11: Best score on train_val: 0.660377358490566\n", | |
| "2025/12/20 10:00:24 INFO dspy.teleprompt.gepa.gepa: Iteration 11: Linear pareto front program index: 8\n", | |
| "2025/12/20 10:00:24 INFO dspy.teleprompt.gepa.gepa: Iteration 11: New program candidate index: 9\n", | |
| "GEPA Optimization: 100%|█████████▉| 590/592 [00:02<00:00, 176.47rollouts/s]2025/12/20 10:00:24 INFO dspy.teleprompt.gepa.gepa: Iteration 12: No merge candidates found\n", | |
| "2025/12/20 10:00:24 INFO dspy.teleprompt.gepa.gepa: Iteration 12: Selected program 6 score: 0.6226415094339622\n" | |
| ] | |
| }, | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "Average Metric: 2.00 / 3 (66.7%): 100%|██████████| 3/3 [00:00<00:00, 3504.01it/s]" | |
| ] | |
| }, | |
| { | |
| "name": "stderr", | |
| "output_type": "stream", | |
| "text": [ | |
| "2025/12/20 10:00:24 INFO dspy.evaluate.evaluate: Average Metric: 2.0 / 3 (66.7%)\n", | |
| "2025/12/20 10:00:24 INFO dspy.teleprompt.gepa.gepa: Iteration 12: Proposed new text for self: You are performing a nutritional analysis from a short free-text food_description. Your job is to parse the described foods and amounts, estimate nutrition, and output a list of FoodItem objects.\n", | |
| "\n", | |
| "Input:\n", | |
| "- A single string field (food_description) describing foods eaten and amounts.\n", | |
| "\n", | |
| "Output:\n", | |
| "- Return ONLY a single Python-style list literal of FoodItem objects, nothing else. Example:\n", | |
| " [FoodItem(name='apple', quantity=1.0, calories=95.0, carbs=25.0, fat=0.3, protein=0.5, fiber=4.4, food_groups=['fruit'])]\n", | |
| "\n", | |
| "FoodItem fields:\n", | |
| "- name: string, concise common name (include brand if specified; e.g., \"Oatly oat milk\").\n", | |
| "- quantity: float, the number of per-unit servings you are using for this item (see quantity rules).\n", | |
| "- calories: float, calories PER ONE UNIT of quantity.\n", | |
| "- carbs, fat, protein, fiber: floats in grams PER ONE UNIT of quantity.\n", | |
| "- food_groups: list of strings (choose from: 'fruit', 'vegetable', 'grain', 'dairy', 'meat and alternatives', 'legume', 'nut and seed', 'fat and oils', 'sweets and snacks', 'beverage'). If unclear, use [].\n", | |
| "\n", | |
| "Critical rule about quantity and per-unit nutrition:\n", | |
| "- The evaluator computes totals as sum(quantity * calories). Therefore, calories and macros MUST be per ONE unit of quantity.\n", | |
| "- Safe approaches:\n", | |
| " - If the description gives a specific portion in grams/ounces/milliliters/cups/etc. (e.g., “190 g bread”, “1 slice pizza”, “2 cups soup”, “620 ml bottle”), compute the TOTAL nutrition for that described portion and set quantity=1.0 with calories/macros equal to that total for the portion.\n", | |
| " - If the description gives a count of identical units (e.g., “2 eggs”, “15 almonds”, “2 little focaccia pieces”), you may set quantity to that count, BUT then calories/macros must be per single unit (e.g., per egg, per almond, per piece). Never provide totals alongside quantity>1, or it will double-count.\n", | |
| " - Fractions of a single unit are allowed (e.g., quantity=0.2 for “0.2 of a pastry”), but then calories/macros must be per whole unit of that item.\n", | |
| "- Never set quantity equal to grams while also providing total calories for that gram amount. Either:\n", | |
| " - Convert to a single portion: quantity=1.0 with calories/macros = totals for that gram/ml amount; OR\n", | |
| " - Use quantity in grams/ml AND provide calories/macros per gram/ml (generally avoid this; prefer quantity=1.0 with totals).\n", | |
| "\n", | |
| "Interpretation and itemization rules:\n", | |
| "- Parse the description to identify distinct foods and their amounts. When amounts are in grams or milliliters, compute totals proportionally from per-100 g or per-100 ml references and set quantity=1.0.\n", | |
| "- Do not invent or add separate items unless clearly present. Treat toppings or descriptors (“with X,” “on a medium crust,” “with fruit”) as part of the same item unless the text clearly indicates an additional, separate serving.\n", | |
| "- If modifiers indicate small pieces (e.g., “little piece”), choose a conservative per-piece size and nutrition consistent with the descriptor.\n", | |
| "- Keep names clear; use singular names with quantity>1 when items are identical units; otherwise describe the whole portion with quantity=1.0.\n", | |
| "- Preserve brand names when given (e.g., “Oatly oat milk”, “Star Nutrition protein isolate”).\n", | |
| "\n", | |
| "Packaging and default size heuristics (important for accuracy):\n", | |
| "- “Bottle” of soda: assume 20 fl oz (591 ml) unless otherwise specified (if it says “small bottle,” assume 500 ml; “can” defaults to 12 fl oz/355 ml).\n", | |
| "- “Bottle” of iced tea: if not specified as “sweetened,” assume unsweetened tea (≈ 0–2 kcal/100 ml). If explicitly “sweetened” or similar, treat like soda ≈ 42–45 kcal/100 ml.\n", | |
| "- “Small bag” of chips/crackers: assume ≈ 1.5 oz (42–45 g). A “snack-size” bag is often 1.0–1.5 oz; choose 1.5 oz unless context implies the smallest 1.0 oz bag.\n", | |
| "- If grams/ml are provided for the entire packaged item, always compute totals from that weight/volume and set quantity=1.0.\n", | |
| "\n", | |
| "Estimation guidance and baselines (tuned to match evaluation):\n", | |
| "- Prefer realistic, conservative estimates when portions are unspecified; however, when packaging implies a standard larger size (e.g., “bottle” of soda), use the standard package assumption above rather than a smaller “dieting” portion.\n", | |
| "- Ensure macros align with calories (kcal ≈ 4*carbs + 4*protein + 9*fat, allowing for rounding). Include fiber where reasonable.\n", | |
| "\n", | |
| "Core baselines (per 100 g unless stated; scale by portion and set quantity=1.0 for gram/ml-specified items):\n", | |
| "- Cheese pizza, general (includes crust and cheese; toppings are part of this if described):\n", | |
| " - ≈ 238 kcal/100 g; carbs ≈ 30 g, fat ≈ 9 g, protein ≈ 10 g, fiber ≈ 2 g per 100 g.\n", | |
| "- Stracciatella (fresh cheese, dairy component of burrata):\n", | |
| " - ≈ 300 kcal/100 g; carbs ≈ 2 g, fat ≈ 27 g, protein ≈ 12 g, fiber 0 g per 100 g.\n", | |
| " - If portion unspecified in a dish, assume a modest 40–60 g unless context implies more.\n", | |
| "- Confit grapes (garnish):\n", | |
| " - Small garnish ≈ 30–60 kcal total per tablespoon or two; mostly carbs; fiber ≈ 0–1 g.\n", | |
| "- Focaccia, “little piece” (small bite-sized piece ≈ 15–20 g each):\n", | |
| " - ≈ 45–60 kcal per piece; carbs ≈ 8–12 g, fat ≈ 1–2 g, protein ≈ 1–2 g, fiber ≈ 0.3–0.6 g per piece.\n", | |
| " - If text says “2 little pieces,” set quantity=2 with per-piece nutrition.\n", | |
| "- Seabream, stewed/cooked:\n", | |
| " - ≈ 90 kcal/100 g; carbs 0 g, fat ≈ 2 g, protein ≈ 18 g per 100 g.\n", | |
| "- Matabala, boiled (starchy root; very low energy density when boiled):\n", | |
| " - ≈ 30 kcal/100 g; carbs ≈ 7–8 g, fat ≈ 0.1 g, protein ≈ 0.5–1 g, fiber ≈ 1–2 g per 100 g.\n", | |
| "- Rice with vegetables, cooked mixed:\n", | |
| " - ≈ 90 kcal/100 g; carbs ≈ 19 g, fat ≈ 1.0 g, protein ≈ 2.5 g, fiber ≈ 1 g per 100 g.\n", | |
| "\n", | |
| "Additional baselines (from prior tasks; use these to match evaluation expectations):\n", | |
| "- Leavened corn and wheat flour bread (cornbread-like/enriched leavened bread):\n", | |
| " - ≈ 350 kcal/100 g; carbs ≈ 57 g, fat ≈ 10 g, protein ≈ 8 g, fiber ≈ 3 g per 100 g.\n", | |
| "- Spiced butter (seasoned butter; treat as butter):\n", | |
| " - ≈ 720–730 kcal/100 g; carbs ≈ 1 g, fat ≈ 81 g, protein ≈ 1 g, fiber 0 g per 100 g.\n", | |
| "- Fish broth/stock:\n", | |
| " - ≈ 10–12 kcal/100 g; carbs 0 g, fat ≈ 0.2–0.3 g, protein ≈ 1–2 g, fiber 0 g per 100 g.\n", | |
| "- Barley flour porridge (thick cooked porridge, water-based unless stated):\n", | |
| " - ≈ 170 kcal/100 g; carbs ≈ 35 g, fat ≈ 1.5 g, protein ≈ 3.5 g, fiber ≈ 3 g per 100 g.\n", | |
| "- Fried ripe plantains (maduros-style; conservative oil uptake):\n", | |
| " - ≈ 133 kcal/100 g; carbs ≈ 26 g, fat ≈ 3 g, protein ≈ 1.3 g, fiber ≈ 2.2 g per 100 g.\n", | |
| "- Pecans:\n", | |
| " - One handful ≈ 28–30 g. Per 28 g: ≈ 196 kcal; carbs ≈ 3.9 g, fat ≈ 20.4 g, protein ≈ 2.6 g, fiber ≈ 2.7 g.\n", | |
| " - Per 100 g reference ≈ 691 kcal; carbs ≈ 14 g, fat ≈ 72 g, protein ≈ 9 g, fiber ≈ 10 g.\n", | |
| "- Water:\n", | |
| " - 0 kcal; carbs 0 g, fat 0 g, protein 0 g, fiber 0 g.\n", | |
| "\n", | |
| "Baselines inferred from examples (apply these to stay within expected answers):\n", | |
| "- Chicken submarine sandwich with cheese/veg/spread (whole mixed item; use gram/ml scaling when provided):\n", | |
| " - ≈ 200 kcal/100 g; carbs ≈ 20 g, fat ≈ 9 g, protein ≈ 10 g, fiber ≈ 1 g per 100 g.\n", | |
| " - If total weight is given (e.g., “520 g”), compute totals and set quantity=1.0 with those totals.\n", | |
| "- Iced green tea, bottled:\n", | |
| " - If not explicitly “sweetened,” assume unsweetened ≈ 2 kcal/100 ml; carbs/fat/protein ≈ 0; fiber 0; food_groups=['beverage'].\n", | |
| " - If clearly sweetened, use soda baseline below.\n", | |
| "- Cola (regular, not diet):\n", | |
| " - ≈ 42–43 kcal/100 ml; carbs ≈ 10.6–11 g/100 ml; fat/protein/fiber ≈ 0.\n", | |
| " - Packaging defaults: can 355 ml ≈ 150 kcal; bottle 591 ml ≈ 240–255 kcal; 500 ml ≈ 210–220 kcal. Use “bottle”=591 ml unless stated otherwise.\n", | |
| "- Cheez-It crackers, small bag:\n", | |
| " - “Small bag/snack bag” ≈ 42–45 g: ≈ 200–240 kcal total; carbs ≈ 25–30 g, fat ≈ 10–12 g, protein ≈ 4–5 g, fiber ≈ 1–2 g. food_groups=['grain','dairy'].\n", | |
| " - Per 28 g (1 oz) reference ≈ 150 kcal; carbs ≈ 17 g, fat ≈ 8 g, protein ≈ 3 g, fiber ≈ 1 g.\n", | |
| "- Chocolate ice cream sandwich (prepackaged; whole sandwich):\n", | |
| " - ≈ 270 kcal/100 g. If a 75 g sandwich: ≈ 200–205 kcal total.\n", | |
| " - Macro profile per 100 g typical: carbs ≈ 34 g, fat ≈ 13 g, protein ≈ 4 g, fiber ≈ 1 g. Scale by weight.\n", | |
| "\n", | |
| "Other useful baselines (from prior tasks; apply as described):\n", | |
| "- Pain au chocolat (one standard pastry ≈ 65–75 g):\n", | |
| " - ≈ 300 kcal per pastry; carbs ≈ 33 g, fat ≈ 17 g, protein ≈ 6 g, fiber ≈ 1.5 g per pastry.\n", | |
| " - For “0.2 of a pain au chocolat,” either set quantity=0.2 with these per-pastry values, or set quantity=1.0 with totals for the 20% portion—do not double-count.\n", | |
| "- Durum falafel wrap (whole wrap with falafel, veg, typical sauces):\n", | |
| " - ≈ 550 kcal per wrap; carbs ≈ 60 g, fat ≈ 24 g, protein ≈ 16 g, fiber ≈ 8 g per wrap.\n", | |
| " - Treat as a single mixed item; do not split into tortilla/falafel/sauce unless clearly separate servings.\n", | |
| "- French fries, per piece:\n", | |
| " - ≈ 15 kcal per fry; carbs ≈ 1.8 g, fat ≈ 0.7 g, protein ≈ 0.2 g, fiber ≈ 0.2 g per fry.\n", | |
| " - If given a count (e.g., “10 fries”), set quantity to that count and keep per-fry nutrition.\n", | |
| "- Peanut butter (1 tablespoon ≈ 16 g):\n", | |
| " - ≈ 95 kcal per tbsp; carbs ≈ 3.5 g, fat ≈ 8.0 g, protein ≈ 4.0 g, fiber ≈ 1.5 g per tbsp.\n", | |
| "- Protein powder isolate (e.g., unflavoured whey/plant isolate; brand like Star Nutrition):\n", | |
| " - Per 30 g scoop: ≈ 110–120 kcal; carbs ≈ 1–3 g, fat ≈ 0.5–1.5 g, protein ≈ 25 g, fiber ≈ 0 g per 30 g.\n", | |
| "- Oat milk (e.g., Oatly; assume Barista unless specified):\n", | |
| " - ≈ 60 kcal/100 ml; carbs ≈ 6.5–7 g, fat ≈ 3.5 g, protein ≈ 1.0 g, fiber ≈ 0–1 g per 100 ml.\n", | |
| " - For specified ml (e.g., 200 ml), compute totals and set quantity=1.0 with those totals.\n", | |
| " - Plant milks are beverages, not dairy, for food_groups.\n", | |
| "- Coffee, unsweetened black:\n", | |
| " - ≈ 2 kcal/100 ml; negligible macros; set as a beverage.\n", | |
| "\n", | |
| "Food group mapping guidance:\n", | |
| "- Breads, focaccia, rice, pizza crust, porridge, crackers -> ['grain'].\n", | |
| "- Cheese, yogurt, stracciatella -> ['dairy'].\n", | |
| "- Fish, chicken, eggs, protein powders/supplements -> ['meat and alternatives'].\n", | |
| "- Falafel, beans, lentils, tofu, tempeh -> ['legume'].\n", | |
| "- Peanut butter, nuts, seeds -> ['nut and seed'].\n", | |
| "- Butter and oils -> ['fat and oils'].\n", | |
| "- Grapes (fresh or confit), plantains -> ['fruit'].\n", | |
| "- Mixed items like “rice with vegetables” -> include both ['grain', 'vegetable'] when appropriate.\n", | |
| "- Water, tea, coffee, plant milks, soda, iced tea -> ['beverage'].\n", | |
| "- Avoid labeling plant milks as 'dairy'.\n", | |
| "\n", | |
| "Handling target calories in the description:\n", | |
| "- If the description includes an explicit or approximate total calorie target (e.g., “~250 cal”, “total of 700 cals”), ensure the sum over all items (sum of quantity*calories) is within ±10% of that target. Adjust portion assumptions and per-item estimates accordingly, while staying realistic.\n", | |
| "\n", | |
| "Portion and unit heuristics:\n", | |
| "- For gram- or milliliter-specified amounts: compute totals by (amount/100) * per-100 baseline; set quantity=1.0 with those totals.\n", | |
| "- For counted items (pieces, slices): set quantity to the count; provide per-unit nutrition, not totals.\n", | |
| "- For “a handful of nuts”: assume ≈ 28–30 g unless otherwise specified; set quantity=1.0 with totals for that portion.\n", | |
| "- For fractional units (e.g., “0.2 of a pastry”), you may set quantity to the fraction with per-whole-unit nutrition.\n", | |
| "- For “a glass of water”: treat as 0 kcal beverage; set quantity=1.0.\n", | |
| "\n", | |
| "Quality checks before output:\n", | |
| "- Verify total calories = sum(quantity * calories) is sensible and, if a target was stated, within ±10% of that target.\n", | |
| "- Ensure macros roughly align with calories (kcal ≈ 4*carbs + 4*protein + 9*fat, allowing for rounding and fiber).\n", | |
| "- Choose food_groups from the allowed list; avoid leaving [] when a clear mapping exists (e.g., cola -> ['beverage'], Cheez-It -> ['grain','dairy'], falafel -> ['legume'], peanut butter -> ['nut and seed'], oat milk -> ['beverage']).\n", | |
| "- Do not include any explanatory text—only the list of FoodItem(...) objects.\n", | |
| "2025/12/20 10:00:24 INFO dspy.evaluate.evaluate: Average Metric: 3.0 / 3 (100.0%)\n", | |
| "2025/12/20 10:00:24 INFO dspy.teleprompt.gepa.gepa: Iteration 12: New subsample score 3 is better than old score 2. Continue to full eval and add to candidate pool.\n" | |
| ] | |
| }, | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "\n" | |
| ] | |
| }, | |
| { | |
| "name": "stderr", | |
| "output_type": "stream", | |
| "text": [ | |
| "2025/12/20 10:00:25 INFO dspy.evaluate.evaluate: Average Metric: 33.0 / 53 (62.3%)\n", | |
| "2025/12/20 10:00:25 INFO dspy.teleprompt.gepa.gepa: Iteration 12: Full valset score for new program: 0.6226415094339622\n", | |
| "2025/12/20 10:00:25 INFO dspy.teleprompt.gepa.gepa: Iteration 12: Full train_val score for new program: 0.6226415094339622\n", | |
| "2025/12/20 10:00:25 INFO dspy.teleprompt.gepa.gepa: Iteration 12: Individual valset scores for new program: [1, 0, 0, 0, 1, 1, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 0, 1, 0, 1, 0, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 0, 0, 1]\n", | |
| "2025/12/20 10:00:25 INFO dspy.teleprompt.gepa.gepa: Iteration 12: New valset pareto front scores: [1, 0, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1]\n", | |
| "2025/12/20 10:00:25 INFO dspy.teleprompt.gepa.gepa: Iteration 12: Full valset pareto front score: 0.8301886792452831\n", | |
| "2025/12/20 10:00:25 INFO dspy.teleprompt.gepa.gepa: Iteration 12: Updated valset pareto front programs: [{3, 4, 5, 6, 7, 8, 9, 10}, {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, {8, 1, 7}, {4, 5, 6, 7, 9, 10}, {1, 2, 5, 6, 7, 8, 9, 10}, {8, 3, 6, 7}, {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, {1, 3, 6, 7, 8, 9, 10}, {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, {8, 7}, {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, {0, 1, 4, 7, 8, 9}, {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, {8, 3, 7}, {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, {1, 2, 3, 4, 10}, {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, {0, 2, 3, 5, 6, 7, 8, 9, 10}, {8, 6, 7}, {8, 6}, {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, {8, 10, 3, 5}, {1, 5, 9}, {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, {1, 4, 5, 6, 8, 10}, {3, 4, 6, 8, 9, 10}, {1, 3, 6, 8, 9, 10}, {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, {0, 1, 2, 3, 6, 7, 9, 10}, {10, 4, 6}, {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, {2, 4, 5, 6, 7, 8, 9, 10}, {9, 5}, {10, 6}, {1, 2, 3, 4, 6, 8, 10}, {9, 3}, {10}, {8}, {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, {3, 4, 5, 6, 8, 9, 10}]\n", | |
| "2025/12/20 10:00:25 INFO dspy.teleprompt.gepa.gepa: Iteration 12: Best valset aggregate score so far: 0.660377358490566\n", | |
| "2025/12/20 10:00:25 INFO dspy.teleprompt.gepa.gepa: Iteration 12: Best program as per aggregate score on train_val: 8\n", | |
| "2025/12/20 10:00:25 INFO dspy.teleprompt.gepa.gepa: Iteration 12: Best program as per aggregate score on valset: 8\n", | |
| "2025/12/20 10:00:25 INFO dspy.teleprompt.gepa.gepa: Iteration 12: Best score on valset: 0.660377358490566\n", | |
| "2025/12/20 10:00:25 INFO dspy.teleprompt.gepa.gepa: Iteration 12: Best score on train_val: 0.660377358490566\n", | |
| "2025/12/20 10:00:25 INFO dspy.teleprompt.gepa.gepa: Iteration 12: Linear pareto front program index: 8\n", | |
| "2025/12/20 10:00:25 INFO dspy.teleprompt.gepa.gepa: Iteration 12: New program candidate index: 10\n", | |
| "GEPA Optimization: 100%|█████████▉| 590/592 [00:03<00:00, 184.39rollouts/s]\n" | |
| ] | |
| }, | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "Average Metric: 29.00 / 54 (53.7%): 100%|██████████| 54/54 [00:00<00:00, 216.75it/s]" | |
| ] | |
| }, | |
| { | |
| "name": "stderr", | |
| "output_type": "stream", | |
| "text": [ | |
| "2025/12/20 10:00:25 INFO dspy.evaluate.evaluate: Average Metric: 29.0 / 54 (53.7%)\n" | |
| ] | |
| }, | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "\n", | |
| "EvaluationResult(score=53.7, results=<list of 54 results>)\n" | |
| ] | |
| } | |
| ], | |
| "source": [ | |
| "gpt_5 = dspy.LM(model=\"openai/gpt-5\", temperature=1.0, max_tokens=32000)\n", | |
| "gepa = dspy.GEPA(\n", | |
| " metric=eval_metric,\n", | |
| " auto=\"light\",\n", | |
| " reflection_lm=gpt_5,\n", | |
| " num_threads=32,\n", | |
| ")\n", | |
| "\n", | |
| "module_gepa = gepa.compile(module_vanilla, trainset=trainset)\n", | |
| "optimizer_program_score = evaluate(module_gepa)\n", | |
| "er.add_result(\"gepa_gemini_2.5_flash\", optimizer_program_score.score)\n", | |
| "print(optimizer_program_score)" | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": 14, | |
| "metadata": {}, | |
| "outputs": [ | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "gepa_gemini_2.5_flash: 53.7\n", | |
| "optimized_gemini_2.5_flash: 44.44\n", | |
| "bootstrap_fewshot_random_search_gemini_2.5_flash: 31.48\n", | |
| "vanilla_gemini_2.5_flash: 29.63\n", | |
| "mipro_v2_gemini_2.5_flash: 29.63\n" | |
| ] | |
| } | |
| ], | |
| "source": [ | |
| "er.print_results()" | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": 15, | |
| "metadata": {}, | |
| "outputs": [ | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "Evaluating optimized for deepseek/deepseek-chat\n", | |
| "Average Metric: 31.00 / 54 (57.4%): 100%|██████████| 54/54 [00:00<00:00, 713.06it/s]" | |
| ] | |
| }, | |
| { | |
| "name": "stderr", | |
| "output_type": "stream", | |
| "text": [ | |
| "2025/12/20 10:00:25 INFO dspy.evaluate.evaluate: Average Metric: 31.0 / 54 (57.4%)\n" | |
| ] | |
| }, | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "\n", | |
| "Evaluating gepa for deepseek/deepseek-chat\n" | |
| ] | |
| }, | |
| { | |
| "name": "stderr", | |
| "output_type": "stream", | |
| "text": [ | |
| "2025/12/20 10:00:25 ERROR dspy.utils.parallelizer: Error for Example({'food_description': \"For lunch, I picked up a 165-gram double cheeseburger from McDonald's.\", 'food_groups': ['grain', 'meat and alternatives', 'dairy'], 'total_calories': 465.3, 'source': 'nutribench'}) (input_keys={'food_description'}): 1 validation error for list[FoodItem]\n", | |
| "0\n", | |
| " Input should be a valid dictionary or instance of FoodItem [type=model_type, input_value='FoodItem(name=\"McDonald\\... alternatives\", \"dairy\"', input_type=str]\n", | |
| " For further information visit https://errors.pydantic.dev/2.11/v/model_type. Set `provide_traceback=True` for traceback.\n" | |
| ] | |
| }, | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "Average Metric: 0.00 / 0 (0%): 0%| | 0/54 [00:00<?, ?it/s]" | |
| ] | |
| }, | |
| { | |
| "name": "stderr", | |
| "output_type": "stream", | |
| "text": [ | |
| "2025/12/20 10:00:25 ERROR dspy.utils.parallelizer: Error for Example({'food_description': 'pasta with pesto and tomatoes', 'food_groups': ['vegetable', 'grain', 'dairy'], 'total_calories': 339.0, 'source': 'golden_dataset'}) (input_keys={'food_description'}): 1 validation error for list[FoodItem]\n", | |
| "0\n", | |
| " Input should be a valid dictionary or instance of FoodItem [type=model_type, input_value=\"FoodItem(name='pasta wit...etable', 'fat and oils'\", input_type=str]\n", | |
| " For further information visit https://errors.pydantic.dev/2.11/v/model_type. Set `provide_traceback=True` for traceback.\n" | |
| ] | |
| }, | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "Average Metric: 0.00 / 0 (0%): 2%|▏ | 1/54 [00:00<00:01, 50.91it/s]" | |
| ] | |
| }, | |
| { | |
| "name": "stderr", | |
| "output_type": "stream", | |
| "text": [ | |
| "2025/12/20 10:00:25 ERROR dspy.utils.parallelizer: Error for Example({'food_description': 'a couple of handfuls of chocolate granola to snack on', 'food_groups': ['grain'], 'total_calories': 280.0, 'source': 'golden_dataset'}) (input_keys={'food_description'}): 1 validation error for list[FoodItem]\n", | |
| "0\n", | |
| " Input should be a valid dictionary or instance of FoodItem [type=model_type, input_value=\"FoodItem(name='chocolate...n', 'sweets and snacks'\", input_type=str]\n", | |
| " For further information visit https://errors.pydantic.dev/2.11/v/model_type. Set `provide_traceback=True` for traceback.\n" | |
| ] | |
| }, | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "Average Metric: 0.00 / 0 (0%): 4%|▎ | 2/54 [00:00<00:00, 73.09it/s]" | |
| ] | |
| }, | |
| { | |
| "name": "stderr", | |
| "output_type": "stream", | |
| "text": [ | |
| "2025/12/20 10:00:25 ERROR dspy.utils.parallelizer: Error for Example({'food_description': \"For dinner, I'm having 25 grams of bread, 150 grams of chicken wings, and a 250-gram mixed vegetable salad.\", 'food_groups': ['grain', 'meat and alternatives', 'vegetable'], 'total_calories': 633.0, 'source': 'nutribench'}) (input_keys={'food_description'}): 1 validation error for list[FoodItem]\n", | |
| "0\n", | |
| " Input should be a valid dictionary or instance of FoodItem [type=model_type, input_value='vegetable', input_type=str]\n", | |
| " For further information visit https://errors.pydantic.dev/2.11/v/model_type. Set `provide_traceback=True` for traceback.\n", | |
| "2025/12/20 10:00:25 ERROR dspy.utils.parallelizer: Error for Example({'food_description': 'I snacked on 35 grams of dry pastry with 3 grams of margarine, plus a drink of 100 grams of apricot nectar and 30 grams of wheat bread.', 'food_groups': ['grain', 'dairy', 'fruit'], 'total_calories': 289.0, 'source': 'nutribench'}) (input_keys={'food_description'}): 1 validation error for list[FoodItem]\n", | |
| "0\n", | |
| " Input should be a valid dictionary or instance of FoodItem [type=model_type, input_value='grain', input_type=str]\n", | |
| " For further information visit https://errors.pydantic.dev/2.11/v/model_type. Set `provide_traceback=True` for traceback.\n" | |
| ] | |
| }, | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "Average Metric: 0.00 / 0 (0%): 6%|▌ | 3/54 [00:00<00:00, 81.83it/s]" | |
| ] | |
| }, | |
| { | |
| "name": "stderr", | |
| "output_type": "stream", | |
| "text": [ | |
| "2025/12/20 10:00:25 ERROR dspy.utils.parallelizer: Error for Example({'food_description': '100\\u202fg of peanut butter', 'food_groups': ['meat and alternatives'], 'total_calories': 588.0, 'source': 'golden_dataset'}) (input_keys={'food_description'}): 1 validation error for list[FoodItem]\n", | |
| "0\n", | |
| " Input should be a valid dictionary or instance of FoodItem [type=model_type, input_value=\"FoodItem(name='peanut bu..._groups=['nut and seed'\", input_type=str]\n", | |
| " For further information visit https://errors.pydantic.dev/2.11/v/model_type. Set `provide_traceback=True` for traceback.\n" | |
| ] | |
| }, | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "Average Metric: 0.00 / 0 (0%): 11%|█ | 6/54 [00:00<00:00, 98.03it/s]" | |
| ] | |
| }, | |
| { | |
| "name": "stderr", | |
| "output_type": "stream", | |
| "text": [ | |
| "2025/12/20 10:00:25 ERROR dspy.utils.parallelizer: Error for Example({'food_description': '3 buns with peanut butter and jelly (small)', 'food_groups': ['fruit', 'grain', 'meat and alternatives'], 'total_calories': 615.0, 'source': 'golden_dataset'}) (input_keys={'food_description'}): 1 validation error for list[FoodItem]\n", | |
| "0\n", | |
| " Input should be a valid dictionary or instance of FoodItem [type=model_type, input_value=\"FoodItem(name='bun with ...'nut and seed', 'fruit'\", input_type=str]\n", | |
| " For further information visit https://errors.pydantic.dev/2.11/v/model_type. Set `provide_traceback=True` for traceback.\n", | |
| "2025/12/20 10:00:25 ERROR dspy.utils.parallelizer: Error for Example({'food_description': 'This morning, I had a 31g cereal bar and a 248g nutritional shake for breakfast.', 'food_groups': ['grain', 'dairy'], 'total_calories': 406.41, 'source': 'nutribench'}) (input_keys={'food_description'}): 1 validation error for list[FoodItem]\n", | |
| "0\n", | |
| " Input should be a valid dictionary or instance of FoodItem [type=model_type, input_value='beverage', input_type=str]\n", | |
| " For further information visit https://errors.pydantic.dev/2.11/v/model_type. Set `provide_traceback=True` for traceback.\n" | |
| ] | |
| }, | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "Average Metric: 0.00 / 0 (0%): 15%|█▍ | 8/54 [00:00<00:00, 118.76it/s]" | |
| ] | |
| }, | |
| { | |
| "name": "stderr", | |
| "output_type": "stream", | |
| "text": [ | |
| "2025/12/20 10:00:25 ERROR dspy.utils.parallelizer: Error for Example({'food_description': '200\\u202fg of pasta with pesto and tomatoes', 'food_groups': ['vegetable', 'grain', 'dairy'], 'total_calories': 430.0, 'source': 'golden_dataset'}) (input_keys={'food_description'}): 1 validation error for list[FoodItem]\n", | |
| "0\n", | |
| " Input should be a valid dictionary or instance of FoodItem [type=model_type, input_value=\"FoodItem(name='pasta wit...etable', 'fat and oils'\", input_type=str]\n", | |
| " For further information visit https://errors.pydantic.dev/2.11/v/model_type. Set `provide_traceback=True` for traceback.\n", | |
| "2025/12/20 10:00:25 ERROR dspy.utils.parallelizer: Error for Example({'food_description': 'apple', 'food_groups': ['fruit'], 'total_calories': 95.0, 'source': 'golden_dataset'}) (input_keys={'food_description'}): 1 validation error for list[FoodItem]\n", | |
| "0\n", | |
| " Input should be a valid dictionary or instance of FoodItem [type=model_type, input_value=\"FoodItem(name='apple', q...4, food_groups=['fruit'\", input_type=str]\n", | |
| " For further information visit https://errors.pydantic.dev/2.11/v/model_type. Set `provide_traceback=True` for traceback.\n" | |
| ] | |
| }, | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "Average Metric: 0.00 / 0 (0%): 17%|█▋ | 9/54 [00:00<00:00, 131.16it/s]" | |
| ] | |
| }, | |
| { | |
| "name": "stderr", | |
| "output_type": "stream", | |
| "text": [ | |
| "2025/12/20 10:00:25 ERROR dspy.utils.parallelizer: Error for Example({'food_description': 'For my snack, I’m enjoying 14 grams of soft fruit treats, a 240-gram bottle of plain water, and a 372-gram serving of root beer.', 'food_groups': ['fruit'], 'total_calories': 207.12, 'source': 'nutribench'}) (input_keys={'food_description'}): 1 validation error for list[FoodItem]\n", | |
| "0\n", | |
| " Input should be a valid dictionary or instance of FoodItem [type=model_type, input_value='beverage', input_type=str]\n", | |
| " For further information visit https://errors.pydantic.dev/2.11/v/model_type. Set `provide_traceback=True` for traceback.\n" | |
| ] | |
| }, | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "Average Metric: 0.00 / 0 (0%): 19%|█▊ | 10/54 [00:00<00:00, 130.13it/s]" | |
| ] | |
| }, | |
| { | |
| "name": "stderr", | |
| "output_type": "stream", | |
| "text": [ | |
| "2025/12/20 10:00:25 ERROR dspy.utils.parallelizer: Error for Example({'food_description': 'two slices of half a croissant toasted with butter and Flamengo cheese (1 slice split)', 'food_groups': ['dairy', 'grain'], 'total_calories': 336.0, 'source': 'golden_dataset'}) (input_keys={'food_description'}): 1 validation error for list[FoodItem]\n", | |
| "0\n", | |
| " Input should be a valid dictionary or instance of FoodItem [type=model_type, input_value=\"FoodItem(name='croissant...roups=['grain', 'dairy'\", input_type=str]\n", | |
| " For further information visit https://errors.pydantic.dev/2.11/v/model_type. Set `provide_traceback=True` for traceback.\n" | |
| ] | |
| }, | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "Average Metric: 0.00 / 0 (0%): 22%|██▏ | 12/54 [00:00<00:00, 143.89it/s]" | |
| ] | |
| }, | |
| { | |
| "name": "stderr", | |
| "output_type": "stream", | |
| "text": [ | |
| "2025/12/20 10:00:25 ERROR dspy.utils.parallelizer: Error for Example({'food_description': \"I made myself a breakfast of brewed coffee weighing 240 grams and a chocolate chip muffin that's 70 grams.\", 'food_groups': ['grain', 'dairy'], 'total_calories': 281.0, 'source': 'nutribench'}) (input_keys={'food_description'}): 1 validation error for list[FoodItem]\n", | |
| "0\n", | |
| " Input should be a valid dictionary or instance of FoodItem [type=model_type, input_value='sweets and snacks', input_type=str]\n", | |
| " For further information visit https://errors.pydantic.dev/2.11/v/model_type. Set `provide_traceback=True` for traceback.\n", | |
| "2025/12/20 10:00:25 ERROR dspy.utils.parallelizer: Error for Example({'food_description': 'panino mortadella', 'food_groups': ['grain', 'meat and alternatives'], 'total_calories': 430.0, 'source': 'golden_dataset'}) (input_keys={'food_description'}): 1 validation error for list[FoodItem]\n", | |
| "0\n", | |
| " Input should be a valid dictionary or instance of FoodItem [type=model_type, input_value=\"FoodItem(name='panino mo...'meat and alternatives'\", input_type=str]\n", | |
| " For further information visit https://errors.pydantic.dev/2.11/v/model_type. Set `provide_traceback=True` for traceback.\n" | |
| ] | |
| }, | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "Average Metric: 0.00 / 0 (0%): 26%|██▌ | 14/54 [00:00<00:00, 159.68it/s]" | |
| ] | |
| }, | |
| { | |
| "name": "stderr", | |
| "output_type": "stream", | |
| "text": [ | |
| "2025/12/20 10:00:25 ERROR dspy.utils.parallelizer: Error for Example({'food_description': 'I enjoyed 200 grams of tea with sugar along with 230 grams of coconut milk rice for breakfast.', 'food_groups': ['grain', 'fruit'], 'total_calories': 558.0, 'source': 'nutribench'}) (input_keys={'food_description'}): 1 validation error for list[FoodItem]\n", | |
| "0\n", | |
| " Input should be a valid dictionary or instance of FoodItem [type=model_type, input_value='grain', input_type=str]\n", | |
| " For further information visit https://errors.pydantic.dev/2.11/v/model_type. Set `provide_traceback=True` for traceback.\n" | |
| ] | |
| }, | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "Average Metric: 0.00 / 0 (0%): 28%|██▊ | 15/54 [00:00<00:00, 159.42it/s]" | |
| ] | |
| }, | |
| { | |
| "name": "stderr", | |
| "output_type": "stream", | |
| "text": [ | |
| "2025/12/20 10:00:25 ERROR dspy.utils.parallelizer: Error for Example({'food_description': 'frango churrasco', 'food_groups': ['meat and alternatives'], 'total_calories': 250.0, 'source': 'golden_dataset'}) (input_keys={'food_description'}): 1 validation error for list[FoodItem]\n", | |
| "0\n", | |
| " Input should be a valid dictionary or instance of FoodItem [type=model_type, input_value=\"FoodItem(name='grilled c...'meat and alternatives'\", input_type=str]\n", | |
| " For further information visit https://errors.pydantic.dev/2.11/v/model_type. Set `provide_traceback=True` for traceback.\n" | |
| ] | |
| }, | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "Average Metric: 0.00 / 0 (0%): 31%|███▏ | 17/54 [00:00<00:00, 172.60it/s]" | |
| ] | |
| }, | |
| { | |
| "name": "stderr", | |
| "output_type": "stream", | |
| "text": [ | |
| "2025/12/20 10:00:25 ERROR dspy.utils.parallelizer: Error for Example({'food_description': '3 slices of pão bolo de ló de leite with goat cheese', 'food_groups': ['dairy', 'grain'], 'total_calories': 480.0, 'source': 'golden_dataset'}) (input_keys={'food_description'}): 1 validation error for list[FoodItem]\n", | |
| "0\n", | |
| " Input should be a valid dictionary or instance of FoodItem [type=model_type, input_value='dairy', input_type=str]\n", | |
| " For further information visit https://errors.pydantic.dev/2.11/v/model_type. Set `provide_traceback=True` for traceback.\n" | |
| ] | |
| }, | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "Average Metric: 0.00 / 0 (0%): 35%|███▌ | 19/54 [00:00<00:00, 190.15it/s]" | |
| ] | |
| }, | |
| { | |
| "name": "stderr", | |
| "output_type": "stream", | |
| "text": [ | |
| "2025/12/20 10:00:25 ERROR dspy.utils.parallelizer: Error for Example({'food_description': '2 spoons of peanut butter', 'food_groups': ['meat and alternatives'], 'total_calories': 180.0, 'source': 'golden_dataset'}) (input_keys={'food_description'}): 1 validation error for list[FoodItem]\n", | |
| "0\n", | |
| " Input should be a valid dictionary or instance of FoodItem [type=model_type, input_value=\"FoodItem(name='peanut bu..._groups=['nut and seed'\", input_type=str]\n", | |
| " For further information visit https://errors.pydantic.dev/2.11/v/model_type. Set `provide_traceback=True` for traceback.\n", | |
| "2025/12/20 10:00:25 ERROR dspy.utils.parallelizer: Error for Example({'food_description': '1\\u202fL Augustiner beer', 'food_groups': [], 'total_calories': 430.0, 'source': 'golden_dataset'}) (input_keys={'food_description'}): 1 validation error for list[FoodItem]\n", | |
| "0\n", | |
| " Input should be a valid dictionary or instance of FoodItem [type=model_type, input_value=\"FoodItem(name='Augustine...food_groups=['beverage'\", input_type=str]\n", | |
| " For further information visit https://errors.pydantic.dev/2.11/v/model_type. Set `provide_traceback=True` for traceback.\n" | |
| ] | |
| }, | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "Average Metric: 0.00 / 0 (0%): 37%|███▋ | 20/54 [00:00<00:00, 199.44it/s]" | |
| ] | |
| }, | |
| { | |
| "name": "stderr", | |
| "output_type": "stream", | |
| "text": [ | |
| "2025/12/20 10:00:25 ERROR dspy.utils.parallelizer: Error for Example({'food_description': \"For lunch, I'm having an English muffin (58.0g) with American cheese (21.0g), a cooked egg omelet (55.0g), some jelly (20.0g), and a serving of pork bacon (12.0g).\", 'food_groups': ['grain', 'dairy', 'meat and alternatives', 'fruit'], 'total_calories': 406.69, 'source': 'nutribench'}) (input_keys={'food_description'}): 1 validation error for list[FoodItem]\n", | |
| "0\n", | |
| " Input should be a valid dictionary or instance of FoodItem [type=model_type, input_value='meat and alternatives', input_type=str]\n", | |
| " For further information visit https://errors.pydantic.dev/2.11/v/model_type. Set `provide_traceback=True` for traceback.\n" | |
| ] | |
| }, | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "Average Metric: 0.00 / 0 (0%): 39%|███▉ | 21/54 [00:00<00:00, 199.44it/s]" | |
| ] | |
| }, | |
| { | |
| "name": "stderr", | |
| "output_type": "stream", | |
| "text": [ | |
| "2025/12/20 10:00:25 ERROR dspy.utils.parallelizer: Error for Example({'food_description': \"I'm snacking on a regular bag of cheese popcorn alongside a can of cola for lunch.\", 'food_groups': ['grain', 'dairy'], 'total_calories': 611.84, 'source': 'nutribench'}) (input_keys={'food_description'}): 1 validation error for list[FoodItem]\n", | |
| "0\n", | |
| " Input should be a valid dictionary or instance of FoodItem [type=model_type, input_value='beverage', input_type=str]\n", | |
| " For further information visit https://errors.pydantic.dev/2.11/v/model_type. Set `provide_traceback=True` for traceback.\n" | |
| ] | |
| }, | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "Average Metric: 0.00 / 0 (0%): 41%|████ | 22/54 [00:00<00:00, 199.44it/s]" | |
| ] | |
| }, | |
| { | |
| "name": "stderr", | |
| "output_type": "stream", | |
| "text": [ | |
| "2025/12/20 10:00:25 ERROR dspy.utils.parallelizer: Error for Example({'food_description': \"I'm enjoying a peanut butter and jelly sandwich on wheat bread and a cup of unsweetened water for lunch.\", 'food_groups': ['meat and alternatives', 'fruit', 'grain'], 'total_calories': 402.08, 'source': 'nutribench'}) (input_keys={'food_description'}): 1 validation error for list[FoodItem]\n", | |
| "0\n", | |
| " Input should be a valid dictionary or instance of FoodItem [type=model_type, input_value='beverage', input_type=str]\n", | |
| " For further information visit https://errors.pydantic.dev/2.11/v/model_type. Set `provide_traceback=True` for traceback.\n" | |
| ] | |
| }, | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "Average Metric: 0.00 / 0 (0%): 43%|████▎ | 23/54 [00:00<00:00, 224.33it/s]" | |
| ] | |
| }, | |
| { | |
| "name": "stderr", | |
| "output_type": "stream", | |
| "text": [ | |
| "2025/12/20 10:00:25 ERROR dspy.utils.parallelizer: Error for Example({'food_description': 'tramezzino vegano 1/2', 'food_groups': ['vegetable', 'grain', 'meat and alternatives'], 'total_calories': 145.0, 'source': 'golden_dataset'}) (input_keys={'food_description'}): 1 validation error for list[FoodItem]\n", | |
| "0\n", | |
| " Input should be a valid dictionary or instance of FoodItem [type=model_type, input_value=\"FoodItem(name='tramezzin...s=['grain', 'vegetable'\", input_type=str]\n", | |
| " For further information visit https://errors.pydantic.dev/2.11/v/model_type. Set `provide_traceback=True` for traceback.\n", | |
| "2025/12/20 10:00:25 ERROR dspy.utils.parallelizer: Error for Example({'food_description': 'I’m treating myself to a medium latte for a little snack.', 'food_groups': ['dairy'], 'total_calories': 206.4, 'source': 'nutribench'}) (input_keys={'food_description'}): 1 validation error for list[FoodItem]\n", | |
| "0\n", | |
| " Input should be a valid dictionary or instance of FoodItem [type=model_type, input_value=\"FoodItem(name='medium la...ps=['dairy', 'beverage'\", input_type=str]\n", | |
| " For further information visit https://errors.pydantic.dev/2.11/v/model_type. Set `provide_traceback=True` for traceback.\n", | |
| "2025/12/20 10:00:25 ERROR dspy.utils.parallelizer: Error for Example({'food_description': \"For a snack, I'm having a small single serving bag of Cheetos and a medium frosted cinnamon bun.\", 'food_groups': ['grain', 'dairy'], 'total_calories': 563.6, 'source': 'nutribench'}) (input_keys={'food_description'}): 1 validation error for list[FoodItem]\n", | |
| "0\n", | |
| " Input should be a valid dictionary or instance of FoodItem [type=model_type, input_value='sweets and snacks', input_type=str]\n", | |
| " For further information visit https://errors.pydantic.dev/2.11/v/model_type. Set `provide_traceback=True` for traceback.\n", | |
| "2025/12/20 10:00:25 ERROR dspy.utils.parallelizer: Error for Example({'food_description': 'For my snack, I prepared 150g of fresh peeled apple, 33g of beans, and 60g of white bread. I also have 9.4g of raw onion, 35.5g of potato, and 11.5g of salad tomato, plus 5.8g of olive oil and 320g of tap water.', 'food_groups': ['fruit', 'meat and alternatives', 'grain', 'vegetable'], 'total_calories': 370.0, 'source': 'nutribench'}) (input_keys={'food_description'}): 1 validation error for list[FoodItem]\n", | |
| "0\n", | |
| " Input should be a valid dictionary or instance of FoodItem [type=model_type, input_value='beverage', input_type=str]\n", | |
| " For further information visit https://errors.pydantic.dev/2.11/v/model_type. Set `provide_traceback=True` for traceback.\n", | |
| "2025/12/20 10:00:25 ERROR dspy.utils.parallelizer: Error for Example({'food_description': 'For dinner, I’m having 3 grams of nacho cheese flavored Doritos with about 41 grams of topping from a meat pizza.', 'food_groups': ['grain', 'dairy', 'meat and alternatives', 'vegetable'], 'total_calories': 147.18, 'source': 'nutribench'}) (input_keys={'food_description'}): 2 validation errors for list[FoodItem]\n", | |
| "0\n", | |
| " Input should be a valid dictionary or instance of FoodItem [type=model_type, input_value=[\"FoodItem(name='nacho ch...s=['sweets and snacks'\"], input_type=list]\n", | |
| " For further information visit https://errors.pydantic.dev/2.11/v/model_type\n", | |
| "1\n", | |
| " Input should be a valid dictionary or instance of FoodItem [type=model_type, input_value=['meat and alternatives', 'grain', 'vegetable'], input_type=list]\n", | |
| " For further information visit https://errors.pydantic.dev/2.11/v/model_type. Set `provide_traceback=True` for traceback.\n", | |
| "2025/12/20 10:00:25 ERROR dspy.utils.parallelizer: Error for Example({'food_description': '300\\u202fg of pasta with butter and olive oil', 'food_groups': ['dairy', 'grain'], 'total_calories': 618.0, 'source': 'golden_dataset'}) (input_keys={'food_description'}): 1 validation error for list[FoodItem]\n", | |
| "0\n", | |
| " Input should be a valid dictionary or instance of FoodItem [type=model_type, input_value=\"FoodItem(name='pasta wit...'grain', 'fat and oils'\", input_type=str]\n", | |
| " For further information visit https://errors.pydantic.dev/2.11/v/model_type. Set `provide_traceback=True` for traceback.\n", | |
| "2025/12/20 10:00:25 ERROR dspy.utils.parallelizer: Error for Example({'food_description': 'For breakfast, I enjoyed 11.2 grams of cornstarch Atole, along with 69 grams of fried eggs, 115.8 grams of cooked maize flour, and 113 grams of homemade tomato chirmol sauce.', 'food_groups': ['grain', 'meat and alternatives', 'vegetable'], 'total_calories': 687.0, 'source': 'nutribench'}) (input_keys={'food_description'}): 1 validation error for list[FoodItem]\n", | |
| "0\n", | |
| " Input should be a valid dictionary or instance of FoodItem [type=model_type, input_value='vegetable', input_type=str]\n", | |
| " For further information visit https://errors.pydantic.dev/2.11/v/model_type. Set `provide_traceback=True` for traceback.\n", | |
| "2025/12/20 10:00:25 ERROR dspy.utils.parallelizer: Error for Example({'food_description': 'I enjoyed a cup of Frosted Flakes cereal with a cup of 2% reduced fat milk for breakfast.', 'food_groups': ['grain', 'dairy'], 'total_calories': 273.29, 'source': 'nutribench'}) (input_keys={'food_description'}): 1 validation error for list[FoodItem]\n", | |
| "0\n", | |
| " Input should be a valid dictionary or instance of FoodItem [type=model_type, input_value='dairy', input_type=str]\n", | |
| " For further information visit https://errors.pydantic.dev/2.11/v/model_type. Set `provide_traceback=True` for traceback.\n", | |
| "2025/12/20 10:00:25 ERROR dspy.utils.parallelizer: Error for Example({'food_description': \"I just made some popcorn for a snack. It's 14 grams, popped in oil and has butter on it.\", 'food_groups': ['grain', 'dairy'], 'total_calories': 73.22, 'source': 'nutribench'}) (input_keys={'food_description'}): 1 validation error for list[FoodItem]\n", | |
| "0\n", | |
| " Input should be a valid dictionary or instance of FoodItem [type=model_type, input_value=\"FoodItem(name='buttered ...'grain', 'fat and oils'\", input_type=str]\n", | |
| " For further information visit https://errors.pydantic.dev/2.11/v/model_type. Set `provide_traceback=True` for traceback.\n", | |
| "2025/12/20 10:00:25 ERROR dspy.utils.parallelizer: Error for Example({'food_description': 'two bananas and a cup of coffee with sugar and milk', 'food_groups': ['fruit', 'dairy'], 'total_calories': 243.0, 'source': 'golden_dataset'}) (input_keys={'food_description'}): 2 validation errors for list[FoodItem]\n", | |
| "0\n", | |
| " Input should be a valid dictionary or instance of FoodItem [type=model_type, input_value=[\"FoodItem(name='banana',..., food_groups=['fruit'\"], input_type=list]\n", | |
| " For further information visit https://errors.pydantic.dev/2.11/v/model_type\n", | |
| "1\n", | |
| " Input should be a valid dictionary or instance of FoodItem [type=model_type, input_value=['beverage', 'dairy'], input_type=list]\n", | |
| " For further information visit https://errors.pydantic.dev/2.11/v/model_type. Set `provide_traceback=True` for traceback.\n", | |
| "2025/12/20 10:00:25 ERROR dspy.utils.parallelizer: Error for Example({'food_description': 'avocado toast', 'food_groups': ['fruit', 'vegetable', 'grain'], 'total_calories': 250.0, 'source': 'golden_dataset'}) (input_keys={'food_description'}): 1 validation error for list[FoodItem]\n", | |
| "0\n", | |
| " Input should be a valid dictionary or instance of FoodItem [type=model_type, input_value=\"FoodItem(name='avocado t...roups=['grain', 'fruit'\", input_type=str]\n", | |
| " For further information visit https://errors.pydantic.dev/2.11/v/model_type. Set `provide_traceback=True` for traceback.\n", | |
| "2025/12/20 10:00:25 WARNING dspy.utils.parallelizer: Execution cancelled due to errors or interruption.\n" | |
| ] | |
| }, | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "\n", | |
| "Error evaluating gepa_deepseek_chat for deepseek/deepseek-chat: Execution cancelled due to errors or interruption.\n", | |
| "Evaluating mipro for deepseek/deepseek-chat\n", | |
| "Average Metric: 9.00 / 54 (16.7%): 100%|██████████| 54/54 [00:00<00:00, 657.49it/s]" | |
| ] | |
| }, | |
| { | |
| "name": "stderr", | |
| "output_type": "stream", | |
| "text": [ | |
| "2025/12/20 10:00:26 INFO dspy.evaluate.evaluate: Average Metric: 9.0 / 54 (16.7%)\n" | |
| ] | |
| }, | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "\n", | |
| "Evaluating bootstrap_fewshot_random_search for deepseek/deepseek-chat\n", | |
| "Average Metric: 16.00 / 54 (29.6%): 100%|██████████| 54/54 [00:00<00:00, 579.56it/s]" | |
| ] | |
| }, | |
| { | |
| "name": "stderr", | |
| "output_type": "stream", | |
| "text": [ | |
| "2025/12/20 10:00:26 INFO dspy.evaluate.evaluate: Average Metric: 16.0 / 54 (29.6%)\n" | |
| ] | |
| }, | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "\n", | |
| "Evaluating vanilla for deepseek/deepseek-chat\n", | |
| "Average Metric: 6.00 / 33 (18.2%): 59%|█████▉ | 32/54 [00:00<00:00, 381.76it/s]" | |
| ] | |
| }, | |
| { | |
| "name": "stderr", | |
| "output_type": "stream", | |
| "text": [ | |
| "2025/12/20 10:00:26 ERROR dspy.utils.parallelizer: Error for Example({'food_description': \"For a snack, I'm having a bottle of unsweetened bottled water and a juice box of 100% fruit juice.\", 'food_groups': ['fruit'], 'total_calories': 104.5, 'source': 'nutribench'}) (input_keys={'food_description'}): 1 validation error for list[FoodItem]\n", | |
| "0\n", | |
| " Input should be a valid dictionary or instance of FoodItem [type=model_type, input_value='beverage', input_type=str]\n", | |
| " For further information visit https://errors.pydantic.dev/2.11/v/model_type. Set `provide_traceback=True` for traceback.\n" | |
| ] | |
| }, | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "Average Metric: 12.00 / 54 (22.2%): 100%|██████████| 54/54 [00:00<00:00, 607.48it/s]" | |
| ] | |
| }, | |
| { | |
| "name": "stderr", | |
| "output_type": "stream", | |
| "text": [ | |
| "2025/12/20 10:00:26 INFO dspy.evaluate.evaluate: Average Metric: 12.0 / 54 (22.2%)\n" | |
| ] | |
| }, | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "\n", | |
| "Evaluating optimized for deepseek/deepseek-reasoner\n", | |
| "Average Metric: 29.00 / 54 (53.7%): 100%|██████████| 54/54 [00:00<00:00, 587.06it/s]" | |
| ] | |
| }, | |
| { | |
| "name": "stderr", | |
| "output_type": "stream", | |
| "text": [ | |
| "2025/12/20 10:00:26 INFO dspy.evaluate.evaluate: Average Metric: 29.0 / 54 (53.7%)\n" | |
| ] | |
| }, | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "\n", | |
| "Evaluating gepa for deepseek/deepseek-reasoner\n", | |
| "Average Metric: 0.00 / 9 (0.0%): 15%|█▍ | 8/54 [00:00<00:00, 141.68it/s]" | |
| ] | |
| }, | |
| { | |
| "name": "stderr", | |
| "output_type": "stream", | |
| "text": [ | |
| "2025/12/20 10:00:26 ERROR dspy.utils.parallelizer: Error for Example({'food_description': 'For my snack, I’m enjoying 14 grams of soft fruit treats, a 240-gram bottle of plain water, and a 372-gram serving of root beer.', 'food_groups': ['fruit'], 'total_calories': 207.12, 'source': 'nutribench'}) (input_keys={'food_description'}): 2 validation errors for list[FoodItem]\n", | |
| "1.food_groups.0\n", | |
| " Input should be 'dairy', 'meat and alternatives', 'grain', 'fruit' or 'vegetable' [type=literal_error, input_value='beverage', input_type=str]\n", | |
| " For further information visit https://errors.pydantic.dev/2.11/v/literal_error\n", | |
| "2.food_groups.0\n", | |
| " Input should be 'dairy', 'meat and alternatives', 'grain', 'fruit' or 'vegetable' [type=literal_error, input_value='beverage', input_type=str]\n", | |
| " For further information visit https://errors.pydantic.dev/2.11/v/literal_error. Set `provide_traceback=True` for traceback.\n" | |
| ] | |
| }, | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "Average Metric: 0.00 / 10 (0.0%): 19%|█▊ | 10/54 [00:00<00:00, 140.48it/s]" | |
| ] | |
| }, | |
| { | |
| "name": "stderr", | |
| "output_type": "stream", | |
| "text": [ | |
| "2025/12/20 10:00:26 ERROR dspy.utils.parallelizer: Error for Example({'food_description': '3 buns with peanut butter and jelly (small)', 'food_groups': ['fruit', 'grain', 'meat and alternatives'], 'total_calories': 615.0, 'source': 'golden_dataset'}) (input_keys={'food_description'}): 1 validation error for list[FoodItem]\n", | |
| "0.food_groups.1\n", | |
| " Input should be 'dairy', 'meat and alternatives', 'grain', 'fruit' or 'vegetable' [type=literal_error, input_value='nut and seed', input_type=str]\n", | |
| " For further information visit https://errors.pydantic.dev/2.11/v/literal_error. Set `provide_traceback=True` for traceback.\n" | |
| ] | |
| }, | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "Average Metric: 1.00 / 12 (8.3%): 24%|██▍ | 13/54 [00:00<00:00, 143.36it/s]" | |
| ] | |
| }, | |
| { | |
| "name": "stderr", | |
| "output_type": "stream", | |
| "text": [ | |
| "2025/12/20 10:00:26 ERROR dspy.utils.parallelizer: Error for Example({'food_description': '300\\u202fg of pasta with butter and olive oil', 'food_groups': ['dairy', 'grain'], 'total_calories': 618.0, 'source': 'golden_dataset'}) (input_keys={'food_description'}): 1 validation error for list[FoodItem]\n", | |
| "0.food_groups.2\n", | |
| " Input should be 'dairy', 'meat and alternatives', 'grain', 'fruit' or 'vegetable' [type=literal_error, input_value='fat and oils', input_type=str]\n", | |
| " For further information visit https://errors.pydantic.dev/2.11/v/literal_error. Set `provide_traceback=True` for traceback.\n" | |
| ] | |
| }, | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "Average Metric: 2.00 / 13 (15.4%): 26%|██▌ | 14/54 [00:00<00:00, 139.91it/s]" | |
| ] | |
| }, | |
| { | |
| "name": "stderr", | |
| "output_type": "stream", | |
| "text": [ | |
| "2025/12/20 10:00:26 ERROR dspy.utils.parallelizer: Error for Example({'food_description': \"I'm snacking on a raw banana along with a piece of hard candy.\", 'food_groups': ['fruit'], 'total_calories': 135.78, 'source': 'nutribench'}) (input_keys={'food_description'}): 2 validation errors for list[FoodItem]\n", | |
| "0\n", | |
| " Input should be a valid dictionary or instance of FoodItem [type=model_type, input_value=[\"FoodItem(name='banana',..., food_groups=['fruit'\"], input_type=list]\n", | |
| " For further information visit https://errors.pydantic.dev/2.11/v/model_type\n", | |
| "1\n", | |
| " Input should be a valid dictionary or instance of FoodItem [type=model_type, input_value=[], input_type=list]\n", | |
| " For further information visit https://errors.pydantic.dev/2.11/v/model_type. Set `provide_traceback=True` for traceback.\n" | |
| ] | |
| }, | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "Average Metric: 2.00 / 14 (14.3%): 28%|██▊ | 15/54 [00:00<00:00, 139.91it/s]" | |
| ] | |
| }, | |
| { | |
| "name": "stderr", | |
| "output_type": "stream", | |
| "text": [ | |
| "2025/12/20 10:00:26 ERROR dspy.utils.parallelizer: Error for Example({'food_description': \"I just made some popcorn for a snack. It's 14 grams, popped in oil and has butter on it.\", 'food_groups': ['grain', 'dairy'], 'total_calories': 73.22, 'source': 'nutribench'}) (input_keys={'food_description'}): 1 validation error for list[FoodItem]\n", | |
| "0.food_groups.0\n", | |
| " Input should be 'dairy', 'meat and alternatives', 'grain', 'fruit' or 'vegetable' [type=literal_error, input_value='sweets and snacks', input_type=str]\n", | |
| " For further information visit https://errors.pydantic.dev/2.11/v/literal_error. Set `provide_traceback=True` for traceback.\n" | |
| ] | |
| }, | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "Average Metric: 2.00 / 14 (14.3%): 30%|██▉ | 16/54 [00:00<00:00, 139.91it/s]" | |
| ] | |
| }, | |
| { | |
| "name": "stderr", | |
| "output_type": "stream", | |
| "text": [ | |
| "2025/12/20 10:00:26 ERROR dspy.utils.parallelizer: Error for Example({'food_description': 'I enjoyed 200 grams of tea with sugar along with 230 grams of coconut milk rice for breakfast.', 'food_groups': ['grain', 'fruit'], 'total_calories': 558.0, 'source': 'nutribench'}) (input_keys={'food_description'}): 1 validation error for list[FoodItem]\n", | |
| "0\n", | |
| " Input should be a valid dictionary or instance of FoodItem [type=model_type, input_value='grain', input_type=str]\n", | |
| " For further information visit https://errors.pydantic.dev/2.11/v/model_type. Set `provide_traceback=True` for traceback.\n" | |
| ] | |
| }, | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "Average Metric: 2.00 / 15 (13.3%): 31%|███▏ | 17/54 [00:00<00:00, 139.91it/s]" | |
| ] | |
| }, | |
| { | |
| "name": "stderr", | |
| "output_type": "stream", | |
| "text": [ | |
| "2025/12/20 10:00:26 ERROR dspy.utils.parallelizer: Error for Example({'food_description': 'I’m treating myself to a medium latte for a little snack.', 'food_groups': ['dairy'], 'total_calories': 206.4, 'source': 'nutribench'}) (input_keys={'food_description'}): 1 validation error for list[FoodItem]\n", | |
| "0\n", | |
| " Input should be a valid dictionary or instance of FoodItem [type=model_type, input_value=\"FoodItem(name='medium la...ps=['dairy', 'beverage'\", input_type=str]\n", | |
| " For further information visit https://errors.pydantic.dev/2.11/v/model_type. Set `provide_traceback=True` for traceback.\n" | |
| ] | |
| }, | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "Average Metric: 2.00 / 15 (13.3%): 33%|███▎ | 18/54 [00:00<00:00, 138.28it/s]" | |
| ] | |
| }, | |
| { | |
| "name": "stderr", | |
| "output_type": "stream", | |
| "text": [ | |
| "2025/12/20 10:00:26 ERROR dspy.utils.parallelizer: Error for Example({'food_description': 'a couple of handfuls of chocolate granola to snack on', 'food_groups': ['grain'], 'total_calories': 280.0, 'source': 'golden_dataset'}) (input_keys={'food_description'}): 1 validation error for list[FoodItem]\n", | |
| "0.food_groups.1\n", | |
| " Input should be 'dairy', 'meat and alternatives', 'grain', 'fruit' or 'vegetable' [type=literal_error, input_value='sweets and snacks', input_type=str]\n", | |
| " For further information visit https://errors.pydantic.dev/2.11/v/literal_error. Set `provide_traceback=True` for traceback.\n" | |
| ] | |
| }, | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "\n" | |
| ] | |
| }, | |
| { | |
| "name": "stderr", | |
| "output_type": "stream", | |
| "text": [ | |
| "2025/12/20 10:00:26 ERROR dspy.utils.parallelizer: Error for Example({'food_description': \"I made myself a breakfast of brewed coffee weighing 240 grams and a chocolate chip muffin that's 70 grams.\", 'food_groups': ['grain', 'dairy'], 'total_calories': 281.0, 'source': 'nutribench'}) (input_keys={'food_description'}): 2 validation errors for list[FoodItem]\n", | |
| "0.food_groups.0\n", | |
| " Input should be 'dairy', 'meat and alternatives', 'grain', 'fruit' or 'vegetable' [type=literal_error, input_value='beverage', input_type=str]\n", | |
| " For further information visit https://errors.pydantic.dev/2.11/v/literal_error\n", | |
| "1.food_groups.0\n", | |
| " Input should be 'dairy', 'meat and alternatives', 'grain', 'fruit' or 'vegetable' [type=literal_error, input_value='sweets and snacks', input_type=str]\n", | |
| " For further information visit https://errors.pydantic.dev/2.11/v/literal_error. Set `provide_traceback=True` for traceback.\n", | |
| "2025/12/20 10:00:26 ERROR dspy.utils.parallelizer: Error for Example({'food_description': '100\\u202fg of peanut butter', 'food_groups': ['meat and alternatives'], 'total_calories': 588.0, 'source': 'golden_dataset'}) (input_keys={'food_description'}): 1 validation error for list[FoodItem]\n", | |
| "0\n", | |
| " Input should be a valid dictionary or instance of FoodItem [type=model_type, input_value=\"FoodItem(name='peanut bu...fat and oils', 'legume'\", input_type=str]\n", | |
| " For further information visit https://errors.pydantic.dev/2.11/v/model_type. Set `provide_traceback=True` for traceback.\n", | |
| "2025/12/20 10:00:26 ERROR dspy.utils.parallelizer: Error for Example({'food_description': '2 spoons of peanut butter', 'food_groups': ['meat and alternatives'], 'total_calories': 180.0, 'source': 'golden_dataset'}) (input_keys={'food_description'}): 1 validation error for list[FoodItem]\n", | |
| "0.food_groups.0\n", | |
| " Input should be 'dairy', 'meat and alternatives', 'grain', 'fruit' or 'vegetable' [type=literal_error, input_value='nut and seed', input_type=str]\n", | |
| " For further information visit https://errors.pydantic.dev/2.11/v/literal_error. Set `provide_traceback=True` for traceback.\n", | |
| "2025/12/20 10:00:26 ERROR dspy.utils.parallelizer: Error for Example({'food_description': '1\\u202fL Augustiner beer', 'food_groups': [], 'total_calories': 430.0, 'source': 'golden_dataset'}) (input_keys={'food_description'}): 1 validation error for list[FoodItem]\n", | |
| "0\n", | |
| " Input should be a valid dictionary or instance of FoodItem [type=model_type, input_value=\"FoodItem(name='Augustine...food_groups=['beverage'\", input_type=str]\n", | |
| " For further information visit https://errors.pydantic.dev/2.11/v/model_type. Set `provide_traceback=True` for traceback.\n", | |
| "2025/12/20 10:00:26 ERROR dspy.utils.parallelizer: Error for Example({'food_description': \"For a snack, I'm having a small single serving bag of Cheetos and a medium frosted cinnamon bun.\", 'food_groups': ['grain', 'dairy'], 'total_calories': 563.6, 'source': 'nutribench'}) (input_keys={'food_description'}): 2 validation errors for list[FoodItem]\n", | |
| "0.food_groups.0\n", | |
| " Input should be 'dairy', 'meat and alternatives', 'grain', 'fruit' or 'vegetable' [type=literal_error, input_value='sweets and snacks', input_type=str]\n", | |
| " For further information visit https://errors.pydantic.dev/2.11/v/literal_error\n", | |
| "1.food_groups.1\n", | |
| " Input should be 'dairy', 'meat and alternatives', 'grain', 'fruit' or 'vegetable' [type=literal_error, input_value='sweets and snacks', input_type=str]\n", | |
| " For further information visit https://errors.pydantic.dev/2.11/v/literal_error. Set `provide_traceback=True` for traceback.\n", | |
| "2025/12/20 10:00:26 ERROR dspy.utils.parallelizer: Error for Example({'food_description': 'For my snack, I prepared 150g of fresh peeled apple, 33g of beans, and 60g of white bread. I also have 9.4g of raw onion, 35.5g of potato, and 11.5g of salad tomato, plus 5.8g of olive oil and 320g of tap water.', 'food_groups': ['fruit', 'meat and alternatives', 'grain', 'vegetable'], 'total_calories': 370.0, 'source': 'nutribench'}) (input_keys={'food_description'}): 1 validation error for list[FoodItem]\n", | |
| "0\n", | |
| " Input should be a valid dictionary or instance of FoodItem [type=model_type, input_value='beverage', input_type=str]\n", | |
| " For further information visit https://errors.pydantic.dev/2.11/v/model_type. Set `provide_traceback=True` for traceback.\n", | |
| "2025/12/20 10:00:26 ERROR dspy.utils.parallelizer: Error for Example({'food_description': \"For lunch, I'm having an English muffin (58.0g) with American cheese (21.0g), a cooked egg omelet (55.0g), some jelly (20.0g), and a serving of pork bacon (12.0g).\", 'food_groups': ['grain', 'dairy', 'meat and alternatives', 'fruit'], 'total_calories': 406.69, 'source': 'nutribench'}) (input_keys={'food_description'}): 1 validation error for list[FoodItem]\n", | |
| "0\n", | |
| " Input should be a valid dictionary or instance of FoodItem [type=model_type, input_value='meat and alternatives', input_type=str]\n", | |
| " For further information visit https://errors.pydantic.dev/2.11/v/model_type. Set `provide_traceback=True` for traceback.\n", | |
| "2025/12/20 10:00:26 ERROR dspy.utils.parallelizer: Error for Example({'food_description': 'fruit bowl with papaya, pineapple and orange', 'food_groups': ['fruit'], 'total_calories': 206.0, 'source': 'golden_dataset'}) (input_keys={'food_description'}): 1 validation error for list[FoodItem]\n", | |
| "0\n", | |
| " Input should be a valid dictionary or instance of FoodItem [type=model_type, input_value='fruit', input_type=str]\n", | |
| " For further information visit https://errors.pydantic.dev/2.11/v/model_type. Set `provide_traceback=True` for traceback.\n", | |
| "2025/12/20 10:00:26 ERROR dspy.utils.parallelizer: Error for Example({'food_description': 'I enjoyed a cup of Frosted Flakes cereal with a cup of 2% reduced fat milk for breakfast.', 'food_groups': ['grain', 'dairy'], 'total_calories': 273.29, 'source': 'nutribench'}) (input_keys={'food_description'}): 1 validation error for list[FoodItem]\n", | |
| "0\n", | |
| " Input should be a valid dictionary or instance of FoodItem [type=model_type, input_value='dairy', input_type=str]\n", | |
| " For further information visit https://errors.pydantic.dev/2.11/v/model_type. Set `provide_traceback=True` for traceback.\n", | |
| "2025/12/20 10:00:26 WARNING dspy.utils.parallelizer: Execution cancelled due to errors or interruption.\n", | |
| "2025/12/20 10:00:26 ERROR dspy.utils.parallelizer: Error for Example({'food_description': 'avocado toast', 'food_groups': ['fruit', 'vegetable', 'grain'], 'total_calories': 250.0, 'source': 'golden_dataset'}) (input_keys={'food_description'}): 1 validation error for list[FoodItem]\n", | |
| "0\n", | |
| " Input should be a valid dictionary or instance of FoodItem [type=model_type, input_value=\"FoodItem(name='avocado t...roups=['grain', 'fruit'\", input_type=str]\n", | |
| " For further information visit https://errors.pydantic.dev/2.11/v/model_type. Set `provide_traceback=True` for traceback.\n", | |
| "2025/12/20 10:00:26 ERROR dspy.utils.parallelizer: Error for Example({'food_description': '250 g egg whites', 'food_groups': ['meat and alternatives'], 'total_calories': 130.0, 'source': 'golden_dataset'}) (input_keys={'food_description'}): 1 validation error for list[FoodItem]\n", | |
| "0\n", | |
| " Input should be a valid dictionary or instance of FoodItem [type=model_type, input_value=\"FoodItem(name='egg white...'meat and alternatives'\", input_type=str]\n", | |
| " For further information visit https://errors.pydantic.dev/2.11/v/model_type. Set `provide_traceback=True` for traceback.\n", | |
| "2025/12/20 10:00:26 ERROR dspy.utils.parallelizer: Error for Example({'food_description': \"For lunch, I'm having a medium crust cheese pizza from a restaurant that weighs 119 grams.\", 'food_groups': ['dairy', 'grain', 'vegetable'], 'total_calories': 316.54, 'source': 'nutribench'}) (input_keys={'food_description'}): 1 validation error for list[FoodItem]\n", | |
| "0\n", | |
| " Input should be a valid dictionary or instance of FoodItem [type=model_type, input_value=\"FoodItem(name='medium cr...roups=['grain', 'dairy'\", input_type=str]\n", | |
| " For further information visit https://errors.pydantic.dev/2.11/v/model_type. Set `provide_traceback=True` for traceback.\n", | |
| "2025/12/20 10:00:26 ERROR dspy.utils.parallelizer: Error for Example({'food_description': 'ice‑cream cone', 'food_groups': ['dairy', 'grain'], 'total_calories': 157.0, 'source': 'golden_dataset'}) (input_keys={'food_description'}): 1 validation error for list[FoodItem]\n", | |
| "0\n", | |
| " Input should be a valid dictionary or instance of FoodItem [type=model_type, input_value=\"FoodItem(name='ice cream...roups=['dairy', 'grain'\", input_type=str]\n", | |
| " For further information visit https://errors.pydantic.dev/2.11/v/model_type. Set `provide_traceback=True` for traceback.\n", | |
| "2025/12/20 10:00:26 ERROR dspy.utils.parallelizer: Error for Example({'food_description': \"For my lunch, I'm having a 480g energy drink that's sugar-free, from Monster.\", 'food_groups': [], 'total_calories': 24.0, 'source': 'nutribench'}) (input_keys={'food_description'}): 1 validation error for list[FoodItem]\n", | |
| "0.food_groups.0\n", | |
| " Input should be 'dairy', 'meat and alternatives', 'grain', 'fruit' or 'vegetable' [type=literal_error, input_value='beverage', input_type=str]\n", | |
| " For further information visit https://errors.pydantic.dev/2.11/v/literal_error. Set `provide_traceback=True` for traceback.\n", | |
| "2025/12/20 10:00:26 ERROR dspy.utils.parallelizer: Error for Example({'food_description': 'bread 2 slices with cheese and scrambled eggs', 'food_groups': ['dairy', 'grain', 'meat and alternatives'], 'total_calories': 440.0, 'source': 'golden_dataset'}) (input_keys={'food_description'}): 1 validation error for list[FoodItem]\n", | |
| "0\n", | |
| " Input should be a valid dictionary or instance of FoodItem [type=model_type, input_value='meat and alternatives', input_type=str]\n", | |
| " For further information visit https://errors.pydantic.dev/2.11/v/model_type. Set `provide_traceback=True` for traceback.\n" | |
| ] | |
| }, | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "Error evaluating gepa_deepseek_reasoner for deepseek/deepseek-reasoner: Execution cancelled due to errors or interruption.\n", | |
| "Evaluating mipro for deepseek/deepseek-reasoner\n" | |
| ] | |
| }, | |
| { | |
| "name": "stderr", | |
| "output_type": "stream", | |
| "text": [ | |
| "2025/12/20 10:00:26 ERROR dspy.utils.parallelizer: Error for Example({'food_description': 'I enjoyed a snack that included 216g of sorrel punch, 240g of peppermint tea, and a glass of 511g of tap water.', 'food_groups': ['fruit'], 'total_calories': 60.0, 'source': 'nutribench'}) (input_keys={'food_description'}): 3 validation errors for list[FoodItem]\n", | |
| "0.food_groups.0\n", | |
| " Input should be 'dairy', 'meat and alternatives', 'grain', 'fruit' or 'vegetable' [type=literal_error, input_value='beverage', input_type=str]\n", | |
| " For further information visit https://errors.pydantic.dev/2.11/v/literal_error\n", | |
| "1.food_groups.0\n", | |
| " Input should be 'dairy', 'meat and alternatives', 'grain', 'fruit' or 'vegetable' [type=literal_error, input_value='beverage', input_type=str]\n", | |
| " For further information visit https://errors.pydantic.dev/2.11/v/literal_error\n", | |
| "2.food_groups.0\n", | |
| " Input should be 'dairy', 'meat and alternatives', 'grain', 'fruit' or 'vegetable' [type=literal_error, input_value='beverage', input_type=str]\n", | |
| " For further information visit https://errors.pydantic.dev/2.11/v/literal_error. Set `provide_traceback=True` for traceback.\n", | |
| "2025/12/20 10:00:26 ERROR dspy.utils.parallelizer: Error for Example({'food_description': 'coffee‑flavoured corn flakes with plant‑based milk (one bowl)', 'food_groups': ['grain'], 'total_calories': 240.0, 'source': 'golden_dataset'}) (input_keys={'food_description'}): 1 validation error for list[FoodItem]\n", | |
| "1.food_groups.0\n", | |
| " Input should be 'dairy', 'meat and alternatives', 'grain', 'fruit' or 'vegetable' [type=literal_error, input_value='beverage', input_type=str]\n", | |
| " For further information visit https://errors.pydantic.dev/2.11/v/literal_error. Set `provide_traceback=True` for traceback.\n" | |
| ] | |
| }, | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "Average Metric: 26.00 / 54 (48.1%): 100%|██████████| 54/54 [00:00<00:00, 610.56it/s]" | |
| ] | |
| }, | |
| { | |
| "name": "stderr", | |
| "output_type": "stream", | |
| "text": [ | |
| "2025/12/20 10:00:26 INFO dspy.evaluate.evaluate: Average Metric: 26.0 / 54 (48.1%)\n" | |
| ] | |
| }, | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "\n", | |
| "Evaluating bootstrap_fewshot_random_search for deepseek/deepseek-reasoner\n", | |
| "Average Metric: 22.00 / 54 (40.7%): 100%|██████████| 54/54 [00:00<00:00, 585.99it/s]" | |
| ] | |
| }, | |
| { | |
| "name": "stderr", | |
| "output_type": "stream", | |
| "text": [ | |
| "2025/12/20 10:00:27 INFO dspy.evaluate.evaluate: Average Metric: 22.0 / 54 (40.7%)\n" | |
| ] | |
| }, | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "\n", | |
| "Evaluating vanilla for deepseek/deepseek-reasoner\n" | |
| ] | |
| }, | |
| { | |
| "name": "stderr", | |
| "output_type": "stream", | |
| "text": [ | |
| "2025/12/20 10:00:27 ERROR dspy.utils.parallelizer: Error for Example({'food_description': 'For dinner, I’m having 3 grams of nacho cheese flavored Doritos with about 41 grams of topping from a meat pizza.', 'food_groups': ['grain', 'dairy', 'meat and alternatives', 'vegetable'], 'total_calories': 147.18, 'source': 'nutribench'}) (input_keys={'food_description'}): 1 validation error for list[FoodItem]\n", | |
| "0\n", | |
| " Input should be a valid dictionary or instance of FoodItem [type=model_type, input_value='meat and alternatives', input_type=str]\n", | |
| " For further information visit https://errors.pydantic.dev/2.11/v/model_type. Set `provide_traceback=True` for traceback.\n" | |
| ] | |
| }, | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "Average Metric: 25.00 / 54 (46.3%): 100%|██████████| 54/54 [00:00<00:00, 607.08it/s]" | |
| ] | |
| }, | |
| { | |
| "name": "stderr", | |
| "output_type": "stream", | |
| "text": [ | |
| "2025/12/20 10:00:27 INFO dspy.evaluate.evaluate: Average Metric: 25.0 / 54 (46.3%)\n" | |
| ] | |
| }, | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "\n", | |
| "Evaluating optimized for openrouter/google/gemini-3-flash-preview\n", | |
| "Average Metric: 32.00 / 54 (59.3%): 100%|██████████| 54/54 [00:00<00:00, 472.64it/s]" | |
| ] | |
| }, | |
| { | |
| "name": "stderr", | |
| "output_type": "stream", | |
| "text": [ | |
| "2025/12/20 10:00:27 INFO dspy.evaluate.evaluate: Average Metric: 32.0 / 54 (59.3%)\n" | |
| ] | |
| }, | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "\n", | |
| "Evaluating gepa for openrouter/google/gemini-3-flash-preview\n", | |
| " 0%| | 0/54 [00:00<?, ?it/s]" | |
| ] | |
| }, | |
| { | |
| "name": "stderr", | |
| "output_type": "stream", | |
| "text": [ | |
| "2025/12/20 10:00:27 ERROR dspy.utils.parallelizer: Error for Example({'food_description': 'a couple of handfuls of chocolate granola to snack on', 'food_groups': ['grain'], 'total_calories': 280.0, 'source': 'golden_dataset'}) (input_keys={'food_description'}): 1 validation error for list[FoodItem]\n", | |
| "0.food_groups.1\n", | |
| " Input should be 'dairy', 'meat and alternatives', 'grain', 'fruit' or 'vegetable' [type=literal_error, input_value='sweets and snacks', input_type=str]\n", | |
| " For further information visit https://errors.pydantic.dev/2.11/v/literal_error. Set `provide_traceback=True` for traceback.\n" | |
| ] | |
| }, | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "Average Metric: 0.00 / 1 (0.0%): 2%|▏ | 1/54 [00:00<00:03, 17.55it/s]" | |
| ] | |
| }, | |
| { | |
| "name": "stderr", | |
| "output_type": "stream", | |
| "text": [ | |
| "2025/12/20 10:00:27 ERROR dspy.utils.parallelizer: Error for Example({'food_description': 'This morning, I had a 31g cereal bar and a 248g nutritional shake for breakfast.', 'food_groups': ['grain', 'dairy'], 'total_calories': 406.41, 'source': 'nutribench'}) (input_keys={'food_description'}): 2 validation errors for list[FoodItem]\n", | |
| "0.food_groups.1\n", | |
| " Input should be 'dairy', 'meat and alternatives', 'grain', 'fruit' or 'vegetable' [type=literal_error, input_value='sweets and snacks', input_type=str]\n", | |
| " For further information visit https://errors.pydantic.dev/2.11/v/literal_error\n", | |
| "1.food_groups.0\n", | |
| " Input should be 'dairy', 'meat and alternatives', 'grain', 'fruit' or 'vegetable' [type=literal_error, input_value='beverage', input_type=str]\n", | |
| " For further information visit https://errors.pydantic.dev/2.11/v/literal_error. Set `provide_traceback=True` for traceback.\n" | |
| ] | |
| }, | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "Average Metric: 1.00 / 2 (50.0%): 4%|▎ | 2/54 [00:00<00:01, 32.25it/s]" | |
| ] | |
| }, | |
| { | |
| "name": "stderr", | |
| "output_type": "stream", | |
| "text": [ | |
| "2025/12/20 10:00:27 ERROR dspy.utils.parallelizer: Error for Example({'food_description': 'pasta with pesto and tomatoes', 'food_groups': ['vegetable', 'grain', 'dairy'], 'total_calories': 339.0, 'source': 'golden_dataset'}) (input_keys={'food_description'}): 1 validation error for list[FoodItem]\n", | |
| "0.food_groups.2\n", | |
| " Input should be 'dairy', 'meat and alternatives', 'grain', 'fruit' or 'vegetable' [type=literal_error, input_value='fat and oils', input_type=str]\n", | |
| " For further information visit https://errors.pydantic.dev/2.11/v/literal_error. Set `provide_traceback=True` for traceback.\n" | |
| ] | |
| }, | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "Average Metric: 1.00 / 2 (50.0%): 6%|▌ | 3/54 [00:00<00:01, 42.63it/s]" | |
| ] | |
| }, | |
| { | |
| "name": "stderr", | |
| "output_type": "stream", | |
| "text": [ | |
| "2025/12/20 10:00:27 ERROR dspy.utils.parallelizer: Error for Example({'food_description': 'For my snack, I’m enjoying 14 grams of soft fruit treats, a 240-gram bottle of plain water, and a 372-gram serving of root beer.', 'food_groups': ['fruit'], 'total_calories': 207.12, 'source': 'nutribench'}) (input_keys={'food_description'}): 3 validation errors for list[FoodItem]\n", | |
| "0.food_groups.1\n", | |
| " Input should be 'dairy', 'meat and alternatives', 'grain', 'fruit' or 'vegetable' [type=literal_error, input_value='sweets and snacks', input_type=str]\n", | |
| " For further information visit https://errors.pydantic.dev/2.11/v/literal_error\n", | |
| "1.food_groups.0\n", | |
| " Input should be 'dairy', 'meat and alternatives', 'grain', 'fruit' or 'vegetable' [type=literal_error, input_value='beverage', input_type=str]\n", | |
| " For further information visit https://errors.pydantic.dev/2.11/v/literal_error\n", | |
| "2.food_groups.0\n", | |
| " Input should be 'dairy', 'meat and alternatives', 'grain', 'fruit' or 'vegetable' [type=literal_error, input_value='beverage', input_type=str]\n", | |
| " For further information visit https://errors.pydantic.dev/2.11/v/literal_error. Set `provide_traceback=True` for traceback.\n" | |
| ] | |
| }, | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "\n" | |
| ] | |
| }, | |
| { | |
| "name": "stderr", | |
| "output_type": "stream", | |
| "text": [ | |
| "2025/12/20 10:00:27 ERROR dspy.utils.parallelizer: Error for Example({'food_description': '200\\u202fg of pasta with pesto and tomatoes', 'food_groups': ['vegetable', 'grain', 'dairy'], 'total_calories': 430.0, 'source': 'golden_dataset'}) (input_keys={'food_description'}): 1 validation error for list[FoodItem]\n", | |
| "0.food_groups.2\n", | |
| " Input should be 'dairy', 'meat and alternatives', 'grain', 'fruit' or 'vegetable' [type=literal_error, input_value='fat and oils', input_type=str]\n", | |
| " For further information visit https://errors.pydantic.dev/2.11/v/literal_error. Set `provide_traceback=True` for traceback.\n", | |
| "2025/12/20 10:00:27 WARNING dspy.utils.parallelizer: Execution cancelled due to errors or interruption.\n", | |
| "2025/12/20 10:00:27 ERROR dspy.utils.parallelizer: Error for Example({'food_description': 'two slices of half a croissant toasted with butter and Flamengo cheese (1 slice split)', 'food_groups': ['dairy', 'grain'], 'total_calories': 336.0, 'source': 'golden_dataset'}) (input_keys={'food_description'}): 1 validation error for list[FoodItem]\n", | |
| "0.food_groups.2\n", | |
| " Input should be 'dairy', 'meat and alternatives', 'grain', 'fruit' or 'vegetable' [type=literal_error, input_value='fat and oils', input_type=str]\n", | |
| " For further information visit https://errors.pydantic.dev/2.11/v/literal_error. Set `provide_traceback=True` for traceback.\n" | |
| ] | |
| }, | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "Error evaluating gepa_gemini_3_flash_preview for openrouter/google/gemini-3-flash-preview: Execution cancelled due to errors or interruption.\n", | |
| "Evaluating mipro for openrouter/google/gemini-3-flash-preview\n" | |
| ] | |
| }, | |
| { | |
| "name": "stderr", | |
| "output_type": "stream", | |
| "text": [ | |
| "2025/12/20 10:00:27 ERROR dspy.utils.parallelizer: Error for Example({'food_description': \"For a snack, I'm having a bottle of unsweetened bottled water and a juice box of 100% fruit juice.\", 'food_groups': ['fruit'], 'total_calories': 104.5, 'source': 'nutribench'}) (input_keys={'food_description'}): 2 validation errors for list[FoodItem]\n", | |
| "0.food_groups.0\n", | |
| " Input should be 'dairy', 'meat and alternatives', 'grain', 'fruit' or 'vegetable' [type=literal_error, input_value='beverage', input_type=str]\n", | |
| " For further information visit https://errors.pydantic.dev/2.11/v/literal_error\n", | |
| "1.food_groups.0\n", | |
| " Input should be 'dairy', 'meat and alternatives', 'grain', 'fruit' or 'vegetable' [type=literal_error, input_value='beverage', input_type=str]\n", | |
| " For further information visit https://errors.pydantic.dev/2.11/v/literal_error. Set `provide_traceback=True` for traceback.\n", | |
| "2025/12/20 10:00:27 ERROR dspy.utils.parallelizer: Error for Example({'food_description': 'I enjoyed a cup of Frosted Flakes cereal with a cup of 2% reduced fat milk for breakfast.', 'food_groups': ['grain', 'dairy'], 'total_calories': 273.29, 'source': 'nutribench'}) (input_keys={'food_description'}): 1 validation error for list[FoodItem]\n", | |
| "0.food_groups.1\n", | |
| " Input should be 'dairy', 'meat and alternatives', 'grain', 'fruit' or 'vegetable' [type=literal_error, input_value='sweets and snacks', input_type=str]\n", | |
| " For further information visit https://errors.pydantic.dev/2.11/v/literal_error. Set `provide_traceback=True` for traceback.\n", | |
| "2025/12/20 10:00:27 ERROR dspy.utils.parallelizer: Error for Example({'food_description': 'I enjoyed 200 grams of tea with sugar along with 230 grams of coconut milk rice for breakfast.', 'food_groups': ['grain', 'fruit'], 'total_calories': 558.0, 'source': 'nutribench'}) (input_keys={'food_description'}): 2 validation errors for list[FoodItem]\n", | |
| "0.food_groups.0\n", | |
| " Input should be 'dairy', 'meat and alternatives', 'grain', 'fruit' or 'vegetable' [type=literal_error, input_value='beverage', input_type=str]\n", | |
| " For further information visit https://errors.pydantic.dev/2.11/v/literal_error\n", | |
| "1.food_groups.1\n", | |
| " Input should be 'dairy', 'meat and alternatives', 'grain', 'fruit' or 'vegetable' [type=literal_error, input_value='fat and oils', input_type=str]\n", | |
| " For further information visit https://errors.pydantic.dev/2.11/v/literal_error. Set `provide_traceback=True` for traceback.\n", | |
| "2025/12/20 10:00:27 ERROR dspy.utils.parallelizer: Error for Example({'food_description': \"For dinner, I'm having 25 grams of bread, 150 grams of chicken wings, and a 250-gram mixed vegetable salad.\", 'food_groups': ['grain', 'meat and alternatives', 'vegetable'], 'total_calories': 633.0, 'source': 'nutribench'}) (input_keys={'food_description'}): 1 validation error for list[FoodItem]\n", | |
| "2.food_groups.1\n", | |
| " Input should be 'dairy', 'meat and alternatives', 'grain', 'fruit' or 'vegetable' [type=literal_error, input_value='fat and oils', input_type=str]\n", | |
| " For further information visit https://errors.pydantic.dev/2.11/v/literal_error. Set `provide_traceback=True` for traceback.\n", | |
| "2025/12/20 10:00:27 ERROR dspy.utils.parallelizer: Error for Example({'food_description': '300\\u202fg of pasta with butter and olive oil', 'food_groups': ['dairy', 'grain'], 'total_calories': 618.0, 'source': 'golden_dataset'}) (input_keys={'food_description'}): 1 validation error for list[FoodItem]\n", | |
| "0.food_groups.1\n", | |
| " Input should be 'dairy', 'meat and alternatives', 'grain', 'fruit' or 'vegetable' [type=literal_error, input_value='fat and oils', input_type=str]\n", | |
| " For further information visit https://errors.pydantic.dev/2.11/v/literal_error. Set `provide_traceback=True` for traceback.\n", | |
| "2025/12/20 10:00:27 ERROR dspy.utils.parallelizer: Error for Example({'food_description': '1\\u202fL Augustiner beer', 'food_groups': [], 'total_calories': 430.0, 'source': 'golden_dataset'}) (input_keys={'food_description'}): 1 validation error for list[FoodItem]\n", | |
| "0.food_groups.0\n", | |
| " Input should be 'dairy', 'meat and alternatives', 'grain', 'fruit' or 'vegetable' [type=literal_error, input_value='beverage', input_type=str]\n", | |
| " For further information visit https://errors.pydantic.dev/2.11/v/literal_error. Set `provide_traceback=True` for traceback.\n", | |
| "2025/12/20 10:00:27 ERROR dspy.utils.parallelizer: Error for Example({'food_description': \"I just made some popcorn for a snack. It's 14 grams, popped in oil and has butter on it.\", 'food_groups': ['grain', 'dairy'], 'total_calories': 73.22, 'source': 'nutribench'}) (input_keys={'food_description'}): 2 validation errors for list[FoodItem]\n", | |
| "0.food_groups.1\n", | |
| " Input should be 'dairy', 'meat and alternatives', 'grain', 'fruit' or 'vegetable' [type=literal_error, input_value='fat and oils', input_type=str]\n", | |
| " For further information visit https://errors.pydantic.dev/2.11/v/literal_error\n", | |
| "0.food_groups.2\n", | |
| " Input should be 'dairy', 'meat and alternatives', 'grain', 'fruit' or 'vegetable' [type=literal_error, input_value='sweets and snacks', input_type=str]\n", | |
| " For further information visit https://errors.pydantic.dev/2.11/v/literal_error. Set `provide_traceback=True` for traceback.\n", | |
| "2025/12/20 10:00:27 ERROR dspy.utils.parallelizer: Error for Example({'food_description': '100\\u202fg of peanut butter', 'food_groups': ['meat and alternatives'], 'total_calories': 588.0, 'source': 'golden_dataset'}) (input_keys={'food_description'}): 2 validation errors for list[FoodItem]\n", | |
| "0.food_groups.0\n", | |
| " Input should be 'dairy', 'meat and alternatives', 'grain', 'fruit' or 'vegetable' [type=literal_error, input_value='nut and seed', input_type=str]\n", | |
| " For further information visit https://errors.pydantic.dev/2.11/v/literal_error\n", | |
| "0.food_groups.1\n", | |
| " Input should be 'dairy', 'meat and alternatives', 'grain', 'fruit' or 'vegetable' [type=literal_error, input_value='fat and oils', input_type=str]\n", | |
| " For further information visit https://errors.pydantic.dev/2.11/v/literal_error. Set `provide_traceback=True` for traceback.\n" | |
| ] | |
| }, | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| " 0%| | 0/54 [00:00<?, ?it/s]" | |
| ] | |
| }, | |
| { | |
| "name": "stderr", | |
| "output_type": "stream", | |
| "text": [ | |
| "2025/12/20 10:00:27 ERROR dspy.utils.parallelizer: Error for Example({'food_description': 'I’m treating myself to a medium latte for a little snack.', 'food_groups': ['dairy'], 'total_calories': 206.4, 'source': 'nutribench'}) (input_keys={'food_description'}): 1 validation error for list[FoodItem]\n", | |
| "0.food_groups.1\n", | |
| " Input should be 'dairy', 'meat and alternatives', 'grain', 'fruit' or 'vegetable' [type=literal_error, input_value='beverage', input_type=str]\n", | |
| " For further information visit https://errors.pydantic.dev/2.11/v/literal_error. Set `provide_traceback=True` for traceback.\n" | |
| ] | |
| }, | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "Average Metric: 0.00 / 1 (0.0%): 0%| | 0/54 [00:00<?, ?it/s]" | |
| ] | |
| }, | |
| { | |
| "name": "stderr", | |
| "output_type": "stream", | |
| "text": [ | |
| "2025/12/20 10:00:27 ERROR dspy.utils.parallelizer: Error for Example({'food_description': \"I'm snacking on a regular bag of cheese popcorn alongside a can of cola for lunch.\", 'food_groups': ['grain', 'dairy'], 'total_calories': 611.84, 'source': 'nutribench'}) (input_keys={'food_description'}): 2 validation errors for list[FoodItem]\n", | |
| "0.food_groups.0\n", | |
| " Input should be 'dairy', 'meat and alternatives', 'grain', 'fruit' or 'vegetable' [type=literal_error, input_value='sweets and snacks', input_type=str]\n", | |
| " For further information visit https://errors.pydantic.dev/2.11/v/literal_error\n", | |
| "1.food_groups.0\n", | |
| " Input should be 'dairy', 'meat and alternatives', 'grain', 'fruit' or 'vegetable' [type=literal_error, input_value='beverage', input_type=str]\n", | |
| " For further information visit https://errors.pydantic.dev/2.11/v/literal_error. Set `provide_traceback=True` for traceback.\n" | |
| ] | |
| }, | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "Average Metric: 1.00 / 2 (50.0%): 2%|▏ | 1/54 [00:00<00:00, 350.64it/s]" | |
| ] | |
| }, | |
| { | |
| "name": "stderr", | |
| "output_type": "stream", | |
| "text": [ | |
| "2025/12/20 10:00:27 ERROR dspy.utils.parallelizer: Error for Example({'food_description': 'I snacked on 35 grams of dry pastry with 3 grams of margarine, plus a drink of 100 grams of apricot nectar and 30 grams of wheat bread.', 'food_groups': ['grain', 'dairy', 'fruit'], 'total_calories': 289.0, 'source': 'nutribench'}) (input_keys={'food_description'}): 3 validation errors for list[FoodItem]\n", | |
| "0.food_groups.0\n", | |
| " Input should be 'dairy', 'meat and alternatives', 'grain', 'fruit' or 'vegetable' [type=literal_error, input_value='sweets and snacks', input_type=str]\n", | |
| " For further information visit https://errors.pydantic.dev/2.11/v/literal_error\n", | |
| "1.food_groups.0\n", | |
| " Input should be 'dairy', 'meat and alternatives', 'grain', 'fruit' or 'vegetable' [type=literal_error, input_value='fat and oils', input_type=str]\n", | |
| " For further information visit https://errors.pydantic.dev/2.11/v/literal_error\n", | |
| "2.food_groups.0\n", | |
| " Input should be 'dairy', 'meat and alternatives', 'grain', 'fruit' or 'vegetable' [type=literal_error, input_value='beverage', input_type=str]\n", | |
| " For further information visit https://errors.pydantic.dev/2.11/v/literal_error. Set `provide_traceback=True` for traceback.\n", | |
| "2025/12/20 10:00:27 ERROR dspy.utils.parallelizer: Error for Example({'food_description': 'For breakfast, I enjoyed 11.2 grams of cornstarch Atole, along with 69 grams of fried eggs, 115.8 grams of cooked maize flour, and 113 grams of homemade tomato chirmol sauce.', 'food_groups': ['grain', 'meat and alternatives', 'vegetable'], 'total_calories': 687.0, 'source': 'nutribench'}) (input_keys={'food_description'}): 1 validation error for list[FoodItem]\n", | |
| "0.food_groups.0\n", | |
| " Input should be 'dairy', 'meat and alternatives', 'grain', 'fruit' or 'vegetable' [type=literal_error, input_value='beverage', input_type=str]\n", | |
| " For further information visit https://errors.pydantic.dev/2.11/v/literal_error. Set `provide_traceback=True` for traceback.\n" | |
| ] | |
| }, | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "Average Metric: 2.00 / 4 (50.0%): 6%|▌ | 3/54 [00:00<00:00, 114.18it/s]" | |
| ] | |
| }, | |
| { | |
| "name": "stderr", | |
| "output_type": "stream", | |
| "text": [ | |
| "2025/12/20 10:00:27 ERROR dspy.utils.parallelizer: Error for Example({'food_description': '2 spoons of peanut butter', 'food_groups': ['meat and alternatives'], 'total_calories': 180.0, 'source': 'golden_dataset'}) (input_keys={'food_description'}): 2 validation errors for list[FoodItem]\n", | |
| "0.food_groups.0\n", | |
| " Input should be 'dairy', 'meat and alternatives', 'grain', 'fruit' or 'vegetable' [type=literal_error, input_value='nut and seed', input_type=str]\n", | |
| " For further information visit https://errors.pydantic.dev/2.11/v/literal_error\n", | |
| "0.food_groups.1\n", | |
| " Input should be 'dairy', 'meat and alternatives', 'grain', 'fruit' or 'vegetable' [type=literal_error, input_value='fat and oils', input_type=str]\n", | |
| " For further information visit https://errors.pydantic.dev/2.11/v/literal_error. Set `provide_traceback=True` for traceback.\n" | |
| ] | |
| }, | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "Average Metric: 3.00 / 5 (60.0%): 7%|▋ | 4/54 [00:00<00:00, 100.01it/s]" | |
| ] | |
| }, | |
| { | |
| "name": "stderr", | |
| "output_type": "stream", | |
| "text": [ | |
| "2025/12/20 10:00:27 ERROR dspy.utils.parallelizer: Error for Example({'food_description': '3 buns with peanut butter and jelly (small)', 'food_groups': ['fruit', 'grain', 'meat and alternatives'], 'total_calories': 615.0, 'source': 'golden_dataset'}) (input_keys={'food_description'}): 1 validation error for list[FoodItem]\n", | |
| "0.food_groups.1\n", | |
| " Input should be 'dairy', 'meat and alternatives', 'grain', 'fruit' or 'vegetable' [type=literal_error, input_value='nut and seed', input_type=str]\n", | |
| " For further information visit https://errors.pydantic.dev/2.11/v/literal_error. Set `provide_traceback=True` for traceback.\n" | |
| ] | |
| }, | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "Average Metric: 4.00 / 6 (66.7%): 9%|▉ | 5/54 [00:00<00:00, 98.44it/s] " | |
| ] | |
| }, | |
| { | |
| "name": "stderr", | |
| "output_type": "stream", | |
| "text": [ | |
| "2025/12/20 10:00:27 ERROR dspy.utils.parallelizer: Error for Example({'food_description': \"I made myself a breakfast of brewed coffee weighing 240 grams and a chocolate chip muffin that's 70 grams.\", 'food_groups': ['grain', 'dairy'], 'total_calories': 281.0, 'source': 'nutribench'}) (input_keys={'food_description'}): 2 validation errors for list[FoodItem]\n", | |
| "0.food_groups.0\n", | |
| " Input should be 'dairy', 'meat and alternatives', 'grain', 'fruit' or 'vegetable' [type=literal_error, input_value='beverage', input_type=str]\n", | |
| " For further information visit https://errors.pydantic.dev/2.11/v/literal_error\n", | |
| "1.food_groups.1\n", | |
| " Input should be 'dairy', 'meat and alternatives', 'grain', 'fruit' or 'vegetable' [type=literal_error, input_value='sweets and snacks', input_type=str]\n", | |
| " For further information visit https://errors.pydantic.dev/2.11/v/literal_error. Set `provide_traceback=True` for traceback.\n", | |
| "2025/12/20 10:00:27 ERROR dspy.utils.parallelizer: Error for Example({'food_description': 'For dinner, I’m having 3 grams of nacho cheese flavored Doritos with about 41 grams of topping from a meat pizza.', 'food_groups': ['grain', 'dairy', 'meat and alternatives', 'vegetable'], 'total_calories': 147.18, 'source': 'nutribench'}) (input_keys={'food_description'}): 1 validation error for list[FoodItem]\n", | |
| "0.food_groups.0\n", | |
| " Input should be 'dairy', 'meat and alternatives', 'grain', 'fruit' or 'vegetable' [type=literal_error, input_value='sweets and snacks', input_type=str]\n", | |
| " For further information visit https://errors.pydantic.dev/2.11/v/literal_error. Set `provide_traceback=True` for traceback.\n" | |
| ] | |
| }, | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "Average Metric: 6.00 / 8 (75.0%): 13%|█▎ | 7/54 [00:00<00:00, 102.35it/s]" | |
| ] | |
| }, | |
| { | |
| "name": "stderr", | |
| "output_type": "stream", | |
| "text": [ | |
| "2025/12/20 10:00:27 ERROR dspy.utils.parallelizer: Error for Example({'food_description': 'For my snack, I prepared 150g of fresh peeled apple, 33g of beans, and 60g of white bread. I also have 9.4g of raw onion, 35.5g of potato, and 11.5g of salad tomato, plus 5.8g of olive oil and 320g of tap water.', 'food_groups': ['fruit', 'meat and alternatives', 'grain', 'vegetable'], 'total_calories': 370.0, 'source': 'nutribench'}) (input_keys={'food_description'}): 3 validation errors for list[FoodItem]\n", | |
| "1.food_groups.0\n", | |
| " Input should be 'dairy', 'meat and alternatives', 'grain', 'fruit' or 'vegetable' [type=literal_error, input_value='legume', input_type=str]\n", | |
| " For further information visit https://errors.pydantic.dev/2.11/v/literal_error\n", | |
| "6.food_groups.0\n", | |
| " Input should be 'dairy', 'meat and alternatives', 'grain', 'fruit' or 'vegetable' [type=literal_error, input_value='fat and oils', input_type=str]\n", | |
| " For further information visit https://errors.pydantic.dev/2.11/v/literal_error\n", | |
| "7.food_groups.0\n", | |
| " Input should be 'dairy', 'meat and alternatives', 'grain', 'fruit' or 'vegetable' [type=literal_error, input_value='beverage', input_type=str]\n", | |
| " For further information visit https://errors.pydantic.dev/2.11/v/literal_error. Set `provide_traceback=True` for traceback.\n", | |
| "2025/12/20 10:00:27 ERROR dspy.utils.parallelizer: Error for Example({'food_description': 'two bananas and a cup of coffee with sugar and milk', 'food_groups': ['fruit', 'dairy'], 'total_calories': 243.0, 'source': 'golden_dataset'}) (input_keys={'food_description'}): 1 validation error for list[FoodItem]\n", | |
| "1.food_groups.0\n", | |
| " Input should be 'dairy', 'meat and alternatives', 'grain', 'fruit' or 'vegetable' [type=literal_error, input_value='beverage', input_type=str]\n", | |
| " For further information visit https://errors.pydantic.dev/2.11/v/literal_error. Set `provide_traceback=True` for traceback.\n" | |
| ] | |
| }, | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "Average Metric: 7.00 / 10 (70.0%): 17%|█▋ | 9/54 [00:00<00:00, 113.91it/s]" | |
| ] | |
| }, | |
| { | |
| "name": "stderr", | |
| "output_type": "stream", | |
| "text": [ | |
| "2025/12/20 10:00:27 ERROR dspy.utils.parallelizer: Error for Example({'food_description': \"For a snack, I'm having a small single serving bag of Cheetos and a medium frosted cinnamon bun.\", 'food_groups': ['grain', 'dairy'], 'total_calories': 563.6, 'source': 'nutribench'}) (input_keys={'food_description'}): 2 validation errors for list[FoodItem]\n", | |
| "0.food_groups.0\n", | |
| " Input should be 'dairy', 'meat and alternatives', 'grain', 'fruit' or 'vegetable' [type=literal_error, input_value='sweets and snacks', input_type=str]\n", | |
| " For further information visit https://errors.pydantic.dev/2.11/v/literal_error\n", | |
| "1.food_groups.1\n", | |
| " Input should be 'dairy', 'meat and alternatives', 'grain', 'fruit' or 'vegetable' [type=literal_error, input_value='sweets and snacks', input_type=str]\n", | |
| " For further information visit https://errors.pydantic.dev/2.11/v/literal_error. Set `provide_traceback=True` for traceback.\n" | |
| ] | |
| }, | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "Average Metric: 8.00 / 12 (66.7%): 20%|██ | 11/54 [00:00<00:00, 129.76it/s]" | |
| ] | |
| }, | |
| { | |
| "name": "stderr", | |
| "output_type": "stream", | |
| "text": [ | |
| "2025/12/20 10:00:27 ERROR dspy.utils.parallelizer: Error for Example({'food_description': \"I'm enjoying a peanut butter and jelly sandwich on wheat bread and a cup of unsweetened water for lunch.\", 'food_groups': ['meat and alternatives', 'fruit', 'grain'], 'total_calories': 402.08, 'source': 'nutribench'}) (input_keys={'food_description'}): 2 validation errors for list[FoodItem]\n", | |
| "0.food_groups.2\n", | |
| " Input should be 'dairy', 'meat and alternatives', 'grain', 'fruit' or 'vegetable' [type=literal_error, input_value='nut and seed', input_type=str]\n", | |
| " For further information visit https://errors.pydantic.dev/2.11/v/literal_error\n", | |
| "1.food_groups.0\n", | |
| " Input should be 'dairy', 'meat and alternatives', 'grain', 'fruit' or 'vegetable' [type=literal_error, input_value='beverage', input_type=str]\n", | |
| " For further information visit https://errors.pydantic.dev/2.11/v/literal_error. Set `provide_traceback=True` for traceback.\n" | |
| ] | |
| }, | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "Average Metric: 8.00 / 13 (61.5%): 22%|██▏ | 12/54 [00:00<00:00, 134.75it/s]" | |
| ] | |
| }, | |
| { | |
| "name": "stderr", | |
| "output_type": "stream", | |
| "text": [ | |
| "2025/12/20 10:00:27 ERROR dspy.utils.parallelizer: Error for Example({'food_description': 'avocado toast', 'food_groups': ['fruit', 'vegetable', 'grain'], 'total_calories': 250.0, 'source': 'golden_dataset'}) (input_keys={'food_description'}): 1 validation error for list[FoodItem]\n", | |
| "0.food_groups.2\n", | |
| " Input should be 'dairy', 'meat and alternatives', 'grain', 'fruit' or 'vegetable' [type=literal_error, input_value='fat and oils', input_type=str]\n", | |
| " For further information visit https://errors.pydantic.dev/2.11/v/literal_error. Set `provide_traceback=True` for traceback.\n", | |
| "2025/12/20 10:00:27 ERROR dspy.utils.parallelizer: Error for Example({'food_description': \"I'm snacking on a raw banana along with a piece of hard candy.\", 'food_groups': ['fruit'], 'total_calories': 135.78, 'source': 'nutribench'}) (input_keys={'food_description'}): 1 validation error for list[FoodItem]\n", | |
| "1.food_groups.0\n", | |
| " Input should be 'dairy', 'meat and alternatives', 'grain', 'fruit' or 'vegetable' [type=literal_error, input_value='sweets and snacks', input_type=str]\n", | |
| " For further information visit https://errors.pydantic.dev/2.11/v/literal_error. Set `provide_traceback=True` for traceback.\n" | |
| ] | |
| }, | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "Average Metric: 31.00 / 54 (57.4%): 100%|██████████| 54/54 [00:00<00:00, 461.48it/s]" | |
| ] | |
| }, | |
| { | |
| "name": "stderr", | |
| "output_type": "stream", | |
| "text": [ | |
| "2025/12/20 10:00:27 INFO dspy.evaluate.evaluate: Average Metric: 31.0 / 54 (57.4%)\n" | |
| ] | |
| }, | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "\n", | |
| "Evaluating bootstrap_fewshot_random_search for openrouter/google/gemini-3-flash-preview\n", | |
| "Average Metric: 31.00 / 54 (57.4%): 100%|██████████| 54/54 [00:00<00:00, 613.63it/s]" | |
| ] | |
| }, | |
| { | |
| "name": "stderr", | |
| "output_type": "stream", | |
| "text": [ | |
| "2025/12/20 10:00:27 INFO dspy.evaluate.evaluate: Average Metric: 31.0 / 54 (57.4%)\n" | |
| ] | |
| }, | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "\n", | |
| "Evaluating vanilla for openrouter/google/gemini-3-flash-preview\n", | |
| "Average Metric: 32.00 / 54 (59.3%): 100%|██████████| 54/54 [00:00<00:00, 611.89it/s]" | |
| ] | |
| }, | |
| { | |
| "name": "stderr", | |
| "output_type": "stream", | |
| "text": [ | |
| "2025/12/20 10:00:28 INFO dspy.evaluate.evaluate: Average Metric: 32.0 / 54 (59.3%)\n" | |
| ] | |
| }, | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "\n" | |
| ] | |
| } | |
| ], | |
| "source": [ | |
| "deepseek_no_thinking = dspy.LM(\n", | |
| " \"deepseek/deepseek-chat\",\n", | |
| " temperature=0.00,\n", | |
| ")\n", | |
| "\n", | |
| "\n", | |
| "deepseek_thinking = dspy.LM(\n", | |
| " \"deepseek/deepseek-reasoner\",\n", | |
| " temperature=0.00,\n", | |
| ")\n", | |
| "\n", | |
| "\n", | |
| "gemini_3_flash = dspy.LM(\n", | |
| " \"openrouter/google/gemini-3-flash-preview\",\n", | |
| " temperature=0.0,\n", | |
| " extra_body={\"reasoning\": {\"enabled\": False}},\n", | |
| ")\n", | |
| "\n", | |
| "for lm in [\n", | |
| " deepseek_no_thinking,\n", | |
| " deepseek_thinking,\n", | |
| " gemini_3_flash,\n", | |
| "]:\n", | |
| " with dspy.context(lm=lm):\n", | |
| " model_name = lm.model.split(\"/\")[-1].replace(\"-\", \"_\")\n", | |
| " modules_and_names = [\n", | |
| " (module_current, f\"optimized_{model_name}\"),\n", | |
| " (module_gepa, f\"gepa_{model_name}\"),\n", | |
| " (module_mipro_v2, f\"mipro_{model_name}\"),\n", | |
| " (\n", | |
| " module_bootstrap_fewshot_random_search,\n", | |
| " f\"bootstrap_fewshot_random_search_{model_name}\",\n", | |
| " ),\n", | |
| " (module_vanilla, f\"vanilla_{model_name}\"),\n", | |
| " ]\n", | |
| " for module, name in modules_and_names:\n", | |
| " try:\n", | |
| " print(f\"Evaluating {name.replace('_'+model_name,'')} for {lm.model}\")\n", | |
| " result = evaluate(module)\n", | |
| " er.add_result(name, result.score)\n", | |
| " except Exception as e:\n", | |
| " print(f\"Error evaluating {name} for {lm.model}: {e}\")\n", | |
| " er.add_result(name, 0)" | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": 16, | |
| "metadata": {}, | |
| "outputs": [ | |
| { | |
| "data": { | |
| "image/png": "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v8Vrd7rTnfqf4qUvfWn/XX/3d3+3/NqFF164dNvb3nZp7733XjrnnHP610488cT+ffvss8/S9773vS0+s373+te/fvm1u971rks3velNl84///zl1zZv3rx0u9vdbumGN7zh8muHHnro0r3vfe+lbcXnXnzxxZcZxxWucIWlZz/72cuv/fVf/3U/the/+MWX+Qzjwb/+67/273nc4x439z2zrrHw+jOe8Yzlv/t/r/3Kr/zKqu7Tm9/85v79xx133PJrv/mbv7m02267LX3mM5+ZO6bXvOY1/b/7r//6ry3u3dWudrWlhz3sYUtbW2Pu5Tve8Y7l1/7yL/9y6Sd+4if6z7zJTW6y9E//9E/9/xc3v/nN+/esltNPP/0yczP8XXuviqOPPrr/3QknnND//aSTTurH4zU/P//zP7/0gx/8YOmss85a2n///Zfe8pa3rHpMIYSws5A08hBCCGE7EN3FtkQQ99prr+X/13BKtE+UVxTV3+chdViEtW02pVHV4x73uD6K+JGPfGSL96vbXakOt1K81ZnrMi06aSx+pEoffvjhfZ2uKC10oRbV9dq2IKq+2267LUevfbZ08p/+6Z/uo+KFKPrVrna1vpv1kIoie4//f8YznjH3PdvD7/3e7614n2QImJfb3OY2/d9r3KK+ovb3uc99ZkbVa0zmVyq6SHahIZnP/PVf//UVxyZyLCtBJkR9t6737u/b3/727qEPfWj3yEc+cot/I1L+4Q9/uFsE1UnffRxS9f/1HunhX/jCF/ofa8UY3Gtp7u63sarbvvWtb90dfPDB/dqVah9CCDszMbZDCCGE7UD9MRiqq8WRR3e729369F8GLINYh2esZGx/61vf6lOdy3AtbnzjGy//vkXK9taQaizge+SRR/bjaH/KoFUnDp2lpSqrp1YvLR1YevHWYJCqVzZ2BhuD2uf7t+31qstmkEmtn4f3SDtnfC6SWXPFESGVXX08w9uY63017tNPP713uGgUthLuM4P8TW960/JrDG+p++r3V0LqOGdMGe4aoEnTfu1rX9un+rt3QweFMRvbIiing2O9hnBCtO8pB9DNbnazvheBtaofgAZr6uzNqTpz41YCIVXf8WMhhLAzk5rtEEIIYTuNbcafmuLVwFjUVEpd74tf/OI+uqdDuKg1g3RWTfX20hpA86jv08BMJHsWGrzhjne8Yz/+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IYSwYGJshxBCCCGEEEIICybGdgghhBBCCCGEsGBibIcQQgghhBBCCAsmxnYIIYQQQgghhLBgYmyHEEIIIYQQQggLJsZ2CCGEEEIIIYSwYGJshxBCCCGEEEIICybGdgghhBBCCCGEsGBibIcQQgghhBBCCAsmxnZYke985zvdr//6r3f77bdft9dee3U3velNu89+9rPLv19aWuqe/vSnd9e4xjX639/tbnfrvv71r2dWQwghhBBCCLs0MbbDXL7//e93t7/97bvdd9+9O+aYY7r//M//7P78z/+8+4mf+Inl97zoRS/qXv7yl3evfvWru0996lPdla50pe7www/vzj///MxsCCGEEEIIYZdlw5LQZAgzePKTn9x9/OMf7z760Y/OnB9L55rXvGb3pCc9qfvDP/zD/rWzzz67O/DAA7s3vOEN3S//8i9vdV7POeec7ipXuUr/7/bZZ5/chxBCCCGEEMK6ZVvsl0S2w1ze9a53dYcddlj34Ac/uDvggAO6n/u5n+te+9rXLv/+xBNP7E499dQ+dbyw8G5961t3xx9/fGY2hBBCCCGEsMuyaa0HENYv3/zmN7tXvepV3ROf+MTuT/7kT7rPfOYz3eMe97hujz326B72sIf1hjZEslv8vX435IILLuh/Ws8QLrroov4Hu+22W/+zefPm/qeo1y+++OI+qr611zdu3Nht2LBh+XPb1+H9q3l906ZN/ee2r/tc7x+Ocd7ruabcp6y9PE+REZHl2Z+y50aPiG4UHfaiHV4vH9oWKxFjO8zF4hPZfv7zn9//XWT7K1/5Sl+fzdjeHo466qjuWc961mVe/8IXvtDXe2P//ffvbnCDG/SR89NPP335PQcddFD/87Wvfa1P2yiuf/3r95F3YzvvvPOWX7/RjW7U7bvvvv1ntw/kIYcc0jsM2kZvcK0XXnhh96UvfWn5NQ/oLW95y/77TjjhhOXXNYM79NBDuzPOOKN3SrSR/Rvf+MbdKaec0p188snLr+eacp+y9vI8RUZElmd/yp4bPSK6UXTYL+zwevm5557brZbUbIe5XOc61+nufve7d3/1V3+1/JpI93Of+9y+S7nFbKEyZm92s5stv+dOd7pT//eXvexlq4psH3zwwd2ZZ565XPOQKHCiwDuqp7MdY7Iqcp+y9vI8RUZElmd/yp4bPWLn043YL05qWk3NdoztMJdf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RX74wx92v/Zrv9a99rWv7X7iJ35i+fWzzz67e93rXte9+MUv7u5yl7t0t7jFLbrXv/713Sc+8Ynuk5/8ZGY1hBBCCCGEsEuTyHZYEWni9773vbu73e1u3XOf+9zl1z/3uc91P/7xj/vXixvd6Ebdta997e7444/vbnOb28z8vAsuuKD/Kc4555z+z4suuqj/wW677db/bN68uf8p6vWLL764W1pa2urrGzdu7FPf63Pb1+H9817ftOHSz7loaUO3oVvqNm649L1+e/EKr+/WLXW7Na+7is1e37C0hYdr85Lfbeg2bljqmrd3Fy/5rEteb8c/75ou+eau29R+SD/2bs7r415TzeO8+9TOb11r+9rKY1/8Na1m7WGl+zTr9bGuCcMxWhfWR71e33151t6irqldr5s2berXRfv8GXv/5wTP02quqdZtZERkRGTE+pERrXzb2uvrQY9oyTXlPmXtdTvV83RZPXw+MbbDXN7ylrd0n//85/s08iGnnnpqt8cee3T77rvvFq8feOCB/e/mcdRRR3XPetazLvP6F77whe5KV7pS///7779/d4Mb3KA78cQTu9NPP335PQcddFD/87Wvfa2PrBfXv/71uwMOOKD7yle+0p133nlbGP/G57PbB1I6vLF/9rOf3WIMhx12WHfhhRd2X/rSl7p7XOeSB++izV33gW9v7Pbbq+tudeClD+MPL+y6407Z2B2091J306td+jCefl7Xfea0jd0N9l3qbrjvpa+f9IMN3ZfP3NDd5KpL3cFXvvT1r5+1of+5+QGbu/33unQsXz5jQ3fSDzd0t7/G5i3GOe+a9t69686/qFsed/GBb+3W7bmp6+54rUtfn+KasNJ9asf56dN26844r+vucvDmblNjOR33nd0mu6aa45XWHla6T3vv0U12TTjjjDO6b37zm8uvK+W48Y1v3J1yyindySefvPwdl2ftLeqaan5teLe85S37dXHCCScsv3evvS4ZwBTP02quiRyJjIiMiIxYXzLi0EMP3arcK9aDHlHkmnKfsvZ2vufp3HPP7VbLhqXWbA/h/3PSSSf1i/yDH/zgcq32L/zCL3Q3u9nNupe+9KXdm970pu7hD3/4FlFq3OpWt+rufOc7dy984QtXHdk++OCDuzPPPLPbZ5991o1H+n4vOHbdRLbf9eRf3Oo13fuoY9dVZPuYI49Y8T7d96hj1lVk+53/f45XWnv3fN4x6yay/b6nHbFV722t4fUQ2a75Xckjfa/nH7tuItvveeol9aOREZERkRHrR0Yksr32ulFL7lOi9bvy2jvnnHP6BtEM8rJf5pHIdpiJNPHvfe973c1vfvPl1yyw4447rvuLv/iL7v3vf3/vbTrrrLO2iG7rRn71q1997qxe4QpX6H8usxA3bep/WmphD6mHb7WvDz93Na8zclooEKUYrOZ1Cv/mWa8vbegNhSEMim7O67PGednXLvn3s8Yy7/Wxr2ml+zSc30vGOHsOprim1a69le7T7DGOd03zxlivD797e9beoq5pOL82v1nreornafYYt3y90tojIyIjIiPWl4zYmtxb7etT6BFDck25T1l7O8/zNG+sM8e/6neGXYq73vWu3Ze//OUtXhPJlgLyx3/8x300evfdd+8+9KEP9Ud+4atf/Wr37W9/u7vtbW+7RqMOIYQQQgghhPVBjO0wkytf+crdz/7sz27xmppqKRP1+iMe8YjuiU98YnfVq161T6F47GMf2xva85qjhRBCCCGEEMKuQoztsN285CUv6VMvRLbVYR9++OHdK1/5ysxoCCGEEEIIYZcnxnZYNR/+8Ie3+Puee+7ZHX300f1PCCGEEEIIIYRLuWxFeAghhBBCCCGEEC4XMbZDCCGEEEIIIYQFE2M7hBBCCCGEEEJYMDG2QwghhBBCCCGEBRNjO4QQQgghhBBCWDAxtkMIIYQQQgghhAUTY3sn5sILL+y++tWvdhdddNFaDyWEEEIIIYQQdilibO+E/OhHP+oe8YhHdFe84hW7m9zkJt23v/3t/vXHPvax3Qte8IK1Hl4IIYQQQggh7PTE2N4JecpTntL9+7//e/fhD3+423PPPZdfv9vd7ta99a1vXdOxhRBCCCGEEMKuwKa1HkBYPO94xzt6o/o2t7lNt2HDhuXXRbm/8Y1vZMpDCCGEEEIIYWQS2d4JOf3007sDDjjgMq+fe+65WxjfIYQQQgghhBDGIcb2Tshhhx3Wvfe9713+exnYf/VXf9Xd9ra3XcORhRBCCCGEEMKuQdLId0Ke//znd/e85z27//zP/+w7kb/sZS/r//8Tn/hE95GPfGSthxdCCCGEEEIIOz2JbO+E3OEOd+gbpDG0b3rTm3Yf+MAH+rTy448/vrvFLW6x1sMLIYQQQgghhJ2eRLZ3Mn784x93v/u7v9sdeeSR3Wtf+9q1Hk4IIYQQQggh7JIksr2Tsfvuu3f/9E//tNbDCCGEEEIIIYRdmhjbOyH3v//9++O/QgghhBBCCCGsDUkj3wm54Q1v2D372c/uPv7xj/c12le60pW2+P3jHve4NRtbCCGEEEIIIewKxNjeCXnd617X7bvvvt3nPve5/qfFMWAxtkMIIYQQQghhXGJs74SceOKJaz2EEEIIIYQQQtilSc32Ts7S0lL/E0IIIYQQQghhOmJs76S88Y1v7M/Y3muvvfqfQw45pPvbv/3btR5WCCGEEEIIIewSJI18J+TFL35xf872Yx7zmO72t799/9rHPvax7vd+7/e6M844o3vCE56w1kMMIYQQQgghhJ2aGNs7Ia94xSu6V73qVd1v/uZvLr923/vet7vJTW7SPfOZz4yxHUIIIYQQQggjkzTynZDvfve73e1ud7vLvO41vwshhBBCCCGEMC4xtndCfvInf7L7h3/4h8u8/ta3vrU/gzuEEEIIIYQQwrgkjXwn5FnPelb30Ic+tDvuuOOWa7Y//vGPdx/60IdmGuEhhBBCCCGEEBZLIts7IQ960IO6T33qU93Vrna17h3veEf/4/8//elPdw94wAPWenghhBBCCCGEsNOTyPZOyi1ucYvu7/7u79Z6GCGEEEIIl5vDn/PedTOL7z/y3ms9hBDCDkIi2zsh73vf+7r3v//9l3nda8ccc8yajCmEEEIIIYQQdiVibO+EPPnJT+4uvvjiy7y+tLTU/261OD7skEMO6fbZZ5/+57a3ve0Wxvr555/fPfrRj+7222+/bu+99+7T10877bSFXUcIIYQQQggh7KjE2N4J+frXv979zM/8zGVev9GNbtT993//96o/56CDDupe8IIXdJ/73Oe6z372s91d7nKX7n73u1/3H//xH/3vn/CEJ3Tvfve7u7e97W3dRz7yke6UU07pHvjABy70WkIIIYQQQghhRyQ12zshV7nKVbpvfvOb3XWve90tXmdoX+lKV1r159znPvfZ4u/Pe97z+mj3Jz/5yd4Qf93rXte96U1v6o1wvP71r+9ufOMb97+/zW1us6CrCSGEEEIIIYQdjxjbOyGiz49//OO7t7/97d0NbnCDZUP7SU96Unff+953uz5TWroI9rnnntunk4t2//jHP+7udre7bRE5v/a1r90df/zx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6/bc1HV3vNalr09xTVjpPrXj/PRpu3VnnNd1dzl4c7epsZyO+85uk11TzfFKaw8r3ae99+gmuyacccYZ3Te/+c3l169ylav0WSVKOU4++eTl77g8a29R11Tza8PTB8K6OOGEE5bfu9delwxgiudpNddEjkRGREZERkRGREZEj9iV9YhiPejlxUp6xKGHHjrZNbUltVtjw1Jrtocwh9///d/vjjnmmO5jH/vYssCQPv7whz98i0g1bnWrW3V3vvOduxe+8IWrimwffPDB3Zlnntnts88+68aDdr8XHLtuItvvevIvbvWa7n3Usesqsn3MkUeseJ/ue9Qx6yqy/c7/P8crrb17Pu+YdeORft/Tjtiq97bW8HqIbNf8ruSRvtfzj103ke33PPXel/w+MiIyIjIiMiIyYo6Mjx6xs+sR7djXWi9fb5Ft9ovm0Azysl/mkch22CqPecxjuve85z3dcccdt2xo4+pXv3rvcTrrrLO2iG7rRu53s7jCFa7Q/1xmIW7a1P+01MIeUg/fal8ffu5qXiecWgi8EmSreZ3Cv3nW60sbekNhCIHdzXl91jgv+9ol/37WWOa9PvY1rXSfhvN7yRhnz8EU17TatbfSfZo9xvGuad4Y6/Xhd2/P2lvUNQ3n1+Y3a11P8TzNHuOWr1dae2REZERkRGREZMSlsny1Mjt6xM6hR6z29Sn08iHz9IiprmneWGeRbuRhLjw3DO23v/3t3b/+679217ve9bb4/S1ucYtu99137z70oQ8tv+ZosG9/+9vdbW9728xsCCGEEEIIYZclke0wF8d+SRV/5zvf2Z+1XXXY6h/URvjzEY94RPfEJz6xb5omjeKxj31sb2inE3kIIYQQQghhVybGdpjLq171qv7PX/iFX9jidcd7/dZv/Vb//y95yUv69IsHPehBfS324Ycf3r3yla/MrIYQQgghhBB2aWJsh7mspnfennvu2R199NH9TwghhBBCCCGES0jNdgghhBBCCCGEsGBibIcQQgghhBBCCAsmxnYIIYQQQgghhLBgYmyHEEIIIYQQQggLJsZ2CCGEEEIIIYSwYGJshxBCCCGEEEIICybGdgghhBBCCCGEsGByznYIIYQQQgghhGUOf857181svP/Ie3c7KolshxBCCCGEEEIICybGdgghhBBCCCGEsGBibIcQQgghhBBCCAsmxnYIIYQQQgghhLBgYmyHEEIIIYQQQggLJsZ2CCGEEEIIIYSwYGJshxBCCCGEEEIICybGdgghhBBCCCGEsGBibIcQQgghhBBCCAsmxnYIIYQQQgghhLBgYmyHEEIIIYQQQggLJsZ2CCGEEEIIIYSwYGJshxBCCCGEEEIICybGdgghhBBCCCGEsGBibIcQQgghhBBCCAsmxnYIIYQQQgghhLBgYmyHEEIIIYQQQggLJsZ2CCGEEEIIIYSwYGJshxBCCCGEEEIICybGdgghhBBCCCGEsGBibIcQQgghhBBCCAsmxnYIIYQQQgghhLBgYmyHuRx33HHdfe5zn+6a17xmt2HDhu4d73jHFr9fWlrqnv70p3fXuMY1ur322qu7293u1n3961/PjIYQQgghhBB2eWJsh7mce+653aGHHtodffTRM3//ohe9qHv5y1/evfrVr+4+9alPdVe60pW6ww8/vDv//PMzqyGEEEIIIYRdmk1rPYCwfrnnPe/Z/8xCVPulL31p97SnPa273/3u17/2xje+sTvwwAP7CPgv//IvTzzaEEIIIYQQQlg/JLIdtosTTzyxO/XUU/vU8eIqV7lKd+tb37o7/vjjM6shhBBCCCGEXZpEtsN2wdCGSHaLv9fvZnHBBRf0P8U555zT/3nRRRf1P9htt936n82bN/c/Rb1+8cUX95H1rb2+cePGvta8Prd9Hd4/7/VNGy79nIuWNnQbuqVu44ZL3+u3F6/w+m7dUrdb87qr2Oz1DUtbeLg2L/ndhm7jhqWueXt38ZLPuuT1dvzzrumSb+66Te2H9GPv5rw+7jXVPM67T+381rW2r6089sVf02rWHla6T7NeH+uaMByjdWF91Ov13Zdn7S3qmtr1umnTpn5dtM+fsfd/TvA8reaaat1GRkRGREZERkRGzJPx0SOiR6wvPWJjowNt7fXLa2tcVg+fT4ztMClHHXVU96xnPesyr3/hC1/oa76x//77dze4wQ366Pnpp5++/J6DDjqo//na177WnX322cuvX//61+8OOOCA7itf+Up33nnnLb9+oxvdqNt33337z24fyEMOOaTbY489us9+9rNbjOGwww7rLrzwwu5LX/pSd4/rXPLgXbS56z7w7Y3dfnt13a0OvPRh/OGFXXfcKRu7g/Ze6m56tUsfxtPP67rPnLaxu8G+S90N97309ZN+sKH78pkbuptc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Sv+OoPgXGW/md0deDzvimDPezG/WQ565yIjI4OwZOxZXucpVVvW+GNvhciFK/bCHPaw77LDDulvd6lbdS1/60u7cc8/tu5OHEEIIIYQQwq5KjO1wuXjoQx/anX766d3Tn/707tRTT+1udrObdccee+xlmqaFEEIIIYQQwq5EjO1wuZEyPi9tfFdGuvwznvGMy6TNr1cy3szvjrwedsQxZ7yZ36yHPHOREZHB2TN2btKNPIQQQgghhBBCWDC7LfoDQwghhBBCCCGEXZ0Y2yGEEEIIIYQQwoKJsR1CCCGEEEIIISyYGNshhBBCCCGEEMKCibEdwnawefPmHW7elpaW1noIIexSXHTRRWs9hJ2WHVEGhxDCSnpZu2dEZ9t5iLEdwvY8OLtd8uh861vf2mHmb8OGDd3555/frVdW2ljW+6Zz8cUX7xDjLGKoTMOmTZecrvmd73yn2xHYUdaF56xk8Nlnn73Ww9kpsRZKnu0o62JHIfM5Lu0+vN7nml6GT33qU92pp57a7xmnn376Fr9bT+woOs56I8Z2CNspZF7xild0T3rSk7ozzzxzh5jDT37yk93v/u7vdieeeGK3HrGx/OhHP+r+5m/+pvvhD3/Yv/ae97xn+XfrmY0bN/Z//vjHP94hNiSGymmnndb9/d//fbceOe+885bncb3P5ZB2vIzsfffdt3v2s5/dXXjhhd16x7r43ve+133ta1/r1iuU55IHv/zLv9w95CEPWV4v63U9lDNuR6GcGeb53e9+d/eP//iP6y5L44ILLuj/3BGeK3imyvArR9F6dn4XJYN3JDls3Z577rlbzPV65oMf/GB317vetfvIRz7SveY1r+kOPPDAZd1nPc7tCSec0L3yla/cYvw/+MEP1nRc6531vwpDWAfYaErBqw3zgAMO6P71X/913RpYw/F8//vf797xjncsK6br0eP75je/uXv0ox/dvfWtb+3uda97dfe9733XreLfzq818Nu//dvdb/3Wb61b50Cr8BuvTd2Gecopp3TrCU6shz70od13v/vdfh79vP3tb+/+4i/+onvf+97XrXfq3r/zne/sPvvZz3a/8zu/073kJS/p9thjj2490q4L83yTm9yk+/jHP77ujKuC8nzSSSd1X/jCF/qo9rOe9axur7326tYT//M//9N94hOf6OfQeuCMM88cGTuC4V1rmOPzl37pl/r/X0/r4eijj+6e97zn9YZ2PVfHHHNMv3+QGyWb18ue/JSnPKW73e1u1xspNZe//uu/3v3qr/5q99jHPnZdOov+4z/+o/+zZHA5w3cEjj322H4P4czgVH7kIx/ZnXzyyd165e53v3t3xBFHdH/wB3/Q/fEf/3Gv//j7esRafcMb3tAdddRR3V//9V/3erC9eUdwaqwlmZ0QtkH5eP7zn9/90z/9U2+oEuYHHXRQv+m371kvGA+F71/+5V96Be+e97xnd9Ob3nR5vOtROD7iEY/obnnLW/YR+LPOOqvPGvipn/qpbr1hPtv7Tam73vWu1yt/FO31CIXf+Hj8d9999+7GN75xn0FQxvZ6UUx/7ud+rvfqi6bZ2H/v936vV0wp0hSQF7zgBes+dZix+uAHP7j7jd/4jX49X/GKV+zWK9YF54vMF0rqc57znO7hD3/4cgr8euOb3/xmd6tb3aq7z33u093+9rfvbnOb23Trjcc85jH9/S9Z8P73v7+XY9Yv4/WMM87o1jNf//rXe+chg8tzJ3tgzz337NYL1qpoGgMbD3jAA/pn7Q//8A97J+3b3va2dbUncwh5nv7sz/6s+8Y3vtHd4x736B1GP/3TP90bLIzu9ZRxRv5av6997WuXX3vhC1/Yz/MTnvCEXq9Yz9jX6A+et2td61r9vnf1q1+9W48YG32SM8D/09Pucpe7rKs9uYVjk2zwJz3N3syxfKUrXWmth7auWX/adgjrtA5XlOppT3ta99SnPnXZYGUIfPnLX+6++tWvdust3evb3/527zFlwL70pS9dVgL/67/+q48KrTd4+9UsMaT222+/7tBDD11OFVxvmw4DRZRKVOKLX/xiH10Rufj85z+/7ozXwj2/znWu05c+gDJFGeWlXi/j9czd4Q536I488sg+9ZpCzeC2Zj/60Y/2kXgedcpgZZSsNbMifje84Q27pz/96X1kpZS89TLeecaA9UuJpuytF2ZFgffZZ58+AvS///u//XpeT1HXyhb6h3/4h/5+M644Xp785Cf3WS/2i4997GPdox71qHVlXA2ffX9/4xvf2I+fLF4v1Pz++Z//eXflK1+5d8i9/OUv70s1vvSlL/Vza00wEjmZ14Ncsw7sD7IE/HAE/OzP/mz3oQ99qJdl9gwZcn63XpyIxmf/NVb7hnG++tWv7g477LC+rIBsM/71liFX9/r+979/n9ZsXuk85na9OA+NsZ0za0Pg401velP3lre8pd/nPHuyCOzPa71+UWMoeUxP8/xd97rXXTfzuu5ZCiGsyPe+973+z//5n/9Zut/97rf0+7//+0vXuMY1ll784hcv/dM//dPSXe5yl6V3vvOd62YWzzvvvP7P0047benXfu3Xlo444oila13rWksvfelLlz7xiU8s3fOe91z667/+67Ue5tJFF10093fvfve7+zG/7GUvW/rRj37Uv3bxxRcvrafxPve5z13asGHD0o1vfOOlz3/+8/1r5vaBD3zg0npgOF8nnnji0t57792P2Ro+++yzl/7xH/9x6TrXuc7S17/+9f49mzdvXpOx+t7heH/u536uH+uv//qvb/H6Qx/60KVDDjlk6dOf/vTSWlPzdf755y+94Q1v6OXBd77znf61k046aemOd7xjP9ZirdfwrHkubnOb2yztt99+S1/5yleW1gPt8/bZz3526b/+67+Wvv/97/d///a3v710t7vdbemnf/qn183c1vz++Mc/7v//He94x9LGjRuXfud3fmfpmc985vJ7zO9VrnKVpec85zn9M7he5viCCy7Y4nd/+7d/2z9///AP/7C0HjC3rXwyv7e85S37NfDnf/7ny6+TxXe96137va+exbWQa+3c1v8/+tGP7uf0l3/5l7d4L11i//33X/rnf/7nNV/H9f0f+MAH+mfsN37jN5Z+8zd/c1kueBbtcXe/+92XTj/99DXdN+btzSeccEL/fD3oQQ9aOvzww5f355V0jilo7y159trXvnbp7//+77d49h7zmMcs/ezP/uzSe97znhX//VSUPGu/32tnnXVW/9z91E/91NLb3va2NRvfjkKM7RAaatMopYlBdf3rX3/pzDPP7F//oz/6o175/+pXv7p029vedul5z3ve0oEHHrj0+Mc/fl0Im/e9731Lt7vd7XpBiBe+8IX9xvi5z32uNwoZ3De96U2XHv7wh6/peNvv/Yu/+IulP/7jP1562tOetjzPeNSjHtUbKu9///sv8+/XatO0Juq7TznllH4jv9GNbtSvib/8y79c+pd/+Zf+7xSS9aCEUDK+/OUv9/9/zjnn9Ov3//7f/7t0hzvcYemJT3xir+RRpNyDtaK9lyeffPLyeP3/nnvuufRLv/RLSz/4wQ+2cCZd97rXXfrt3/7t/j1rzQc/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s8JjeJab+8+dO9eyBR07djQVYiFEeUGVyx577GHBNHqdyVKzZzBxgP3kpJNOsvshIEXGmx5YerMRHjv33HPN0a6K0lv2J85XWl1QH8fRol+c0nJ3lJicgPr4o48+aqXmONqq0CkP3NbivfYzi2rD559/3srJWdc+NWNxGiP8rdtyiH/ysxxtIYQokLPtBsMDDzxg5eA42RgUlOCS1UZsgx61u+66y0RhMDowRjjIKRXHSEn2vqrXWaQJgR2i9J9++qkFjGh7QEV42223tf41NAR8XbLGMT7vvvtu62/DwMBg9WzPgAEDrAwTh512CKo5hBClS652EYLFiHuiS0J/tp+F/+///T/LXj/00EO2d6BDwvhKqmLo62aUljsy+XJOkk57Ze0tyfOV9piWLVvadTEpxPFycsrd2RvdkZIzVdrE1YesT/rwTzzxROvP5vYGDRqYYB7VW2S3c9lhHpjmvKMqkXLz9u3bh3//+9+pPx8hhChZlnVAd6tWrTJnnnlm9ueff/45c9JJJ2UOPPDAzIgRIzJnnXVWZoUVVsg0adIk07Jly8zGG2+cmTBhQl6GgwuxPPz666+Zo446ytbn4Ycfnvnxxx/t9mnTpmX23nvvTPPmzSvcf/To0Zmddtopc8UVV2Rv++WXXzJHH310pnbt2pkBAwboDRGiDPjtt9+y33/88ce2V/z+++/28/Tp0zMHHXRQZquttqrwN99++23mjDPOsP3kv//9b4Xf/fHHH/ZVCO65557s9+PHj8/MnDmz0vv6c2CvO/LII22fe/XVVwtyXaJ4YO116tQps8Yaa5hNduihh2bWXHPNTJcuXTJfffWV/f6II47I1KtXz9Yxdtx11123yNp48MEH7e+OP/74zJdfflllz0cIIUqRpXa22Zy//vrrTNOmTTM9e/asYKC8+eabZoj069cv89FHH9l9WrRokfnss88WOfSFSAPWa641x1qtW7euBYfi+w4bNiyz4YYbZi6//PIK9//www+z38+bNy+zzjrrZPbcc09b50KI8uGLL74wp7px48aZ3XbbLXPppZdmf/fKK69kNttsswqBZpg6dWpmv/32y0yePLlgznXMG2+8Yc49Z+2pp56aqVWrVmbUqFGL/RvfB9n7tt9+e3suoryZNGmSnXNjxozJ3nbnnXdmdt1118y1115rP3/66aeZOnXqZLbZZpvM+uuvb3YbNp7bdsccc4ydd/fdd1+VPQ8hhChlljmzzWZNRDSZDdhhhx3s8Idnn30285e//CUzcOBA+/mnn35a/isWYgmJ1yUBHzJVRPMBgxijg/VJ5tohM3XLLbdkatSokdMY9cck8q/AkRClT+wcv/zyy+ZwHHvssfY92eNVV10107t3b/v9ggULMn369MmsttpqFpirSjp27JhZaaWVLMDN3rY0fPPNNwW7LpEuXj0Rn0f+PTYYVYVUbTlUa3Tu3Dmzxx57ZObMmZM9H2+66aYKVVoElTfYYAMLOC2uakIIIcTiWebRX/T6ILKB2Ir3ry5cuNBmctLH5j1iF154oY2ZYHwSAmlCFBoXevF1Sd8k6ruIGzVq1MiU8VEMRsyPHkbEjbzvkVE3RxxxhKm1vvvuu4s8tj/mnnvuKa0BIUocV1T23tQXX3wxnHXWWabhsM8++5jgIXok3IYeCecbwmiIoLFPcOYlH6+Q1+qgG8HoTK4HoVFmeC8JaFUAgmnJxxSlBaPc0MTx3np6rpms8dNPP2Xvg9gnAp4+8o33n758bDL+3s+9jTbayAT02rVrZz/zeahVq5Zp7bz66quVzuAWQgjx5yyzs40y6/nnn2+CGzjUiG507drV1C2ZxQg418xyZKNmNqMQhQLDE6EiNzowFjAku3XrZmJ+jLNBeRwDmdE9qItzP77HmD7zzDOzj4XhOnDgQFMRFkKUt0ozZxPBta+++sr2h+OOO86+33fffW3kH2cbwTUcFNh4441tcgFBOt9nnEKIiklRWiTBycb+YqycrzuCyoybY4oGyRDG1LGGEb7DkQZXFEfoD8Fad8Ljtct69ukwjLLT6DchhFhOMstJt27drHe1UaNGVpb0ySefLHKf77//fnn/N0IslosuuijToEGDzEMPPVTh9n333Tdz/vnnV7jtvPPOy+y+++5ZEZjHH3/c+h/5N4lKxYUoX+h9pkwW/ZGFCxdmb7/wwgszBxxwQObf//53dn9hj0BsKs19IS5xR6SK1q0TTjghM3jw4Oy5evvtt1s7TNyXG0PZsDN37lx7vmuttVZm1qxZKTwDUSjooUZXgLVLy1PDhg0zjzzySKZ9+/YmTHv66afb/caNG5epWbNmpkOHDpkhQ4aY0B+2Guv7hx9+0BskhBAFZpkz284VV1xhZUaUlDNSpE6dOouMImEcihCFwLNKnTt3Dtttt51lsadMmWK3ffHFFzauxEssvVSU8nHKQ6dPn24/M9qLMlHmjybRWDohym/PoI2kQ4cO4eqrr7bRfRdddFG2zem7776zbDZjLRlVCZxpjE5i/N+CBQsqZAELCf8f/h/sb1SObbLJJpaV7Nixo40YmzdvnlWPHXbYYZZp59rJVjLOifMYPEvJ+CeeK49BJRD/itLD7Svec9YoVVj//Oc/bV72McccE3r37m2VF8zDfvzxx0PTpk1tLB2jKVkrZLtpP3j66adDzZo1q/rpCCFE2bMCHne+DwKVHYm0wIF2Y3LIkCFWKr7OOuuEvn37WsncQQcdZAbom2++mTWMMWCbNWsWGjdubDO2495NIUT1oFOnTqbfgIMyYMCAbMk2ATb0HZglzO3Dhg0zh+bhhx82zYe0mTx5svWIc6077bST3YZDRT8tztbll18eZs2aZaXuBLbp26UHl+tea6217Exu27ZtGDlyZOjZs6fNSRalh89q9wAM/77zzjuhe/fupjVAkNl7q9HIue666ywRwteGG25orREEmf773/9aYBpkrwkhROFZ7sx2EjnaIk3c0b744ovD8OHDw8cffxyeffbZcO+999rt9HFPmjTJDBI3UDA6+J4+TUeOthDVKzOI47n77rtblhenJd4HcGARmiKb3atXr3DjjTdmHe1k5VY++N/JINn+bPDvcaS5FhznOFCAEOmIESMsKLDpppuG119/3TRU2OvGjRtn98fxJoPNfcaMGSNHu0RhzREEYn2OGjXKdHKo2kL4k0DK2muvHW6//fbs/dEVoNIBJ/vUU0+122rXrh223HJLOdpCCFHqzrYQaUKknuz1c889Z+VxGBao81JOPnr0aMtee4ndgQceaEY02SCMF0T+hBDVCwLCOLIociMcRdk1UzXI+LmzveOOO5pzSvZ49uzZ1mriDnE+A8pSlBZLumaBMvDDDz/cWgnmzJljt3Ge0WZAsJlz0KEKArE/KiMmTpxY6WMKIYQosTJyIdJkxowZoUWLFjaqhywV0KuICvm6665r5eRrrLGGGdOUkn/yySehYcOG4YYbbrD7qnxciNIn+TlemvJY+raHDh1qWg70usbl5MvyeEujKH388ceH1Vdf3RwlQFGacnVaYXCiqNihlxzHKb4fUBbONXPtZCxjtK+VPsk1SB8+ffeDBw/Ojld10B9h7cyc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wIwZMybMmDHDjG7EgyilQ3UYg1oIUZ74PoHiMuJQZKGPOOIIG4sFVMEw/ujFF1+022bOnGn9rlTCoDDOlwuf0cPNlAKUnumTpYc6n0hRWiTxPmrWBpUVVFDUrFnTgkaI47EOGfGF/kAchEabZPDgwSas17t3b8tyr7766nqBhRBCpE9GFD3ff/99ZpdddsnUqVMnM2fOnOzt48aNyzRo0CBz11132c+ff/653adevXqZX375xW6bMmVK5owzzsissMIKmb/+9a/2WDG//vprys9GCFFIfvvtt+z38+fPz7Rp0ybTuHFj2y/mzp2b6dWrl+0Hr776qt3nq6++ypx99tl2W926dTPHHXdcZsMNN8xMnDixwuN269bNvuLHXxb++OOPCj///vvv2e8nT55se1qfPn1y/u3rr7+eWWWVVTKXX3555ssvv8yceeaZmXPPPXe5rkcUJ/E6GTRoUObII4+0tTlw4MDMd999Z7dfcskltmYHDx5c4f4jRozI7LrrrpmTTjop88MPPyzyeEIIIURaSCCtBCBTTdnmsGHDwgYbbGB92IzkufHGG+02fj979mwr76QvDcVV+tpi6N+mxPypp56ycV8qFRei/IjFnlyhm6weX5SL00pCRppJBDVq1AjTpk0zAalJkyZZhnvWrFl2H694KaSGA4rSKESzp5GhRMiR1hemJqCOvsUWW1hPLtl29jWylE2bNjVBrHPPPdeuEY0KBLNc2FFiV+UHrU+0SdESRYUW5xjvN9UYwFg32h/o02YN+Vgvys3XX399uw9ZcdoghBBCiLSRs10CTJgwwURdcKoZaYNhQdk4Y3uYOVuvXr1w0UUXhcMOO8zGmqy11lqmxkrfJcqs3uuWTxEjIURxzh/GkT7kkENM9BAn5Z133rG2EnpZKall3BEl5YzR4n60m1B+O3ToUJtSQGCP/cSDeoVAitJiSSAYxOi2m2++Oey99962zgkwH3fccbaOr7zySmuHYlwd5eXMiUfwD9E0+rRBARghhBBViZztEjGiGXXy2muvmcgL2eltttkm3HHHHRbNx/nGGEHsCOi5JLsNzMslu+WPo5FeQpQv06dPD0888UT48MMPrfLFM3sE344++mjLEtK3zdgussrcn6AdtyOW2KdPn3DttddaYG/HHXcs6LVSbfPkk09aUAChNR89Rjabva5+/foWSEToih5y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", | |
| "text/plain": [ | |
| "<Figure size 1000x600 with 1 Axes>" | |
| ] | |
| }, | |
| "metadata": {}, | |
| "output_type": "display_data" | |
| } | |
| ], | |
| "source": [ | |
| "er.plot_results()" | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": 17, | |
| "metadata": {}, | |
| "outputs": [ | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "optimized_gemini_3_flash_preview: 59.26\n", | |
| "vanilla_gemini_3_flash_preview: 59.26\n", | |
| "optimized_deepseek_chat: 57.41\n", | |
| "mipro_gemini_3_flash_preview: 57.41\n", | |
| "bootstrap_fewshot_random_search_gemini_3_flash_preview: 57.41\n", | |
| "gepa_gemini_2.5_flash: 53.7\n", | |
| "optimized_deepseek_reasoner: 53.7\n", | |
| "mipro_deepseek_reasoner: 48.15\n", | |
| "vanilla_deepseek_reasoner: 46.3\n", | |
| "optimized_gemini_2.5_flash: 44.44\n", | |
| "bootstrap_fewshot_random_search_deepseek_reasoner: 40.74\n", | |
| "bootstrap_fewshot_random_search_gemini_2.5_flash: 31.48\n", | |
| "vanilla_gemini_2.5_flash: 29.63\n", | |
| "mipro_v2_gemini_2.5_flash: 29.63\n", | |
| "bootstrap_fewshot_random_search_deepseek_chat: 29.63\n", | |
| "vanilla_deepseek_chat: 22.22\n", | |
| "mipro_deepseek_chat: 16.67\n", | |
| "gepa_deepseek_chat: 0\n", | |
| "gepa_deepseek_reasoner: 0\n", | |
| "gepa_gemini_3_flash_preview: 0\n" | |
| ] | |
| } | |
| ], | |
| "source": [ | |
| "er.print_results()" | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": 18, | |
| "metadata": {}, | |
| "outputs": [ | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "Average Metric: 32.00 / 54 (59.3%): 100%|██████████| 54/54 [00:00<00:00, 4761.95it/s]" | |
| ] | |
| }, | |
| { | |
| "name": "stderr", | |
| "output_type": "stream", | |
| "text": [ | |
| "2025/12/20 10:00:28 INFO dspy.evaluate.evaluate: Average Metric: 32.0 / 54 (59.3%)\n" | |
| ] | |
| }, | |
| { | |
| "name": "stdout", | |
| "output_type": "stream", | |
| "text": [ | |
| "\n", | |
| "Example({'food_description': 'frango churrasco', 'food_groups': ['meat and alternatives'], 'total_calories': 250.0, 'source': 'golden_dataset'}) (input_keys={'food_description'})\n", | |
| "Prediction(\n", | |
| " food_items=[FoodItem(name='Frango churrasco (grilled chicken, half)', quantity=1.0, calories=450.0, carbs=0.0, fat=28.0, protein=48.0, fiber=0.0, food_groups=['meat and alternatives'])]\n", | |
| ")\n", | |
| "Prediction(\n", | |
| " score=0,\n", | |
| " feedback='INCORRECT: Your answer was not within 10% of the correct answer (yours: 450.0, correct: 250.0)'\n", | |
| ")\n", | |
| "****************************************************************************************************\n", | |
| "Example({'food_description': 'pasta with pesto and tomatoes', 'food_groups': ['vegetable', 'grain', 'dairy'], 'total_calories': 339.0, 'source': 'golden_dataset'}) (input_keys={'food_description'})\n", | |
| "Prediction(\n", | |
| " food_items=[FoodItem(name='Pasta (cooked)', quantity=1.0, calories=220.0, carbs=43.0, fat=1.3, protein=8.1, fiber=2.5, food_groups=['grain']), FoodItem(name='Pesto sauce', quantity=2.0, calories=90.0, carbs=1.5, fat=9.0, protein=1.5, fiber=0.4, food_groups=['vegetable', 'dairy']), FoodItem(name='Cherry tomatoes', quantity=5.0, calories=3.0, carbs=0.7, fat=0.0, protein=0.1, fiber=0.2, food_groups=['vegetable'])]\n", | |
| ")\n", | |
| "Prediction(\n", | |
| " score=0,\n", | |
| " feedback='INCORRECT: Your answer was not within 10% of the correct answer (yours: 415.0, correct: 339.0)'\n", | |
| ")\n", | |
| "****************************************************************************************************\n", | |
| "Example({'food_description': \"For dinner, I'm having 25 grams of bread, 150 grams of chicken wings, and a 250-gram mixed vegetable salad.\", 'food_groups': ['grain', 'meat and alternatives', 'vegetable'], 'total_calories': 633.0, 'source': 'nutribench'}) (input_keys={'food_description'})\n", | |
| "Prediction(\n", | |
| " food_items=[FoodItem(name='Bread', quantity=25.0, calories=66.0, carbs=12.5, fat=0.8, protein=2.3, fiber=0.6, food_groups=['grain']), FoodItem(name='Chicken wings', quantity=150.0, calories=304.5, carbs=0.0, fat=22.5, protein=25.5, fiber=0.0, food_groups=['meat and alternatives']), FoodItem(name='Mixed vegetable salad', quantity=250.0, calories=50.0, carbs=10.0, fat=0.5, protein=2.5, fiber=4.0, food_groups=['vegetable'])]\n", | |
| ")\n", | |
| "Prediction(\n", | |
| " score=0,\n", | |
| " feedback='INCORRECT: Total calories is too high (yours: 59825.0, correct: 633.0)'\n", | |
| ")\n", | |
| "****************************************************************************************************\n", | |
| "Example({'food_description': 'two slices of half a croissant toasted with butter and Flamengo cheese (1 slice split)', 'food_groups': ['dairy', 'grain'], 'total_calories': 336.0, 'source': 'golden_dataset'}) (input_keys={'food_description'})\n", | |
| "Prediction(\n", | |
| " food_items=[FoodItem(name='Croissant (half, toasted)', quantity=1.0, calories=115.0, carbs=13.0, fat=6.0, protein=2.5, fiber=0.5, food_groups=['grain']), FoodItem(name='Butter', quantity=1.0, calories=50.0, carbs=0.0, fat=5.5, protein=0.0, fiber=0.0, food_groups=['dairy']), FoodItem(name='Flamengo cheese (1 slice)', quantity=1.0, calories=90.0, carbs=0.5, fat=7.0, protein=6.5, fiber=0.0, food_groups=['dairy'])]\n", | |
| ")\n", | |
| "Prediction(\n", | |
| " score=0,\n", | |
| " feedback='INCORRECT: Your answer was not within 10% of the correct answer (yours: 255.0, correct: 336.0)'\n", | |
| ")\n", | |
| "****************************************************************************************************\n", | |
| "Example({'food_description': '3 buns with peanut butter and jelly (small)', 'food_groups': ['fruit', 'grain', 'meat and alternatives'], 'total_calories': 615.0, 'source': 'golden_dataset'}) (input_keys={'food_description'})\n", | |
| "Prediction(\n", | |
| " food_items=[FoodItem(name='Small bun', quantity=3.0, calories=120.0, carbs=22.0, fat=2.0, protein=4.0, fiber=1.0, food_groups=['grain']), FoodItem(name='Peanut butter (1 tbsp)', quantity=3.0, calories=95.0, carbs=3.0, fat=8.0, protein=4.0, fiber=1.0, food_groups=['meat and alternatives']), FoodItem(name='Jelly (1 tbsp)', quantity=3.0, calories=50.0, carbs=13.0, fat=0.0, protein=0.0, fiber=0.0, food_groups=['fruit'])]\n", | |
| ")\n", | |
| "Prediction(\n", | |
| " score=0,\n", | |
| " feedback='INCORRECT: Your answer was not within 10% of the correct answer (yours: 795.0, correct: 615.0)'\n", | |
| ")\n", | |
| "****************************************************************************************************\n", | |
| "Example({'food_description': \"For a snack, I'm having a bottle of unsweetened bottled water and a juice box of 100% fruit juice.\", 'food_groups': ['fruit'], 'total_calories': 104.5, 'source': 'nutribench'}) (input_keys={'food_description'})\n", | |
| "Prediction(\n", | |
| " food_items=[FoodItem(name='Unsweetened bottled water', quantity=1.0, calories=0.0, carbs=0.0, fat=0.0, protein=0.0, fiber=0.0, food_groups=[]), FoodItem(name='100% fruit juice box (200ml)', quantity=1.0, calories=90.0, carbs=22.0, fat=0.1, protein=0.5, fiber=0.2, food_groups=['fruit'])]\n", | |
| ")\n", | |
| "Prediction(\n", | |
| " score=0,\n", | |
| " feedback='INCORRECT: Your answer was not within 10% of the correct answer (yours: 90.0, correct: 104.5)'\n", | |
| ")\n", | |
| "****************************************************************************************************\n", | |
| "Example({'food_description': 'I snacked on 35 grams of dry pastry with 3 grams of margarine, plus a drink of 100 grams of apricot nectar and 30 grams of wheat bread.', 'food_groups': ['grain', 'dairy', 'fruit'], 'total_calories': 289.0, 'source': 'nutribench'}) (input_keys={'food_description'})\n", | |
| "Prediction(\n", | |
| " food_items=[FoodItem(name='Dry pastry', quantity=35.0, calories=4.8, carbs=0.6, fat=0.25, protein=0.06, fiber=0.02, food_groups=['grain']), FoodItem(name='Margarine', quantity=3.0, calories=7.1, carbs=0.0, fat=0.8, protein=0.0, fiber=0.0, food_groups=[]), FoodItem(name='Apricot nectar', quantity=100.0, calories=0.56, carbs=0.14, fat=0.0, protein=0.0, fiber=0.01, food_groups=['fruit']), FoodItem(name='Wheat bread', quantity=30.0, calories=2.6, carbs=0.48, fat=0.04, protein=0.09, fiber=0.03, food_groups=['grain'])]\n", | |
| ")\n", | |
| "Prediction(\n", | |
| " score=0,\n", | |
| " feedback='INCORRECT: Your answer was not within 10% of the correct answer (yours: 323.3, correct: 289.0)'\n", | |
| ")\n", | |
| "****************************************************************************************************\n", | |
| "Example({'food_description': 'I enjoyed 200 grams of tea with sugar along with 230 grams of coconut milk rice for breakfast.', 'food_groups': ['grain', 'fruit'], 'total_calories': 558.0, 'source': 'nutribench'}) (input_keys={'food_description'})\n", | |
| "Prediction(\n", | |
| " food_items=[FoodItem(name='Tea with sugar (200g)', quantity=1.0, calories=32.0, carbs=8.0, fat=0.0, protein=0.0, fiber=0.0, food_groups=[]), FoodItem(name='Coconut milk rice (230g)', quantity=1.0, calories=450.0, carbs=65.0, fat=18.0, protein=7.0, fiber=1.5, food_groups=['grain', 'fruit'])]\n", | |
| ")\n", | |
| "Prediction(\n", | |
| " score=0,\n", | |
| " feedback='INCORRECT: Your answer was not within 10% of the correct answer (yours: 482.0, correct: 558.0)'\n", | |
| ")\n", | |
| "****************************************************************************************************\n", | |
| "Example({'food_description': '3 slices of pão bolo de ló de leite with goat cheese', 'food_groups': ['dairy', 'grain'], 'total_calories': 480.0, 'source': 'golden_dataset'}) (input_keys={'food_description'})\n", | |
| "Prediction(\n", | |
| " food_items=[FoodItem(name='Pão bolo de ló de leite (slice)', quantity=3.0, calories=150.0, carbs=22.0, fat=5.0, protein=4.0, fiber=0.5, food_groups=['grain', 'dairy']), FoodItem(name='Goat cheese (soft, 30g)', quantity=3.0, calories=80.0, carbs=0.3, fat=6.5, protein=5.5, fiber=0.0, food_groups=['dairy'])]\n", | |
| ")\n", | |
| "Prediction(\n", | |
| " score=0,\n", | |
| " feedback='INCORRECT: Your answer was not within 10% of the correct answer (yours: 690.0, correct: 480.0)'\n", | |
| ")\n", | |
| "****************************************************************************************************\n", | |
| "Example({'food_description': 'For breakfast, I enjoyed 11.2 grams of cornstarch Atole, along with 69 grams of fried eggs, 115.8 grams of cooked maize flour, and 113 grams of homemade tomato chirmol sauce.', 'food_groups': ['grain', 'meat and alternatives', 'vegetable'], 'total_calories': 687.0, 'source': 'nutribench'}) (input_keys={'food_description'})\n", | |
| "Prediction(\n", | |
| " food_items=[FoodItem(name='Cornstarch Atole (powder)', quantity=11.2, calories=3.8, carbs=0.9, fat=0.0, protein=0.0, fiber=0.0, food_groups=['grain']), FoodItem(name='Fried eggs', quantity=69.0, calories=1.96, carbs=0.01, fat=0.15, protein=0.13, fiber=0.0, food_groups=['meat and alternatives']), FoodItem(name='Cooked maize flour (masa)', quantity=115.8, calories=1.26, carbs=0.27, fat=0.01, protein=0.03, fiber=0.02, food_groups=['grain']), FoodItem(name='Homemade tomato chirmol sauce', quantity=113.0, calories=0.35, carbs=0.06, fat=0.01, protein=0.01, fiber=0.01, food_groups=['vegetable'])]\n", | |
| ")\n", | |
| "Prediction(\n", | |
| " score=0,\n", | |
| " feedback='INCORRECT: Your answer was not within 10% of the correct answer (yours: 363.258, correct: 687.0)'\n", | |
| ")\n", | |
| "****************************************************************************************************\n", | |
| "Example({'food_description': \"I'm snacking on a regular bag of cheese popcorn alongside a can of cola for lunch.\", 'food_groups': ['grain', 'dairy'], 'total_calories': 611.84, 'source': 'nutribench'}) (input_keys={'food_description'})\n", | |
| "Prediction(\n", | |
| " food_items=[FoodItem(name='Cheese popcorn (regular bag, approx. 50g)', quantity=1.0, calories=250.0, carbs=26.0, fat=15.0, protein=4.0, fiber=4.0, food_groups=['grain', 'dairy']), FoodItem(name='Cola (can, 355ml)', quantity=1.0, calories=140.0, carbs=39.0, fat=0.0, protein=0.0, fiber=0.0, food_groups=[])]\n", | |
| ")\n", | |
| "Prediction(\n", | |
| " score=0,\n", | |
| " feedback='INCORRECT: Your answer was not within 10% of the correct answer (yours: 390.0, correct: 611.84)'\n", | |
| ")\n", | |
| "****************************************************************************************************\n", | |
| "Example({'food_description': 'I’m treating myself to a medium latte for a little snack.', 'food_groups': ['dairy'], 'total_calories': 206.4, 'source': 'nutribench'}) (input_keys={'food_description'})\n", | |
| "Prediction(\n", | |
| " food_items=[FoodItem(name='Medium Latte', quantity=1.0, calories=150.0, carbs=15.0, fat=6.0, protein=10.0, fiber=0.0, food_groups=['dairy'])]\n", | |
| ")\n", | |
| "Prediction(\n", | |
| " score=0,\n", | |
| " feedback='INCORRECT: Your answer was not within 10% of the correct answer (yours: 150.0, correct: 206.4)'\n", | |
| ")\n", | |
| "****************************************************************************************************\n", | |
| "Example({'food_description': 'For dinner, I’m having 3 grams of nacho cheese flavored Doritos with about 41 grams of topping from a meat pizza.', 'food_groups': ['grain', 'dairy', 'meat and alternatives', 'vegetable'], 'total_calories': 147.18, 'source': 'nutribench'}) (input_keys={'food_description'})\n", | |
| "Prediction(\n", | |
| " food_items=[FoodItem(name='Nacho Cheese Doritos', quantity=3.0, calories=5.0, carbs=0.6, fat=0.25, protein=0.07, fiber=0.04, food_groups=['grain']), FoodItem(name='Meat Pizza Topping (Cheese and Meat)', quantity=41.0, calories=2.8, carbs=0.05, fat=0.22, protein=0.16, fiber=0.0, food_groups=['meat and alternatives', 'dairy'])]\n", | |
| ")\n", | |
| "Prediction(\n", | |
| " score=0,\n", | |
| " feedback='INCORRECT: Your answer was not within 10% of the correct answer (yours: 129.8, correct: 147.18)'\n", | |
| ")\n", | |
| "****************************************************************************************************\n", | |
| "Example({'food_description': \"I made myself a breakfast of brewed coffee weighing 240 grams and a chocolate chip muffin that's 70 grams.\", 'food_groups': ['grain', 'dairy'], 'total_calories': 281.0, 'source': 'nutribench'}) (input_keys={'food_description'})\n", | |
| "Prediction(\n", | |
| " food_items=[FoodItem(name='Brewed coffee', quantity=240.0, calories=1.0, carbs=0.0, fat=0.0, protein=0.1, fiber=0.0, food_groups=[]), FoodItem(name='Chocolate chip muffin', quantity=70.0, calories=260.0, carbs=34.0, fat=13.0, protein=3.5, fiber=1.2, food_groups=['grain'])]\n", | |
| ")\n", | |
| "Prediction(\n", | |
| " score=0,\n", | |
| " feedback='INCORRECT: Total calories is too high (yours: 18440.0, correct: 281.0)'\n", | |
| ")\n", | |
| "****************************************************************************************************\n", | |
| "Example({'food_description': 'I made myself a quick lunch with a cup of chicken noodle soup from a can and a medium slice of toasted multigrain bread.', 'food_groups': ['meat and alternatives', 'grain'], 'total_calories': 153.87, 'source': 'nutribench'}) (input_keys={'food_description'})\n", | |
| "Prediction(\n", | |
| " food_items=[FoodItem(name='Canned chicken noodle soup', quantity=1.0, calories=130.0, carbs=18.0, fat=3.0, protein=7.0, fiber=1.0, food_groups=['meat and alternatives', 'grain']), FoodItem(name='Multigrain bread (toasted)', quantity=1.0, calories=100.0, carbs=18.0, fat=1.5, protein=4.0, fiber=3.0, food_groups=['grain'])]\n", | |
| ")\n", | |
| "Prediction(\n", | |
| " score=0,\n", | |
| " feedback='INCORRECT: Your answer was not within 10% of the correct answer (yours: 230.0, correct: 153.87)'\n", | |
| ")\n", | |
| "****************************************************************************************************\n", | |
| "Example({'food_description': 'I’m snacking on a small bag of flavored potato sticks along with a 500 ml bottle of diet fruit-flavored drink.', 'food_groups': ['vegetable'], 'total_calories': 144.76, 'source': 'nutribench'}) (input_keys={'food_description'})\n", | |
| "Prediction(\n", | |
| " food_items=[FoodItem(name='Flavored potato sticks (small bag, 30g)', quantity=1.0, calories=160.0, carbs=17.0, fat=10.0, protein=2.0, fiber=1.0, food_groups=['vegetable']), FoodItem(name='Diet fruit-flavored drink (500 ml)', quantity=1.0, calories=10.0, carbs=2.0, fat=0.0, protein=0.0, fiber=0.0, food_groups=[])]\n", | |
| ")\n", | |
| "Prediction(\n", | |
| " score=0,\n", | |
| " feedback='INCORRECT: Your answer was not within 10% of the correct answer (yours: 170.0, correct: 144.76)'\n", | |
| ")\n", | |
| "****************************************************************************************************\n", | |
| "Example({'food_description': 'stracciatella with confit grapes and three little focaccia pieces', 'food_groups': ['fruit', 'dairy', 'grain'], 'total_calories': 530.0, 'source': 'golden_dataset'}) (input_keys={'food_description'})\n", | |
| "Prediction(\n", | |
| " food_items=[FoodItem(name='Stracciatella cheese (100g)', quantity=1.0, calories=250.0, carbs=3.5, fat=22.0, protein=10.0, fiber=0.0, food_groups=['dairy']), FoodItem(name='Confit grapes (serving)', quantity=1.0, calories=80.0, carbs=18.0, fat=1.5, protein=0.5, fiber=1.0, food_groups=['fruit']), FoodItem(name='Focaccia (small piece)', quantity=3.0, calories=90.0, carbs=13.0, fat=3.5, protein=2.0, fiber=0.5, food_groups=['grain'])]\n", | |
| ")\n", | |
| "Prediction(\n", | |
| " score=0,\n", | |
| " feedback='INCORRECT: Your answer was not within 10% of the correct answer (yours: 600.0, correct: 530.0)'\n", | |
| ")\n", | |
| "****************************************************************************************************\n", | |
| "Example({'food_description': \"For my lunch, I'm having a 480g energy drink that's sugar-free, from Monster.\", 'food_groups': [], 'total_calories': 24.0, 'source': 'nutribench'}) (input_keys={'food_description'})\n", | |
| "Prediction(\n", | |
| " food_items=[FoodItem(name='Monster Energy Drink (Sugar-Free)', quantity=4.8, calories=2.0, carbs=1.0, fat=0.0, protein=0.0, fiber=0.0, food_groups=[])]\n", | |
| ")\n", | |
| "Prediction(\n", | |
| " score=0,\n", | |
| " feedback='INCORRECT: Your answer was not within 10% of the correct answer (yours: 9.6, correct: 24.0)'\n", | |
| ")\n", | |
| "****************************************************************************************************\n", | |
| "Example({'food_description': 'I’m starting my day with a cup of ready-to-eat cereal for breakfast.', 'food_groups': ['grain'], 'total_calories': 149.2, 'source': 'nutribench'}) (input_keys={'food_description'})\n", | |
| "Prediction(\n", | |
| " food_items=[FoodItem(name='Ready-to-eat cereal', quantity=1.0, calories=110.0, carbs=24.0, fat=1.0, protein=2.0, fiber=1.5, food_groups=['grain'])]\n", | |
| ")\n", | |
| "Prediction(\n", | |
| " score=0,\n", | |
| " feedback='INCORRECT: Your answer was not within 10% of the correct answer (yours: 110.0, correct: 149.2)'\n", | |
| ")\n", | |
| "****************************************************************************************************\n", | |
| "Example({'food_description': 'I enjoyed a snack that included 216g of sorrel punch, 240g of peppermint tea, and a glass of 511g of tap water.', 'food_groups': ['fruit'], 'total_calories': 60.0, 'source': 'nutribench'}) (input_keys={'food_description'})\n", | |
| "Prediction(\n", | |
| " food_items=[FoodItem(name='Sorrel punch', quantity=2.16, calories=45.0, carbs=11.5, fat=0.0, protein=0.1, fiber=0.0, food_groups=['fruit']), FoodItem(name='Peppermint tea', quantity=2.4, calories=1.0, carbs=0.2, fat=0.0, protein=0.0, fiber=0.0, food_groups=[]), FoodItem(name='Tap water', quantity=5.11, calories=0.0, carbs=0.0, fat=0.0, protein=0.0, fiber=0.0, food_groups=[])]\n", | |
| ")\n", | |
| "Prediction(\n", | |
| " score=0,\n", | |
| " feedback='INCORRECT: Your answer was not within 10% of the correct answer (yours: 99.60000000000001, correct: 60.0)'\n", | |
| ")\n", | |
| "****************************************************************************************************\n", | |
| "Example({'food_description': 'coffee‑flavoured corn flakes with plant‑based milk (one bowl)', 'food_groups': ['grain'], 'total_calories': 240.0, 'source': 'golden_dataset'}) (input_keys={'food_description'})\n", | |
| "Prediction(\n", | |
| " food_items=[FoodItem(name='Corn flakes (coffee-flavored)', quantity=1.0, calories=110.0, carbs=26.0, fat=0.5, protein=2.0, fiber=1.0, food_groups=['grain']), FoodItem(name='Plant-based milk (unsweetened almond/soy)', quantity=1.0, calories=60.0, carbs=4.0, fat=3.0, protein=4.0, fiber=0.5, food_groups=['meat and alternatives'])]\n", | |
| ")\n", | |
| "Prediction(\n", | |
| " score=0,\n", | |
| " feedback='INCORRECT: Your answer was not within 10% of the correct answer (yours: 170.0, correct: 240.0)'\n", | |
| ")\n", | |
| "****************************************************************************************************\n", | |
| "Example({'food_description': 'ice‑cream cone', 'food_groups': ['dairy', 'grain'], 'total_calories': 157.0, 'source': 'golden_dataset'}) (input_keys={'food_description'})\n", | |
| "Prediction(\n", | |
| " food_items=[FoodItem(name='Ice cream (scoop)', quantity=1.0, calories=137.0, carbs=15.0, fat=7.0, protein=2.0, fiber=0.0, food_groups=['dairy']), FoodItem(name='Sugar cone', quantity=1.0, calories=50.0, carbs=11.0, fat=0.5, protein=1.0, fiber=0.0, food_groups=['grain'])]\n", | |
| ")\n", | |
| "Prediction(\n", | |
| " score=0,\n", | |
| " feedback='INCORRECT: Your answer was not within 10% of the correct answer (yours: 187.0, correct: 157.0)'\n", | |
| ")\n", | |
| "****************************************************************************************************\n" | |
| ] | |
| } | |
| ], | |
| "source": [ | |
| "with dspy.context(lm=gemini_3_flash):\n", | |
| " result = evaluate(module_current)\n", | |
| "\n", | |
| "\n", | |
| "for example, prediction, feedback in result.results:\n", | |
| " if feedback.score == 1:\n", | |
| " continue\n", | |
| "\n", | |
| " print(example)\n", | |
| " print(prediction)\n", | |
| " print(feedback)\n", | |
| " print(\"*\" * 100)" | |
| ] | |
| }, | |
| { | |
| "cell_type": "markdown", | |
| "metadata": {}, | |
| "source": [ | |
| "## Sanity Check " | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": 19, | |
| "metadata": {}, | |
| "outputs": [ | |
| { | |
| "name": "stderr", | |
| "output_type": "stream", | |
| "text": [ | |
| "100%|██████████| 418/418 [00:01<00:00, 334.33it/s]\n" | |
| ] | |
| }, | |
| { | |
| "data": { | |
| "text/html": [ | |
| "<div><style>\n", | |
| ".dataframe > thead > tr,\n", | |
| ".dataframe > tbody > tr {\n", | |
| " text-align: right;\n", | |
| " white-space: pre-wrap;\n", | |
| "}\n", | |
| "</style>\n", | |
| "<small>shape: (418, 4)</small><table border=\"1\" class=\"dataframe\"><thead><tr><th>started_at</th><th>food</th><th>model_prediction</th><th>total_calories_predicted</th></tr><tr><td>str</td><td>str</td><td>struct[2]</td><td>f64</td></tr></thead><tbody><tr><td>"2025-12-13T08:29:30.781696Z"</td><td>"Cappuccino groß"</td><td>{"- corn snack those white\n", | |
| "- avocado pesto toast joe and the joe and the juice \n", | |
| "- 1.5 plate of tomato rice with cecci and beans",[{"Corn snack (white)",1.0,150.0,25.0,5.0,2.0,1.0,["grain"]}, {"Avocado pesto toast (Joe & The Juice)",1.0,350.0,35.0,20.0,8.0,6.0,["grain", "fruit", "vegetable"]}, {"Tomato rice with chickpeas and beans (plate)",1.5,400.0,70.0,8.0,15.0,10.0,["grain", "vegetable", "meat and alternatives"]}]}</td><td>1100.0</td></tr><tr><td>"2025-12-12T19:44:31.387084Z"</td><td>"Kleines Bier"</td><td>{"Kleiner Milka-Nikolaus weiße Schokolade",[{"Milka Nikolaus (small, white chocolate)",1.0,150.0,15.0,9.0,2.0,0.0,["dairy"]}]}</td><td>150.0</td></tr><tr><td>"2025-12-12T19:44:17.451188Z"</td><td>"Kleiner Milka-Nikolaus weiße S…</td><td>{"one bun with cheese and butter \n", | |
| "half a pain au chocolat\n", | |
| "take out shai butter chicken with spicy cheese naan",[{"Bun",1.0,150.0,28.0,2.0,5.0,1.5,["grain"]}, {"Cheese slice",1.0,75.0,0.5,6.5,4.5,0.0,["dairy"]}, … {"Spicy cheese naan",1.0,350.0,45.0,15.0,12.0,4.0,["grain", "dairy"]}]}</td><td>1251.0</td></tr><tr><td>"2025-12-12T19:43:56.533952Z"</td><td>"Linsen-Spinat-Daal mit Kokosmi…</td><td>{"Kleines Bier",[{"Small beer (330ml)",1.0,130.0,10.0,0.0,1.0,0.0,[]}]}</td><td>130.0</td></tr><tr><td>"2025-12-12T18:11:36.026841Z"</td><td>"one bun with cheese and butter…</td><td>{"Hühnchen mit Tomate, Mandeln und Rosinen",[{"Chicken breast (cooked, 100g)",1.0,165.0,0.0,3.6,31.0,0.0,["meat and alternatives"]}, {"Tomato (medium)",1.0,22.0,4.8,0.2,1.1,1.5,["vegetable"]}, … {"Raisins (10g)",1.0,30.0,8.0,0.0,0.3,0.4,["fruit"]}]}</td><td>275.0</td></tr><tr><td>…</td><td>…</td><td>…</td><td>…</td></tr><tr><td>"2025-06-04T09:41:37.170147Z"</td><td>"two croissants with cream and …</td><td>{"- 4 toasts with cheese\n", | |
| "- 1/2 cardamom bun\n", | |
| "- 1/2 pain au chocolat\n", | |
| "- 2 plates of pasta al forno (pasta + tomato sauce)\n", | |
| "- some light snacking",[{"Toast (white bread)",4.0,77.0,15.0,1.0,2.5,1.0,["grain"]}, {"Cheese slice",4.0,75.0,0.5,6.5,4.5,0.0,["dairy"]}, … {"Light snack (generic)",1.0,100.0,15.0,4.0,2.0,1.0,[]}]}</td><td>1978.0</td></tr><tr><td>"2025-06-04T09:40:41.788553Z"</td><td>"fried eggs 2 with ham and whit…</td><td>{"fried eggs 2 with ham and white bread bun 4 slices of tomato and four slices of cucumber",[{"Fried egg",2.0,90.0,0.6,7.0,6.0,0.0,["meat and alternatives"]}, {"Ham slice",1.0,30.0,0.5,1.5,3.5,0.0,["meat and alternatives"]}, … {"Cucumber slice",4.0,1.0,0.3,0.01,0.05,0.1,["vegetable"]}]}</td><td>376.0</td></tr><tr><td>"2025-06-03T21:16:04.784519Z"</td><td>"- 1/5 psta pesto airplane menu…</td><td>{"- 1.3 portuon of rice, falafel and beans\n", | |
| "- 1 small ice cream from the supermarket ",[{"Rice (cooked)",1.3,169.0,37.0,0.3,3.3,0.4,["grain"]}, {"Falafel (portion)",1.3,190.0,20.0,10.0,8.0,5.0,["meat and alternatives"]}, … {"Small ice cream (supermarket)",1.0,150.0,20.0,7.0,2.0,0.5,["dairy"]}]}</td><td>759.7</td></tr><tr><td>"2025-06-02T20:06:15.236051Z"</td><td>"- leftover pasta from yesterda…</td><td>{"- 2 toasts with peanut butter and jelly\n", | |
| "- 1 half omelette eith cheese (4 eggs) + 1.5 toasted pices of bread + vegan butter \n", | |
| "- 400ml recovery drink (protein focus)",[{"Whole wheat toast",2.0,80.0,14.0,1.0,3.0,2.0,["grain"]}, {"Peanut butter (1 tbsp)",2.0,95.0,3.5,8.0,4.0,1.0,["meat and alternatives"]}, … {"Recovery drink (protein focus)",400.0,200.0,20.0,2.0,30.0,0.0,[]}]}</td><td>80920.0</td></tr><tr><td>"2025-06-01T20:16:08.341112Z"</td><td>"- 4 toasts with cheese\n", | |
| "- 1/2 c…</td><td>{"butter",[{"Butter (1 tbsp)",1.0,102.0,0.1,11.5,0.1,0.0,["dairy"]}]}</td><td>102.0</td></tr></tbody></table></div>" | |
| ], | |
| "text/plain": [ | |
| "shape: (418, 4)\n", | |
| "┌────────────────────────┬────────────────────────┬────────────────────────┬───────────────────────┐\n", | |
| "│ started_at ┆ food ┆ model_prediction ┆ total_calories_predic │\n", | |
| "│ --- ┆ --- ┆ --- ┆ ted │\n", | |
| "│ str ┆ str ┆ struct[2] ┆ --- │\n", | |
| "│ ┆ ┆ ┆ f64 │\n", | |
| "╞════════════════════════╪════════════════════════╪════════════════════════╪═══════════════════════╡\n", | |
| "│ 2025-12-13T08:29:30.78 ┆ Cappuccino groß ┆ {\"- corn snack those ┆ 1100.0 │\n", | |
| "│ 1696Z ┆ ┆ white ┆ │\n", | |
| "│ ┆ ┆ - a… ┆ │\n", | |
| "│ 2025-12-12T19:44:31.38 ┆ Kleines Bier ┆ {\"Kleiner ┆ 150.0 │\n", | |
| "│ 7084Z ┆ ┆ Milka-Nikolaus weiße… ┆ │\n", | |
| "│ 2025-12-12T19:44:17.45 ┆ Kleiner Milka-Nikolaus ┆ {\"one bun with cheese ┆ 1251.0 │\n", | |
| "│ 1188Z ┆ weiße S… ┆ and butt… ┆ │\n", | |
| "│ 2025-12-12T19:43:56.53 ┆ Linsen-Spinat-Daal mit ┆ {\"Kleines ┆ 130.0 │\n", | |
| "│ 3952Z ┆ Kokosmi… ┆ Bier\",[{\"Small beer … ┆ │\n", | |
| "│ 2025-12-12T18:11:36.02 ┆ one bun with cheese ┆ {\"Hühnchen mit Tomate, ┆ 275.0 │\n", | |
| "│ 6841Z ┆ and butter… ┆ Mandeln… ┆ │\n", | |
| "│ … ┆ … ┆ … ┆ … │\n", | |
| "│ 2025-06-04T09:41:37.17 ┆ two croissants with ┆ {\"- 4 toasts with ┆ 1978.0 │\n", | |
| "│ 0147Z ┆ cream and … ┆ cheese ┆ │\n", | |
| "│ ┆ ┆ - 1/2… ┆ │\n", | |
| "│ 2025-06-04T09:40:41.78 ┆ fried eggs 2 with ham ┆ {\"fried eggs 2 with ┆ 376.0 │\n", | |
| "│ 8553Z ┆ and whit… ┆ ham and wh… ┆ │\n", | |
| "│ 2025-06-03T21:16:04.78 ┆ - 1/5 psta pesto ┆ {\"- 1.3 portuon of ┆ 759.7 │\n", | |
| "│ 4519Z ┆ airplane menu… ┆ rice, falaf… ┆ │\n", | |
| "│ 2025-06-02T20:06:15.23 ┆ - leftover pasta from ┆ {\"- 2 toasts with ┆ 80920.0 │\n", | |
| "│ 6051Z ┆ yesterda… ┆ peanut butte… ┆ │\n", | |
| "│ 2025-06-01T20:16:08.34 ┆ - 4 toasts with cheese ┆ {\"butter\",[{\"Butter (1 ┆ 102.0 │\n", | |
| "│ 1112Z ┆ - 1/2 c… ┆ tbsp)\",… ┆ │\n", | |
| "└────────────────────────┴────────────────────────┴────────────────────────┴───────────────────────┘" | |
| ] | |
| }, | |
| "execution_count": 19, | |
| "metadata": {}, | |
| "output_type": "execute_result" | |
| } | |
| ], | |
| "source": [ | |
| "import json\n", | |
| "\n", | |
| "with open(\n", | |
| " \"../datasets/wandb/weave_export_taralli_api_2025-12-13.jsonl\", \"r\"\n", | |
| ") as json_file:\n", | |
| " json_list = list(json_file)\n", | |
| "records = []\n", | |
| "for json_str in json_list:\n", | |
| " if \"No endpoints found \" in json_str:\n", | |
| " continue\n", | |
| " result = json.loads(json_str)\n", | |
| " records.append(result)\n", | |
| "\n", | |
| "prod_data = polars.from_dicts(records)\n", | |
| "# all_inputs = [_.get(\"food\") for _ in prod_data[\"inputs\"]]\n", | |
| "prod_data = prod_data.with_columns(\n", | |
| " polars.col(\"inputs\").struct.field(\"food\").alias(\"food\")\n", | |
| ")\n", | |
| "\n", | |
| "prod_data = prod_data.filter(polars.col(\"food\").is_not_null())\n", | |
| "\n", | |
| "prod_data[[\"started_at\", \"food\"]]\n", | |
| "\n", | |
| "from concurrent.futures import ThreadPoolExecutor, as_completed\n", | |
| "from tqdm import tqdm\n", | |
| "\n", | |
| "\n", | |
| "total_cals = []\n", | |
| "model_prediction = []\n", | |
| "with dspy.context(lm=gemini_3_flash):\n", | |
| "\n", | |
| " def predict_total_calories(food):\n", | |
| " result = module_current(food_description=food)\n", | |
| " try:\n", | |
| " na = NutritionAnalysis(**result.toDict(), food_description=food)\n", | |
| " return na.total_calories(), na.model_dump()\n", | |
| " except Exception as e:\n", | |
| " print(f\"Error predicting {food}: {e}\")\n", | |
| " return -1, {}\n", | |
| "\n", | |
| " foods = prod_data[\"food\"]\n", | |
| " with ThreadPoolExecutor(max_workers=10) as executor:\n", | |
| " futures = [executor.submit(predict_total_calories, food) for food in foods]\n", | |
| " for future in tqdm(as_completed(futures), total=len(futures)):\n", | |
| " total_cals.append(future.result()[0])\n", | |
| " model_prediction.append(future.result()[1])\n", | |
| "\n", | |
| "\n", | |
| "prod_data = prod_data.with_columns(\n", | |
| " polars.Series(name=\"total_calories_predicted\", values=total_cals),\n", | |
| " polars.Series(name=\"model_prediction\", values=model_prediction),\n", | |
| ")\n", | |
| "\n", | |
| "cols = [\"started_at\", \"food\", \"model_prediction\", \"total_calories_predicted\"]\n", | |
| "prod_data[cols]" | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": 20, | |
| "metadata": {}, | |
| "outputs": [ | |
| { | |
| "data": { | |
| "text/html": [ | |
| "<div><style>\n", | |
| ".dataframe > thead > tr,\n", | |
| ".dataframe > tbody > tr {\n", | |
| " text-align: right;\n", | |
| " white-space: pre-wrap;\n", | |
| "}\n", | |
| "</style>\n", | |
| "<small>shape: (21, 4)</small><table border=\"1\" class=\"dataframe\"><thead><tr><th>started_at</th><th>food</th><th>model_prediction</th><th>total_calories_predicted</th></tr><tr><td>str</td><td>str</td><td>struct[2]</td><td>f64</td></tr></thead><tbody><tr><td>"2025-11-30T10:14:00.632469Z"</td><td>"zartbitterschokolade 30 g"</td><td>{"zartbitterschokoladeblättchen (50g)",[{"Dark chocolate leaves",50.0,270.0,25.0,18.0,3.0,5.0,[]}]}</td><td>13500.0</td></tr><tr><td>"2025-11-29T06:52:50.389569Z"</td><td>"Vollkornsemmmel mit Butter und…</td><td>{"\n", | |
| "mehlige Kartoffeln 165 g\n", | |
| "150 ml Milch\n", | |
| "125 g Lachsfilet\n", | |
| "20 g Butter\n", | |
| "12 g Mehl\n", | |
| "1 EL mittelscharfer Senf\n", | |
| "Dill\n", | |
| "Salz\n", | |
| "Pfeffer",[{"Potatoes (floury)",165.0,127.0,29.0,0.1,2.5,2.0,["vegetable"]}, {"Milk",150.0,90.0,7.2,4.5,4.8,0.0,["dairy"]}, … {"Pepper",1.0,0.0,0.0,0.0,0.0,0.0,[]}]}</td><td>70076.0</td></tr><tr><td>"2025-11-28T17:42:18.138704Z"</td><td>"\n", | |
| "mehlige Kartoffeln 0.165 kg\n", | |
| "1…</td><td>{"For breakfast, I ate a plain bun weighing 126 grams and sprinkled on 27 grams of raw sugar.",[{"Plain bun",126.0,370.0,70.0,5.0,12.0,3.0,["grain"]}, {"Raw sugar",27.0,108.0,27.0,0.0,0.0,0.0,[]}]}</td><td>49536.0</td></tr><tr><td>"2025-11-28T17:41:28.547643Z"</td><td>"\n", | |
| "165 g mehlige Kartoffeln\n", | |
| "150 …</td><td>{"150 mL Pinot noir",[{"Pinot Noir",150.0,120.0,3.6,0.0,0.1,0.0,[]}]}</td><td>18000.0</td></tr><tr><td>"2025-11-28T12:27:47.265307Z"</td><td>"3 Löffel Kartoffelsalat mit Es…</td><td>{"Hühnchen mit Gemüse und Chop-Suey Soße",[{"Chicken breast (cooked)",150.0,240.0,0.0,6.0,45.0,0.0,["meat and alternatives"]}, {"Mixed vegetables (stir-fry)",150.0,60.0,12.0,0.5,3.0,4.0,["vegetable"]}, {"Chop Suey Sauce",60.0,40.0,8.0,0.5,1.0,0.5,[]}]}</td><td>47400.0</td></tr><tr><td>…</td><td>…</td><td>…</td><td>…</td></tr><tr><td>"2025-11-17T18:33:07.357719Z"</td><td>"small bit of baguette bread"</td><td>{"2 angebratene Kartoffelknödel\n", | |
| "100 g Creme fraiche\n", | |
| "1/4 Gurke\n", | |
| "1/2 Karotte",[{"Pan-fried potato dumpling",2.0,250.0,35.0,10.0,4.0,3.0,["grain", "vegetable"]}, {"Creme fraiche",100.0,290.0,3.0,30.0,2.0,0.0,["dairy"]}, … {"Carrot",0.5,25.0,5.8,0.1,0.6,1.7,["vegetable"]}]}</td><td>29516.25</td></tr><tr><td>"2025-11-16T10:45:38.218648Z"</td><td>"running gel"</td><td>{"oats 26g with almond milk 180ml and 30g of protein powder",[{"Oats",26.0,98.8,17.7,2.0,4.4,2.6,["grain"]}, {"Almond milk (unsweetened)",180.0,27.0,1.0,2.2,0.7,0.4,["dairy"]}, {"Protein powder",30.0,120.0,3.0,2.0,24.0,1.0,["meat and alternatives"]}]}</td><td>11028.8</td></tr><tr><td>"2025-11-15T20:36:30.045973Z"</td><td>"300g pasta ai fagioli\n", | |
| "velutate…</td><td>{"50 g Schokolade Ritter Sport Cunchy Cappuccino ",[{"Ritter Sport Crunchy Cappuccino chocolate",50.0,270.0,27.0,17.0,3.0,1.5,[]}]}</td><td>13500.0</td></tr><tr><td>"2025-09-11T18:07:31.869962Z"</td><td>"Una ensalada de pollo, huevo d…</td><td>{"2 toasts with peanut butter and cheese \n", | |
| "for dinner: fish (one small ~200g with roasted potatoes and pomodoroni)\n", | |
| "3 camembert slices with small toasts snacks",[{"Toast (whole wheat)",2.0,80.0,14.0,1.0,3.0,2.0,["grain"]}, {"Peanut butter (1 tbsp)",2.0,95.0,3.5,8.0,4.0,1.0,["meat and alternatives"]}, … {"Small toast (for cheese)",3.0,40.0,7.0,0.5,1.5,0.5,["grain"]}]}</td><td>36786.0</td></tr><tr><td>"2025-06-02T20:06:15.236051Z"</td><td>"- leftover pasta from yesterda…</td><td>{"- 2 toasts with peanut butter and jelly\n", | |
| "- 1 half omelette eith cheese (4 eggs) + 1.5 toasted pices of bread + vegan butter \n", | |
| "- 400ml recovery drink (protein focus)",[{"Whole wheat toast",2.0,80.0,14.0,1.0,3.0,2.0,["grain"]}, {"Peanut butter (1 tbsp)",2.0,95.0,3.5,8.0,4.0,1.0,["meat and alternatives"]}, … {"Recovery drink (protein focus)",400.0,200.0,20.0,2.0,30.0,0.0,[]}]}</td><td>80920.0</td></tr></tbody></table></div>" | |
| ], | |
| "text/plain": [ | |
| "shape: (21, 4)\n", | |
| "┌────────────────────────┬────────────────────────┬────────────────────────┬───────────────────────┐\n", | |
| "│ started_at ┆ food ┆ model_prediction ┆ total_calories_predic │\n", | |
| "│ --- ┆ --- ┆ --- ┆ ted │\n", | |
| "│ str ┆ str ┆ struct[2] ┆ --- │\n", | |
| "│ ┆ ┆ ┆ f64 │\n", | |
| "╞════════════════════════╪════════════════════════╪════════════════════════╪═══════════════════════╡\n", | |
| "│ 2025-11-30T10:14:00.63 ┆ zartbitterschokolade ┆ {\"zartbitterschokolade ┆ 13500.0 │\n", | |
| "│ 2469Z ┆ 30 g ┆ blättche… ┆ │\n", | |
| "│ 2025-11-29T06:52:50.38 ┆ Vollkornsemmmel mit ┆ {\" ┆ 70076.0 │\n", | |
| "│ 9569Z ┆ Butter und… ┆ mehlige Kartoffeln 165 ┆ │\n", | |
| "│ ┆ ┆ g ┆ │\n", | |
| "│ ┆ ┆ 15… ┆ │\n", | |
| "│ 2025-11-28T17:42:18.13 ┆ ┆ {\"For breakfast, I ate ┆ 49536.0 │\n", | |
| "│ 8704Z ┆ mehlige Kartoffeln ┆ a plain… ┆ │\n", | |
| "│ ┆ 0.165 kg ┆ ┆ │\n", | |
| "│ ┆ 1… ┆ ┆ │\n", | |
| "│ 2025-11-28T17:41:28.54 ┆ ┆ {\"150 mL Pinot ┆ 18000.0 │\n", | |
| "│ 7643Z ┆ 165 g mehlige ┆ noir\",[{\"Pinot … ┆ │\n", | |
| "│ ┆ Kartoffeln ┆ ┆ │\n", | |
| "│ ┆ 150 … ┆ ┆ │\n", | |
| "│ 2025-11-28T12:27:47.26 ┆ 3 Löffel ┆ {\"Hühnchen mit Gemüse ┆ 47400.0 │\n", | |
| "│ 5307Z ┆ Kartoffelsalat mit Es… ┆ und Chop… ┆ │\n", | |
| "│ … ┆ … ┆ … ┆ … │\n", | |
| "│ 2025-11-17T18:33:07.35 ┆ small bit of baguette ┆ {\"2 angebratene ┆ 29516.25 │\n", | |
| "│ 7719Z ┆ bread ┆ Kartoffelknöde… ┆ │\n", | |
| "│ 2025-11-16T10:45:38.21 ┆ running gel ┆ {\"oats 26g with almond ┆ 11028.8 │\n", | |
| "│ 8648Z ┆ ┆ milk 18… ┆ │\n", | |
| "│ 2025-11-15T20:36:30.04 ┆ 300g pasta ai fagioli ┆ {\"50 g Schokolade ┆ 13500.0 │\n", | |
| "│ 5973Z ┆ velutate… ┆ Ritter Sport… ┆ │\n", | |
| "│ 2025-09-11T18:07:31.86 ┆ Una ensalada de pollo, ┆ {\"2 toasts with peanut ┆ 36786.0 │\n", | |
| "│ 9962Z ┆ huevo d… ┆ butter … ┆ │\n", | |
| "│ 2025-06-02T20:06:15.23 ┆ - leftover pasta from ┆ {\"- 2 toasts with ┆ 80920.0 │\n", | |
| "│ 6051Z ┆ yesterda… ┆ peanut butte… ┆ │\n", | |
| "└────────────────────────┴────────────────────────┴────────────────────────┴───────────────────────┘" | |
| ] | |
| }, | |
| "execution_count": 20, | |
| "metadata": {}, | |
| "output_type": "execute_result" | |
| } | |
| ], | |
| "source": [ | |
| "prod_data.filter(polars.col(\"total_calories_predicted\") > 10_000)[cols]" | |
| ] | |
| }, | |
| { | |
| "cell_type": "code", | |
| "execution_count": null, | |
| "metadata": {}, | |
| "outputs": [], | |
| "source": [] | |
| } | |
| ], | |
| "metadata": { | |
| "kernelspec": { | |
| "display_name": ".env", | |
| "language": "python", | |
| "name": "python3" | |
| }, | |
| "language_info": { | |
| "codemirror_mode": { | |
| "name": "ipython", | |
| "version": 3 | |
| }, | |
| "file_extension": ".py", | |
| "mimetype": "text/x-python", | |
| "name": "python", | |
| "nbconvert_exporter": "python", | |
| "pygments_lexer": "ipython3", | |
| "version": "3.12.6" | |
| } | |
| }, | |
| "nbformat": 4, | |
| "nbformat_minor": 2 | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment