Lección 55 · 5 min · Gratis

Extracción de información de recibos con la API por lotes de Mistral

En este notebook, te guiaremos a través de un ejemplo de cómo extraer información de múltiples recibos usando la API por lotes de Mistral y devolver los datos en un DataFrame de pandas.

!pip install mistralai datasets
Collecting mistralai
  Downloading mistralai-1.2.3-py3-none-any.whl.metadata (26 kB)
Collecting datasets
  Downloading datasets-3.1.0-py3-none-any.whl.metadata (20 kB)
Requirement already satisfied: eval-type-backport<0.3.0,>=0.2.0 in /usr/local/lib/python3.10/dist-packages (from mistralai) (0.2.0)
Requirement already satisfied: httpx<0.28.0,>=0.27.0 in /usr/local/lib/python3.10/dist-packages (from mistralai) (0.27.2)
Collecting jsonpath-python<2.0.0,>=1.0.6 (from mistralai)
  Downloading jsonpath_python-1.0.6-py3-none-any.whl.metadata (12 kB)
Requirement already satisfied: pydantic<3.0.0,>=2.9.0 in /usr/local/lib/python3.10/dist-packages (from mistralai) (2.9.2)
Requirement already satisfied: python-dateutil==2.8.2 in /usr/local/lib/python3.10/dist-packages (from mistralai) (2.8.2)
Collecting typing-inspect<0.10.0,>=0.9.0 (from mistralai)
  Downloading typing_inspect-0.9.0-py3-none-any.whl.metadata (1.5 kB)
Requirement already satisfied: six>=1.5 in /usr/local/lib/python3.10/dist-packages (from python-dateutil==2.8.2->mistralai) (1.16.0)
Requirement already satisfied: filelock in /usr/local/lib/python3.10/dist-packages (from datasets) (3.16.1)
Requirement already satisfied: numpy>=1.17 in /usr/local/lib/python3.10/dist-packages (from datasets) (1.26.4)
Requirement already satisfied: pyarrow>=15.0.0 in /usr/local/lib/python3.10/dist-packages (from datasets) (17.0.0)
Collecting dill<0.3.9,>=0.3.0 (from datasets)
  Downloading dill-0.3.8-py3-none-any.whl.metadata (10 kB)
Requirement already satisfied: pandas in /usr/local/lib/python3.10/dist-packages (from datasets) (2.2.2)
Requirement already satisfied: requests>=2.32.2 in /usr/local/lib/python3.10/dist-packages (from datasets) (2.32.3)
Requirement already satisfied: tqdm>=4.66.3 in /usr/local/lib/python3.10/dist-packages (from datasets) (4.66.6)
Collecting xxhash (from datasets)
  Downloading xxhash-3.5.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (12 kB)
Collecting multiprocess<0.70.17 (from datasets)
  Downloading multiprocess-0.70.16-py310-none-any.whl.metadata (7.2 kB)
Collecting fsspec<=2024.9.0,>=2023.1.0 (from fsspec[http]<=2024.9.0,>=2023.1.0->datasets)
  Downloading fsspec-2024.9.0-py3-none-any.whl.metadata (11 kB)
Requirement already satisfied: aiohttp in /usr/local/lib/python3.10/dist-packages (from datasets) (3.11.2)
Requirement already satisfied: huggingface-hub>=0.23.0 in /usr/local/lib/python3.10/dist-packages (from datasets) (0.26.2)
Requirement already satisfied: packaging in /usr/local/lib/python3.10/dist-packages (from datasets) (24.2)
Requirement already satisfied: pyyaml>=5.1 in /usr/local/lib/python3.10/dist-packages (from datasets) (6.0.2)
Requirement already satisfied: aiohappyeyeballs>=2.3.0 in /usr/local/lib/python3.10/dist-packages (from aiohttp->datasets) (2.4.3)
Requirement already satisfied: aiosignal>=1.1.2 in /usr/local/lib/python3.10/dist-packages (from aiohttp->datasets) (1.3.1)
Requirement already satisfied: attrs>=17.3.0 in /usr/local/lib/python3.10/dist-packages (from aiohttp->datasets) (24.2.0)
Requirement already satisfied: frozenlist>=1.1.1 in /usr/local/lib/python3.10/dist-packages (from aiohttp->datasets) (1.5.0)
Requirement already satisfied: multidict<7.0,>=4.5 in /usr/local/lib/python3.10/dist-packages (from aiohttp->datasets) (6.1.0)
Requirement already satisfied: propcache>=0.2.0 in /usr/local/lib/python3.10/dist-packages (from aiohttp->datasets) (0.2.0)
Requirement already satisfied: yarl<2.0,>=1.17.0 in /usr/local/lib/python3.10/dist-packages (from aiohttp->datasets) (1.17.2)
Requirement already satisfied: async-timeout<6.0,>=4.0 in /usr/local/lib/python3.10/dist-packages (from aiohttp->datasets) (4.0.3)
Requirement already satisfied: anyio in /usr/local/lib/python3.10/dist-packages (from httpx<0.28.0,>=0.27.0->mistralai) (3.7.1)
Requirement already satisfied: certifi in /usr/local/lib/python3.10/dist-packages (from httpx<0.28.0,>=0.27.0->mistralai) (2024.8.30)
Requirement already satisfied: httpcore==1.* in /usr/local/lib/python3.10/dist-packages (from httpx<0.28.0,>=0.27.0->mistralai) (1.0.7)
Requirement already satisfied: idna in /usr/local/lib/python3.10/dist-packages (from httpx<0.28.0,>=0.27.0->mistralai) (3.10)
Requirement already satisfied: sniffio in /usr/local/lib/python3.10/dist-packages (from httpx<0.28.0,>=0.27.0->mistralai) (1.3.1)
Requirement already satisfied: h11<0.15,>=0.13 in /usr/local/lib/python3.10/dist-packages (from httpcore==1.*->httpx<0.28.0,>=0.27.0->mistralai) (0.14.0)
Requirement already satisfied: typing-extensions>=3.7.4.3 in /usr/local/lib/python3.10/dist-packages (from huggingface-hub>=0.23.0->datasets) (4.12.2)
Requirement already satisfied: annotated-types>=0.6.0 in /usr/local/lib/python3.10/dist-packages (from pydantic<3.0.0,>=2.9.0->mistralai) (0.7.0)
Requirement already satisfied: pydantic-core==2.23.4 in /usr/local/lib/python3.10/dist-packages (from pydantic<3.0.0,>=2.9.0->mistralai) (2.23.4)
Requirement already satisfied: charset-normalizer<4,>=2 in /usr/local/lib/python3.10/dist-packages (from requests>=2.32.2->datasets) (3.4.0)
Requirement already satisfied: urllib3<3,>=1.21.1 in /usr/local/lib/python3.10/dist-packages (from requests>=2.32.2->datasets) (2.2.3)
Collecting mypy-extensions>=0.3.0 (from typing-inspect<0.10.0,>=0.9.0->mistralai)
  Downloading mypy_extensions-1.0.0-py3-none-any.whl.metadata (1.1 kB)
Requirement already satisfied: pytz>=2020.1 in /usr/local/lib/python3.10/dist-packages (from pandas->datasets) (2024.2)
Requirement already satisfied: tzdata>=2022.7 in /usr/local/lib/python3.10/dist-packages (from pandas->datasets) (2024.2)
Requirement already satisfied: exceptiongroup in /usr/local/lib/python3.10/dist-packages (from anyio->httpx<0.28.0,>=0.27.0->mistralai) (1.2.2)
Downloading mistralai-1.2.3-py3-none-any.whl (256 kB)
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 256.9/256.9 kB 8.2 MB/s eta 0:00:00
[?25hDownloading datasets-3.1.0-py3-none-any.whl (480 kB)
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 480.6/480.6 kB 18.1 MB/s eta 0:00:00
[?25hDownloading dill-0.3.8-py3-none-any.whl (116 kB)
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 116.3/116.3 kB 7.5 MB/s eta 0:00:00
[?25hDownloading fsspec-2024.9.0-py3-none-any.whl (179 kB)
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 179.3/179.3 kB 11.3 MB/s eta 0:00:00
[?25hDownloading jsonpath_python-1.0.6-py3-none-any.whl (7.6 kB)
Downloading multiprocess-0.70.16-py310-none-any.whl (134 kB)
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 134.8/134.8 kB 6.5 MB/s eta 0:00:00
[?25hDownloading typing_inspect-0.9.0-py3-none-any.whl (8.8 kB)
Downloading xxhash-3.5.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (194 kB)
   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 194.1/194.1 kB 12.8 MB/s eta 0:00:00
[?25hDownloading mypy_extensions-1.0.0-py3-none-any.whl (4.7 kB)
Installing collected packages: xxhash, mypy-extensions, jsonpath-python, fsspec, dill, typing-inspect, multiprocess, mistralai, datasets
  Attempting uninstall: fsspec
    Found existing installation: fsspec 2024.10.0
    Uninstalling fsspec-2024.10.0:
      Successfully uninstalled fsspec-2024.10.0
ERROR: pip's dependency resolver does not currently take into account all the packages that are installed. This behaviour is the source of the following dependency conflicts.
gcsfs 2024.10.0 requires fsspec==2024.10.0, but you have fsspec 2024.9.0 which is incompatible.
Successfully installed datasets-3.1.0 dill-0.3.8 fsspec-2024.9.0 jsonpath-python-1.0.6 mistralai-1.2.3 multiprocess-0.70.16 mypy-extensions-1.0.0 typing-inspect-0.9.0 xxhash-3.5.0

Obtener imágenes de recibos

Encontramos algunas imágenes de recibos en Hugging Face:

import pandas as pd
from datasets import load_dataset

# Replace 'dataset_name' with the actual name of the dataset you want to download
dataset_name = 'shirastromer/supermarket-receipts'  # Example: IMDB dataset

# Load the dataset
dataset = load_dataset(dataset_name)

# Convert the dataset to a pandas DataFrame
# Assuming you want to load the 'train' split of the dataset
df = pd.DataFrame(dataset['train'])

# Display the first few rows of the DataFrame
df.head()
README.md:   0%|          | 0.00/509 [00:00<?, ?B/s]
train-00000-of-00001.parquet:   0%|          | 0.00/245M [00:00<?, ?B/s]
test-00000-of-00001.parquet:   0%|          | 0.00/60.3M [00:00<?, ?B/s]
Generating train split:   0%|          | 0/153 [00:00<?, ? examples/s]
Generating test split:   0%|          | 0/39 [00:00<?, ? examples/s]
image         receiptId  \
0  <PIL.PngImagePlugin.PngImageFile image mode=RG...   label-studio-91   
1  <PIL.PngImagePlugin.PngImageFile image mode=RG...   label-studio-75   
2  <PIL.PngImagePlugin.PngImageFile image mode=RG...  label-studio-201   
3  <PIL.PngImagePlugin.PngImageFile image mode=RG...  label-studio-138   
4  <PIL.PngImagePlugin.PngImageFile image mode=RG...  label-studio-109   

                                                text  
0  {\n    "store_name": "",\n    "store_addr": ""...  
1  {\n    "store_name": "",\n    "store_addr": ""...  
2  {\n    "store_name": "",\n    "store_addr": ""...  
3  {\n  "store_name": "",\n  "store_addr": "",\n ...  
4  {\n    "store_name": "",\n    "store_addr": ""...
image receiptId text
0 <PIL.PngImagePlugin.PngImageFile image mode=RG... label-studio-91 {\n "store_name": "",\n "store_addr": ""...
1 <PIL.PngImagePlugin.PngImageFile image mode=RG... label-studio-75 {\n "store_name": "",\n "store_addr": ""...
2 <PIL.PngImagePlugin.PngImageFile image mode=RG... label-studio-201 {\n "store_name": "",\n "store_addr": ""...
3 <PIL.PngImagePlugin.PngImageFile image mode=RG... label-studio-138 {\n "store_name": "",\n "store_addr": "",\n ...
4 <PIL.PngImagePlugin.PngImageFile image mode=RG... label-studio-109 {\n "store_name": "",\n "store_addr": ""...

Echemos un vistazo a una imagen

Comencemos con una imagen y obtengamos información de ella con Pixtral Large.

# take a look at an image
df.image[1]
<PIL.PngImagePlugin.PngImageFile image mode=RGB size=576x864>

Extraigamos información de una sola imagen usando la API de Mistral.

import base64
from io import BytesIO
from typing import Any
from PIL.Image import Image

def format_image(image: Image) -> str:
    """
    Converts an image to a base64-encoded string with a JPEG format.

    Args:
        image (Image): The image to be formatted.

    Returns:
        str: The base64-encoded string with a data URI prefix.
    """
    # Convert image to base64
    buffer = BytesIO()
    image.save(buffer, format="JPEG")
    image_base64 = base64.b64encode(buffer.getvalue()).decode("utf-8")

    # Add the prefix for base64 format
    formatted_base64 = f"data:image/jpeg;base64,{image_base64}"
    return formatted_base64
from mistralai.client import Mistral
import os

api_key = os.environ["MISTRAL_API_KEY"]

client = Mistral(api_key=api_key)
# Define the messages for the chat
# Let's extract name, price, and get category for the item
messages = [
    {
        "role": "user",
        "content": [
            {
                "type": "image_url",
                "image_url": format_image(df.image[1])
            },
            {
                "type": "text",
                "text": "Extract the name and price of each item on the receipt, categorize each item into one of the following categories: 'Medical', 'Food', 'Beverage', 'Travel', or 'Other', and return the results as a well-structured JSON object. The JSON object should include only the fields: name, price, and classification for each item."
            }
        ]
    },
    {"role": "assistant", "content": "{", "prefix": True},
]

# Get the chat response
chat_response = client.chat.complete(
    model="pixtral-large-latest",
    messages=messages,
    response_format = {
          "type": "json_object",
    }

)

# Print the content of the response
print(chat_response.choices[0].message.content)
{
  "items": [
    {
      "name": "Maple glazed",
      "price": 25.000,
      "classification": "Food"
    },
    {
      "name": "Plastic Bag Small",
      "price": 0,
      "classification": "Other"
    }
  ]
}

Usar la API por lotes para procesar muchas imágenes

Crear un lote

Procesaremos 10 imágenes como ejemplo.

import json
from io import BytesIO

num_samples = 10

list_of_json = []
for idx in range(num_samples):
    request = {
        "custom_id": str(idx),
        "body": {
            "max_tokens": 1000,
            "messages": [
                {
                    "role": "user",
                    "content": [
                        {
                            "type": "image_url",
                            "image_url": format_image(df.image[idx])
                        },
                        {
                            "type": "text",
                            "text": "Identify the name and price of each item on the receipt, categorize each item into one of the following categories: 'Medical', 'Food', 'Beverage', 'Travel', or 'Other', and return the results as a well-structured JSON object. The JSON object should include only the fields: name, price, and classification for each item."
                        }
                    ]
                },
                {
                    "role": "assistant",
                    "content": "{",
                    "prefix": True
                }
            ],
        "response_format": {"type": "json_object"}
        }
    }
    list_of_json.append(json.dumps(request).encode("utf-8"))

Subir tu lote

batch_data = client.files.upload(
    file={
        "file_name": "file.jsonl",
        "content": b"\n".join(list_of_json)},
    purpose = "batch"
)
batch_data
UploadFileOut(id='30f63bb3-6f9e-4dd5-9295-a9cdbdeabf75', object='file', bytes=1769058, created_at=1732400347, filename='file.jsonl', purpose='batch', sample_type='batch_request', source='upload', num_lines=10)

Crear un trabajo por lotes

created_job = client.batch.jobs.create(
    input_files=[batch_data.id],
    model="pixtral-large-latest",
    endpoint="/v1/chat/completions",
    metadata={"job_type": "testing"}
)
created_job
BatchJobOut(id='5befe8c6-b8cb-4858-a068-d600cf5ed73f', input_files=['30f63bb3-6f9e-4dd5-9295-a9cdbdeabf75'], endpoint='/v1/chat/completions', model='pixtral-large-latest', errors=[], status='QUEUED', created_at=1732400351, total_requests=0, completed_requests=0, succeeded_requests=0, failed_requests=0, OBJECT='batch', metadata={'job_type': 'testing'}, output_file=None, error_file=None, started_at=None, completed_at=None)

Obtener detalles del trabajo por lotes

retrieved_job = client.batch.jobs.get(job_id=created_job.id)
retrieved_job
BatchJobOut(id='5befe8c6-b8cb-4858-a068-d600cf5ed73f', input_files=['30f63bb3-6f9e-4dd5-9295-a9cdbdeabf75'], endpoint='/v1/chat/completions', model='pixtral-large-latest', errors=[], status='SUCCESS', created_at=1732400351, total_requests=10, completed_requests=10, succeeded_requests=10, failed_requests=0, OBJECT='batch', metadata={'job_type': 'testing'}, output_file='950fae83-2802-40c0-a677-c7839a4e238a', error_file=None, started_at=1732400351, completed_at=1732400376)
print(f"Total requests: {retrieved_job.total_requests}")
print(f"Failed requests: {retrieved_job.failed_requests}")
print(f"Successful requests: {retrieved_job.succeeded_requests}")
print(
    f"Percent done: {round((retrieved_job.succeeded_requests + retrieved_job.failed_requests) / retrieved_job.total_requests, 4) * 100}")
Total requests: 10
Failed requests: 0
Successful requests: 10
Percent done: 100.0

Obtener resultados del lote

output = client.files.download(file_id=retrieved_job.output_file).read().decode("utf-8").strip()
print(output)
{"id":"0-61c18c3c-1873-4288-9f92-af97f696fb2c","custom_id":"0","response":{"status_code":200,"body":{"id":"7f3874116cb943fea58853d60cf17ff3","object":"chat.completion","model":"pixtral-large-latest","usage":{"prompt_tokens":2898,"completion_tokens":88,"total_tokens":2986},"created":1732400353,"choices":[{"index":0,"finish_reason":"stop","message":{"role":"assistant","content":"{\n  \"items\": [\n    {\n      \"name\": \"VANCOOLEN\",\n      \"price\": 8182,\n      \"classification\": \"Medical\"\n    },\n    {\n      \"name\": \"PLASTIK 25\",\n      \"price\": 0,\n      \"classification\": \"Other\"\n    }\n  ]\n}","tool_calls":null}}]}},"error":null}
{"id":"1-18219f09-acff-4644-baec-a73b15788364","custom_id":"1","response":{"status_code":200,"body":{"id":"aaee50ed69384fe9ae8e9fe9c4bc0c55","object":"chat.completion","model":"pixtral-large-latest","usage":{"prompt_tokens":2080,"completion_tokens":86,"total_tokens":2166},"created":1732400354,"choices":[{"index":0,"finish_reason":"stop","message":{"role":"assistant","content":"{\n  \"items\": [\n    {\n      \"name\": \"Maple glazed\",\n      \"price\": 25.000,\n      \"classification\": \"Food\"\n    },\n    {\n      \"name\": \"Plastic Bag Small\",\n      \"price\": 0,\n      \"classification\": \"Other\"\n    }\n  ]\n}","tool_calls":null}}]}},"error":null}
{"id":"2-6ae7d6cb-4c00-4836-b182-b3a37c315c51","custom_id":"2","response":{"status_code":200,"body":{"id":"c5e88f4a35374ec3becfd756ceb7e80e","object":"chat.completion","model":"pixtral-large-latest","usage":{"prompt_tokens":2450,"completion_tokens":88,"total_tokens":2538},"created":1732400353,"choices":[{"index":0,"finish_reason":"stop","message":{"role":"assistant","content":"{\n  \"items\": [\n    {\n      \"name\": \"DEPT04\",\n      \"price\": 24000,\n      \"classification\": \"Other\"\n    },\n    {\n      \"name\": \"DEPT01\",\n      \"price\": 22000,\n      \"classification\": \"Other\"\n    }\n  ]\n}","tool_calls":null}}]}},"error":null}
{"id":"3-8a7f98e8-1e79-4b7b-8872-5675f56ab42e","custom_id":"3","response":{"status_code":200,"body":{"id":"8dd62429cb5b406ca120339fccc0cd55","object":"chat.completion","model":"pixtral-large-latest","usage":{"prompt_tokens":2898,"completion_tokens":58,"total_tokens":2956},"created":1732400353,"choices":[{"index":0,"finish_reason":"stop","message":{"role":"assistant","content":"{\n  \"items\": [\n    {\n      \"name\": \"TAKOYAKI 12PCS\",\n      \"price\": 30000,\n      \"classification\": \"Food\"\n    }\n  ]\n}","tool_calls":null}}]}},"error":null}
{"id":"4-f14a0083-95c5-4f60-bae0-baf591aef3f2","custom_id":"4","response":{"status_code":200,"body":{"id":"c4c63eb1f77e44de8bb615b33a4d96ad","object":"chat.completion","model":"pixtral-large-latest","usage":{"prompt_tokens":2130,"completion_tokens":168,"total_tokens":2298},"created":1732400353,"choices":[{"index":0,"finish_reason":"stop","message":{"role":"assistant","content":"{\n  \"items\": [\n    {\n      \"name\": \"Jamur Crispy\",\n      \"price\": 13500,\n      \"classification\": \"Food\"\n    },\n    {\n      \"name\": \"Nasi Putih\",\n      \"price\": 7000,\n      \"classification\": \"Food\"\n    },\n    {\n      \"name\": \"Sambel Kecap\",\n      \"price\": 4500,\n      \"classification\": \"Food\"\n    },\n    {\n      \"name\": \"Es Teh\",\n      \"price\": 7500,\n      \"classification\": \"Beverage\"\n    }\n  ]\n}","tool_calls":null}}]}},"error":null}
{"id":"5-aacc6428-9266-4690-aa2c-47b84ec06189","custom_id":"5","response":{"status_code":200,"body":{"id":"bd959d8edc234e5585dbfb8d0005ffe4","object":"chat.completion","model":"pixtral-large-latest","usage":{"prompt_tokens":3218,"completion_tokens":100,"total_tokens":3318},"created":1732400353,"choices":[{"index":0,"finish_reason":"stop","message":{"role":"assistant","content":"{\n  \"items\": [\n    {\n      \"name\": \"118 Round Wagyu (1gr)\",\n      \"price\": 47.200,\n      \"classification\": \"Food\"\n    },\n    {\n      \"name\": \"1 Wagyu Rice Box\",\n      \"price\": 10.000,\n      \"classification\": \"Food\"\n    }\n  ]\n}","tool_calls":null}}]}},"error":null}
{"id":"6-70a513f7-732d-47d4-a4fe-52941107e534","custom_id":"6","response":{"status_code":200,"body":{"id":"7ed84ab380d54ad69f774b4149a26147","object":"chat.completion","model":"pixtral-large-latest","usage":{"prompt_tokens":2080,"completion_tokens":94,"total_tokens":2174},"created":1732400353,"choices":[{"index":0,"finish_reason":"stop","message":{"role":"assistant","content":"{\n  \"items\": [\n    {\n      \"name\": \"Silky Green Tea\",\n      \"price\": 12500,\n      \"classification\": \"Beverage\"\n    },\n    {\n      \"name\": \"Silky Hazelnut\",\n      \"price\": 12500,\n      \"classification\": \"Beverage\"\n    }\n  ]\n}","tool_calls":null}}]}},"error":null}
{"id":"7-e6225030-c9d7-42a5-8834-eb655a0afdaa","custom_id":"7","response":{"status_code":200,"body":{"id":"28e76cc88e5249188ec4ecc1b63856fd","object":"chat.completion","model":"pixtral-large-latest","usage":{"prompt_tokens":2898,"completion_tokens":55,"total_tokens":2953},"created":1732400353,"choices":[{"index":0,"finish_reason":"stop","message":{"role":"assistant","content":"{\n  \"items\": [\n    {\n      \"name\": \"THAI ICED TEA\",\n      \"price\": 40.000,\n      \"classification\": \"Beverage\"\n    }\n  ]\n}","tool_calls":null}}]}},"error":null}
{"id":"8-9614eb6b-cb35-4d86-aa2c-1432db6d7874","custom_id":"8","response":{"status_code":200,"body":{"id":"8b55ceb5bb554091a7b6d88083c9a49e","object":"chat.completion","model":"pixtral-large-latest","usage":{"prompt_tokens":2080,"completion_tokens":51,"total_tokens":2131},"created":1732400353,"choices":[{"index":0,"finish_reason":"stop","message":{"role":"assistant","content":"{\n  \"items\": [\n    {\n      \"name\": \"BBQ Chicken\",\n      \"price\": 41000,\n      \"classification\": \"Food\"\n    }\n  ]\n}","tool_calls":null}}]}},"error":null}
{"id":"0-5565de65-a128-4828-bec1-5162f1b8c376","custom_id":"9","response":{"status_code":200,"body":{"id":"8096776b2d25495d9e226000f58f08fe","object":"chat.completion","model":"pixtral-large-latest","usage":{"prompt_tokens":2080,"completion_tokens":57,"total_tokens":2137},"created":1732400353,"choices":[{"index":0,"finish_reason":"stop","message":{"role":"assistant","content":"{\n  \"items\": [\n    {\n      \"name\": \"KOREAN CURRY M\",\n      \"price\": 40.909,\n      \"classification\": \"Food\"\n    }\n  ]\n}","tool_calls":null}}]}},"error":null}

Extraer información a un dataframe de Pandas

# Parse JSON lines
lines = output.strip().split('\n')

# Extract required fields
extracted_data = []
for line in lines:
    parsed_line = json.loads(line)
    custom_id = parsed_line.get("custom_id")
    response = parsed_line.get("response", {})
    body = response.get("body", {})
    choices = body.get("choices", [])

    for choice in choices:
        message_content = choice.get("message", {}).get("content", "")
        # Extract items from the JSON string in "content"
        try:
            items_data = json.loads(message_content.strip('```'))
            items = items_data if isinstance(items_data, list) else items_data.get("items", [])
            for item in items:
                extracted_data.append({
                    "custom_id": custom_id,
                    "name": item.get("name"),
                    "price": item.get("price"),
                    "classification": item.get("classification")
                })
        except json.JSONDecodeError:
            continue

# Create a Pandas DataFrame
df_output = pd.DataFrame(extracted_data)
df_output
custom_id                   name      price classification
0          0              VANCOOLEN   8182.000        Medical
1          0             PLASTIK 25      0.000          Other
2          1           Maple glazed     25.000           Food
3          1      Plastic Bag Small      0.000          Other
4          2                 DEPT04  24000.000          Other
5          2                 DEPT01  22000.000          Other
6          3         TAKOYAKI 12PCS  30000.000           Food
7          4           Jamur Crispy  13500.000           Food
8          4             Nasi Putih   7000.000           Food
9          4           Sambel Kecap   4500.000           Food
10         4                 Es Teh   7500.000       Beverage
11         5  118 Round Wagyu (1gr)     47.200           Food
12         5       1 Wagyu Rice Box     10.000           Food
13         6        Silky Green Tea  12500.000       Beverage
14         6         Silky Hazelnut  12500.000       Beverage
15         7          THAI ICED TEA     40.000       Beverage
16         8            BBQ Chicken  41000.000           Food
17         9         KOREAN CURRY M     40.909           Food
custom_id name price classification
0 0 VANCOOLEN 8182.000 Medical
1 0 PLASTIK 25 0.000 Other
2 1 Maple glazed 25.000 Food
3 1 Plastic Bag Small 0.000 Other
4 2 DEPT04 24000.000 Other
5 2 DEPT01 22000.000 Other
6 3 TAKOYAKI 12PCS 30000.000 Food
7 4 Jamur Crispy 13500.000 Food
8 4 Nasi Putih 7000.000 Food
9 4 Sambel Kecap 4500.000 Food
10 4 Es Teh 7500.000 Beverage
11 5 118 Round Wagyu (1gr) 47.200 Food
12 5 1 Wagyu Rice Box 10.000 Food
13 6 Silky Green Tea 12500.000 Beverage
14 6 Silky Hazelnut 12500.000 Beverage
15 7 THAI ICED TEA 40.000 Beverage
16 8 BBQ Chicken 41000.000 Food
17 9 KOREAN CURRY M 40.909 Food
Lección del curso «Mistral Cookbook» de Mistral AI, publicado con licencia MIT. Traducción y adaptación al español de IA con Clase. IA con Clase no está afiliado a Mistral AI. Ver el original · Licencia
Esta lección es gratuita. El resto del curso se abre con la Membresía de IA con Clase, que incluye todos los cursos del catálogo. Ver precios
← AnteriorSiguiente: Extracción de descripción de imágenes con la API Pixtral de Mistral →