Пример работы с VLM qwen2.5-vl-72b-instruct через сервис OpenRouter

Посмотрим как LLM может анализировать изображения, распознавать текст со сканов даже плохого качества.

В современном мире обработка изображений с помощью искусственного интеллекта становится всё более востребованной — от автоматического распознавания документов до анализа скриншотов и чеков. В этой статье мы покажем, как с помощью простого, но мощного инструмента — визуально-лингвистической модели (VLM) — можно извлекать текст, понимать содержание изображений и структурировать данные в формате JSON, пригодном для дальнейшей обработки.

Мы используем модель Qwen2.5-VL через API сервиса OpenRouter, который позволяет легко взаимодействовать с передовыми мультимодальными моделями, не разворачивая их локально. Наша задача — продемонстрировать, как один и тот же код может решать разнообразные практические кейсы: распознавание текста на сканах документов, извлечение реквизитов (например, ОГРН), анализ чеков, интерпретация учебных материалов, перевод и анализ постов социальных сетей и анализ фото с камер наблюдения.

В листинге кода вы увидите, как мы загружаем изображение, кодируем его в формат base64 и отправляем в модель с точным текстовым запросом. Ответ приходит в виде структурированного текста или JSON — в зависимости от поставленной задачи. Мы разбираем примеры с плохим качеством изображений, скриншотами книг, официальными документами и сценами из реальной жизни, чтобы показать надёжность и гибкость подхода.

Этот код можно легко адаптировать под автоматизацию обработки документов, интеграцию в CRM-системы, парсинг чеков или создание умных помощников. Главное — правильно сформулировать запрос, предоставить изображение, и модель сделает остальное. Давайте посмотрим, как это работает на практике.

Импорты нужных библиотек.

import requests
import json
import base64
from dotenv import load_dotenv
import os
from PIL import Image
from IPython.display import display
# создайте файл env с содержимым OPEN_ROUTER_KEY=ваш_ключ
load_dotenv('env')
True
# для работы нужен ключ, созданный на https://openrouter.ai/
OPENROUTER_API_KEY = os.environ['OPEN_ROUTER_KEY']

Файлы с которыми будем работать.

! ls -l ./assets
итого 3988
-rw-rw-r-- 1 alex alex  643692 июл 25 15:39 cheque.jpg
-rw-rw-r-- 1 alex alex   88807 июл 25 16:56 code_book_bad.jpg
-rw-rw-r-- 1 alex alex  298443 июл 25 16:54 code_book.jpg
-rw-rw-r-- 1 alex alex   45148 июл 25 20:04 doc.jpg
-rw-rw-r-- 1 alex alex 1915183 июл 25 15:42 doc_scan.jpg
-rw-rw-r-- 1 alex alex   36969 июл 25 17:16 fight.jpg
-rw-rw-r-- 1 alex alex   19260 июл 25 15:47 page_pic_bad_2.jpg
-rw-rw-r-- 1 alex alex   73681 июл 25 15:46 page_pic.jpg
-rw-rw-r-- 1 alex alex  831007 июл 25 15:41 page_scan.jpg
-rw-rw-r-- 1 alex alex   74747 июл 25 15:50 scan_bad.jpg
-rw-rw-r-- 1 alex alex   35230 июл 25 15:54 screen_bad.jpg

Вспомогательные функции.

# функция кодирования изображения в base64
def encode_image_to_base64(image_path):
    with open(image_path, "rb") as image_file:
        return base64.b64encode(image_file.read()).decode('utf-8')
# функция показа изображения в Jupyter Notebook
def show_image(filepath: str):
    image = Image.open(filepath)
    image.thumbnail((600, 600))
    display(image)

Модель с которой будем работать.

  • https://openrouter.ai/qwen/qwen2.5-vl-72b-instruct
  • 32,000 context
  • $0.25 / Million input tokens
  • $0.75 / Million output tokens
  • 25 центов за 1 млн входных токенов, 75 центов за 1 млн выходных токенов
# функция обращения к VLM через сервис OpenRouter

def response_img(query: str, filepath: str, model: str):
    base64_image = encode_image_to_base64(filepath)

    response = requests.post(
      url="https://openrouter.ai/api/v1/chat/completions",
      headers={
        "Authorization": f"Bearer {OPENROUTER_API_KEY}",
        "Content-Type": "application/json",
        "HTTP-Referer": "test", # Optional. Site URL for rankings on openrouter.ai.
        "X-Title": "test", # Optional. Site title for rankings on openrouter.ai.
      },
      data=json.dumps({
        "model": model,
        "messages": [
          {
            "role": "user",
            "content": [
              {
                "type": "text",
                "text": query
              },
              {
                "type": "image_url",
                "image_url": {
                  "url": f"data:image/jpeg;base64,{base64_image}"
                }
              }
            ]
          }
        ],
        "temperature": 0.01
      })
    )
    return response.json()

Начинаем эксперименты. Проанализируем фото кассового чека.

query = 'что на этом изображении?'
filepath = './assets/cheque.jpg'
model = "qwen/qwen2.5-vl-32b-instruct"
# qwen/qwen2.5-vl-32b-instruct
show_image(filepath)

png

Ответ модели

res = response_img(query, filepath, model)
res
{'id': 'gen-1753693827-TDzmA5U6JAs9Ucfh1pIp',
 'provider': 'DeepInfra',
 'model': 'qwen/qwen2.5-vl-32b-instruct',
 'object': 'chat.completion',
 'created': 1753693827,
 'choices': [{'logprobs': None,
   'finish_reason': 'stop',
   'native_finish_reason': 'stop',
   'index': 0,
   'message': {'role': 'assistant',
    'content': 'На изображении представлен чек от магазина DNS (DNS 2350) с информацией о покупке. Вот основные детали, которые можно увидеть:\n\n### **Основная информация о чеке:**\n- **Магазин:** DNS 2350\n- **Адрес магазина:** Ростов-на-Дону, Ростовская область, Космонавтов, 3д, 2/2\n- **Телефон магазина:** 88007707999\n- **Дата и время покупки:** 02.05.23, 20:16\n- **Терминал:** 22935087\n- **Мастер-терминал:** 211000004035\n- **Тип карты:** MasterCard\n- **Номер карты:** Частично виден (последние 4 цифры: 0809)\n- **Сумма покупки:** 4199.00 рублей\n- **Комиссия за операцию:** 0 рублей\n- **Код авторизации:** 013319\n- **Номер ссылки:** 719967494147\n- **Временный НИИ-код:** 7D069CE29DAED11DFE1D558CDA6D7C1C4EF04AC\n\n### **Информация о товаре:**\n- **Товар:** Смартфон BQ 5565L FEST 2/16GB [5, 45/LTE/2Mp/2200/Black] (Гарантия - 12 месяцев)\n- **Количество:** 1 шт.\n- **Цена за штуку:** 199,00 рублей\n- **Итоговая стоимость товара:** 199,00 рублей\n\n### **Дополнительные сведения:**\n- **Налог НДС:** 20% (699,83 рублей)\n- **Итоговая сумма с учетом НДС:** 4199,00 рублей\n- **Адрес доставки:** 344092, Ростовская область, Ростов-на-Дону, Космонавтов, дом 2, корпус 2\n- **Информация о сайте:** dns-shop.ru\n- **Номер заказа:** 344068\n- **Место расчетов:** Ростов ТРЦ Бавария Гипер\n- **Адрес ТРЦ:** Ростов-на-Дону, Космонавтов, дом 2, корпус 2\n- **АВТОМАТ:** EY0000000000000215\n- **QR-код:** Присутствует в правом нижнем углу чека\n- **Сайт для проверки чека:** ofdp.platformaofd.ru\n- **ИНН:** 2540167061\n- **Кассир:** 6826\n\n### **Дополнительные данные:**\n- **Сумма НДС:** 699,83 рублей\n- **Сумма без НДС:** 3499,17 рублей\n- **Сумма с НДС:** 4199,00 рублей\n\n### **Вывод:**\nЭто чек о покупке смартфона BQ 5565L FEST 2/16GB в магазине DNS в Ростове-на-Дону. Сумма покупки составила 4199,00 рублей, включая НДС. Товар заказан с доставкой по указанному адресу.',
    'refusal': None,
    'reasoning': None}}],
 'usage': {'prompt_tokens': 4371,
  'completion_tokens': 909,
  'total_tokens': 5280,
  'prompt_tokens_details': None}}

Только текстовая часть ответа модели без метаданных.

print(res['choices'][0]['message']['content'])
На изображении представлен чек от магазина DNS (DNS 2350) с информацией о покупке. Вот основные детали, которые можно увидеть:

### **Основная информация о чеке:**
- **Магазин:** DNS 2350
- **Адрес магазина:** Ростов-на-Дону, Ростовская область, Космонавтов, 3д, 2/2
- **Телефон магазина:** 88007707999
- **Дата и время покупки:** 02.05.23, 20:16
- **Терминал:** 22935087
- **Мастер-терминал:** 211000004035
- **Тип карты:** MasterCard
- **Номер карты:** Частично виден (последние 4 цифры: 0809)
- **Сумма покупки:** 4199.00 рублей
- **Комиссия за операцию:** 0 рублей
- **Код авторизации:** 013319
- **Номер ссылки:** 719967494147
- **Временный НИИ-код:** 7D069CE29DAED11DFE1D558CDA6D7C1C4EF04AC

### **Информация о товаре:**
- **Товар:** Смартфон BQ 5565L FEST 2/16GB [5, 45/LTE/2Mp/2200/Black] (Гарантия - 12 месяцев)
- **Количество:** 1 шт.
- **Цена за штуку:** 199,00 рублей
- **Итоговая стоимость товара:** 199,00 рублей

### **Дополнительные сведения:**
- **Налог НДС:** 20% (699,83 рублей)
- **Итоговая сумма с учетом НДС:** 4199,00 рублей
- **Адрес доставки:** 344092, Ростовская область, Ростов-на-Дону, Космонавтов, дом 2, корпус 2
- **Информация о сайте:** dns-shop.ru
- **Номер заказа:** 344068
- **Место расчетов:** Ростов ТРЦ Бавария Гипер
- **Адрес ТРЦ:** Ростов-на-Дону, Космонавтов, дом 2, корпус 2
- **АВТОМАТ:** EY0000000000000215
- **QR-код:** Присутствует в правом нижнем углу чека
- **Сайт для проверки чека:** ofdp.platformaofd.ru
- **ИНН:** 2540167061
- **Кассир:** 6826

### **Дополнительные данные:**
- **Сумма НДС:** 699,83 рублей
- **Сумма без НДС:** 3499,17 рублей
- **Сумма с НДС:** 4199,00 рублей

### **Вывод:**
Это чек о покупке смартфона BQ 5565L FEST 2/16GB в магазине DNS в Ростове-на-Дону. Сумма покупки составила 4199,00 рублей, включая НДС. Товар заказан с доставкой по указанному адресу.

Выведем анализ чека в структурированный вид.

query = 'выведи всю информацию с данного чека в формате json.'
filepath = './assets/cheque.jpg'
model = "qwen/qwen2.5-vl-32b-instruct"
res = response_img(query, filepath, model)

Ответ модели.

res
{'id': 'gen-1753693955-fjJ10R01IVpT2tKR8a7Y',
 'provider': 'DeepInfra',
 'model': 'qwen/qwen2.5-vl-32b-instruct',
 'object': 'chat.completion',
 'created': 1753693955,
 'choices': [{'logprobs': None,
   'finish_reason': 'stop',
   'native_finish_reason': 'stop',
   'index': 0,
   'message': {'role': 'assistant',
    'content': 'Вот информация с чека в формате JSON:\n\n```json\n{\n  "shop": "DNS 2350",\n  "address": "Ростов-на-Дону, Ростовская область, пп-кт Космонавтов, 3д 2/2",\n  "phone": "88007707999",\n  "date": "02.05.23",\n  "time": "20:16",\n  "check_number": "0030",\n  "terminal": "22935087",\n  "merchant": "211000004035",\n  "card": {\n    "type": "MASTERCARD",\n    "number": "**** **** **** 0809",\n    "amount": 4199.00,\n    "commission": 0.00,\n    "status": "ОАОБРЕНО"\n  },\n  "authorization_code": "013319",\n  "reference_number": "719967494147",\n  "transaction_id": "7D069CE29DAED11DFE1D558CDAA6D7C1C4EF04AC",\n  "delivery_address": "344092, Ростовская, обл, Ростов-на-Дону, г, Космонавтов, пп-кт, дом 2, корпус 2, т.",\n  "items": [\n    {\n      "description": "4888290 Смартфон BQ 5565L FEST 2/16GB [5, 45/LTE/2MpX/2200/Black] (Гарантия - 12 мес)",\n      "quantity": 1,\n      "price": 199.00,\n      "total": 199.00\n    }\n  ],\n  "discount": {\n    "percentage": 20,\n    "amount": 699.83\n  },\n  "total": 4199.00,\n  "document_number": "E-08631902",\n  "shop_website": "dns-shop.ru",\n  "delivery_point": {\n    "address": "Ростов-на-Дону, пп-кт им. Михаила Нагибина, д. 40, пом. 36, 38",\n    "number": 344068\n  },\n  "payment_details": {\n    "amount": 4199.00,\n    "discount_amount": 699.83,\n    "final_amount": 4199.00\n  },\n  "delivery_address": "344092, Ростовская, обл, Ростов-на-Дону, г, Космонавтов, пп-кт, дом 2, корпус 2",\n  "payment_terminal": "EY00000000000000215",\n  "payment_location": "Ростов ТРЦ Вавилон Гипер",\n  "payment_method": "000 \\"Звотор ОфД\\"",\n  "kkt_serial_numbers": [\n    "0005443236043929",\n    "0484480005028820"\n  ],\n  "fiscal_document_number": "06367",\n  "fiscal_storage_number": "9960440502798330",\n  "fiscal_register_number": "2540167061",\n  "fiscal_document_date": "02.05.23",\n  "fiscal_document_time": "20:22",\n  "fiscal_document_code": "6826",\n  "fiscal_document_number": "4021146396",\n  "additional_info": {\n    "url": "ofdp.platformaofd.ru",\n    "tax_info": "www.nalog.gov.ru"\n  }\n}\n```\n\nЭтот JSON содержит все ключевые данные из чека, включая информацию о магазине, товарах, оплате, адресе доставки и других деталях.',
    'refusal': None,
    'reasoning': None}}],
 'usage': {'prompt_tokens': 4381,
  'completion_tokens': 998,
  'total_tokens': 5379,
  'prompt_tokens_details': None}}

Только текстовая часть ответа модели.

print(res['choices'][0]['message']['content'])
Вот информация с чека в формате JSON:

```json
{
  "shop": "DNS 2350",
  "address": "Ростов-на-Дону, Ростовская область, пп-кт Космонавтов, 3д 2/2",
  "phone": "88007707999",
  "date": "02.05.23",
  "time": "20:16",
  "check_number": "0030",
  "terminal": "22935087",
  "merchant": "211000004035",
  "card": {
    "type": "MASTERCARD",
    "number": "**** **** **** 0809",
    "amount": 4199.00,
    "commission": 0.00,
    "status": "ОАОБРЕНО"
  },
  "authorization_code": "013319",
  "reference_number": "719967494147",
  "transaction_id": "7D069CE29DAED11DFE1D558CDAA6D7C1C4EF04AC",
  "delivery_address": "344092, Ростовская, обл, Ростов-на-Дону, г, Космонавтов, пп-кт, дом 2, корпус 2, т.",
  "items": [
    {
      "description": "4888290 Смартфон BQ 5565L FEST 2/16GB [5, 45/LTE/2MpX/2200/Black] (Гарантия - 12 мес)",
      "quantity": 1,
      "price": 199.00,
      "total": 199.00
    }
  ],
  "discount": {
    "percentage": 20,
    "amount": 699.83
  },
  "total": 4199.00,
  "document_number": "E-08631902",
  "shop_website": "dns-shop.ru",
  "delivery_point": {
    "address": "Ростов-на-Дону, пп-кт им. Михаила Нагибина, д. 40, пом. 36, 38",
    "number": 344068
  },
  "payment_details": {
    "amount": 4199.00,
    "discount_amount": 699.83,
    "final_amount": 4199.00
  },
  "delivery_address": "344092, Ростовская, обл, Ростов-на-Дону, г, Космонавтов, пп-кт, дом 2, корпус 2",
  "payment_terminal": "EY00000000000000215",
  "payment_location": "Ростов ТРЦ Вавилон Гипер",
  "payment_method": "000 \"Звотор ОфД\"",
  "kkt_serial_numbers": [
    "0005443236043929",
    "0484480005028820"
  ],
  "fiscal_document_number": "06367",
  "fiscal_storage_number": "9960440502798330",
  "fiscal_register_number": "2540167061",
  "fiscal_document_date": "02.05.23",
  "fiscal_document_time": "20:22",
  "fiscal_document_code": "6826",
  "fiscal_document_number": "4021146396",
  "additional_info": {
    "url": "ofdp.platformaofd.ru",
    "tax_info": "www.nalog.gov.ru"
  }
}
```

Этот JSON содержит все ключевые данные из чека, включая информацию о магазине, товарах, оплате, адресе доставки и других деталях.
query = 'выведи всю информацию с данного изображения в формате json.'
filepath = './assets/doc_scan.jpg'
model = "qwen/qwen2.5-vl-32b-instruct"
show_image(filepath)

png

res = response_img(query, filepath, model)
res
{'id': 'gen-1753694222-VZ50sGeF29BCtlKqmjsc',
 'provider': 'DeepInfra',
 'model': 'qwen/qwen2.5-vl-32b-instruct',
 'object': 'chat.completion',
 'created': 1753694222,
 'choices': [{'logprobs': None,
   'finish_reason': 'stop',
   'native_finish_reason': 'stop',
   'index': 0,
   'message': {'role': 'assistant',
    'content': 'Вот информация с изображения в формате JSON:\n\n```json\n{\n  "document_type": "Свидетельство о государственной регистрации некоммерческой организации",\n  "issuing_authority": "Министерство юстиции Российской Федерации",\n  "organization_name": "Благотворительный фонд «Мальтийская служба помощи Аугсбург и Берлин»",\n  "address": {\n    "index": "107140",\n    "city": "г. Москва",\n    "street": "ул. Краснопрудная",\n    "house": "д. 24/2",\n    "building": "стр. 1"\n  },\n  "registration_decision_date": "26 апреля 1996 г.",\n  "registration_in_egrl_date": "02 июля 2002 г.",\n  "registration_number": "1027700000712",\n  "issuer": {\n    "position": "Исполняющий обязанности начальника Главного управления Министерства юстиции Российской Федерации по Москве",\n    "name": "Л.Г. Ильина"\n  },\n  "account_number": "77140111375",\n  "issue_date": "03 декабря 2012 г."\n}\n```\n\nЭта структура JSON содержит все ключевые данные, извлеченные из изображения.',
    'refusal': None,
    'reasoning': None}}],
 'usage': {'prompt_tokens': 4994,
  'completion_tokens': 324,
  'total_tokens': 5318,
  'prompt_tokens_details': None}}
print(res['choices'][0]['message']['content'])
Вот информация с изображения в формате JSON:

```json
{
  "document_type": "Свидетельство о государственной регистрации некоммерческой организации",
  "issuing_authority": "Министерство юстиции Российской Федерации",
  "organization_name": "Благотворительный фонд «Мальтийская служба помощи Аугсбург и Берлин»",
  "address": {
    "postal_code": "107140",
    "city": "г. Москва",
    "street": "ул. Краснопрудная",
    "house_number": "д. 24/2",
    "building": "стр. 1"
  },
  "registration_decision_date": "26 апреля 1996 г.",
  "registration_in_egrl_date": "02 июля 2002 г.",
  "registration_number": "1027700000712",
  "issuer": {
    "position": "Исполняющий обязанности начальника Главного управления Министерства юстиции Российской Федерации по Москве",
    "name": "Л.Г. Ильина"
  },
  "account_number": "77140111375",
  "issue_date": "03 декабря 2012 г."
}
```

Эта структура JSON содержит все ключевые данные, извлеченные из изображения.
query = 'извлеки ОГРН и название компании из данного скана. Сложи в json структуру с полями name: str и ogrn: str'
filepath = './assets/doc_scan.jpg'
model = "qwen/qwen2.5-vl-32b-instruct"
res = response_img(query, filepath, model)
print(res['choices'][0]['message']['content'])
Из скана извлечен следующий ОГРН и название компании:

- **ОГРН**: 1027700000712
- **Название компании**: Благотворительный фонд «Мальтийская служба помощи Аугсбург и Берлин»

Сформированная JSON-структура:

```json
{
  "name": "Благотворительный фонд «Мальтийская служба помощи Аугсбург и Берлин»",
  "ogrn": "1027700000712"
}
```
query = 'выведи всю информацию с данного изображения в формате json.'
filepath = './assets/page_pic_bad_2.jpg'
model = "qwen/qwen2.5-vl-32b-instruct"
show_image(filepath)

png

res = response_img(query, filepath, model)
print(res['choices'][0]['message']['content'])
Вот информация с изображения в формате JSON:

```json
{
  "section": "3.1 Arduino Hardware",
  "figures": [
    {
      "figure_number": "3.2",
      "caption": "Arduino Nano connected to a laptop PC",
      "description": "Изображение показывает Arduino Nano, подключенный к ноутбуку через USB-кабель."
    },
    {
      "figure_number": "3.3",
      "caption": "Arduino app with empty program",
      "description": "Изображение показывает интерфейс Arduino IDE с пустой программой, содержащей две функции: `setup()` и `loop()`. Код в окне редактора выглядит следующим образом:\n\n```c\nvoid setup() {\n  // put your setup code here, to run once:\n}\n\nvoid loop() {\n  // put your main code here, to run repeatedly:\n}\n```"
    }
  ],
  "text": {
    "paragraph": "Для написания программы для Arduino и загрузки её на контроллер используется бесплатная среда разработки Arduino IDE (Integrated Development Environment). При запуске на Mac, Windows или Linux PC пользователь видит пустую программу с двумя функциями на C: `setup()` и `loop()`, как показано на рисунке 3.3."
  },
  "footnote": {
    "number": "1",
    "text": "Arduino IDE online at https://www.arduino.cc"
  },
  "page_number": 55
}
```

Этот JSON-объект включает все ключевые элементы изображения, такие как номера рисунков, их подписи, описания и текстовый контент.
query = 'распознай весь текст со скриншота максимально точно'
filepath = './assets/code_book_bad.jpg'
model = "qwen/qwen2.5-vl-32b-instruct"
show_image(filepath)

png

res = response_img(query, filepath, model)
print(res['choices'][0]['message']['content'])
Вот текст, распознанный со скриншота:

---

### Левая страница (страница 242)

**Inserted Sauteed String Beans**  
**Inserted Confucius "Chicken"**

Запись строки в файл формата CSV выполняется аналогично чтению из строки из такого файла. С этой целью вызывается функция `fputcsv()`, принимающая в качестве аргументов дескриптор файла и массив значений, заранее подготовленных в формате CSV для записи в файл. Так, в примере 9.11 демонстрируется применение функций `fputcsv()` и `fopen()` для записи в файл формата CSV информации, извлеченной из таблицы базы данных.

#### Пример 9.11. Запись данных в файл формата CSV

```php
try {
    $db = new PDO('sqlite:/tmp/restaurant.db');
} catch (Exception $e) {
    print "Couldn't connect to database: " . $e->getMessage();
    exit();
}

// открыть файл формата CSV для записи
$fh = fopen('dish-list.csv', 'wb');
$dishes = $db->query('SELECT dish_name, price, is_spicy FROM dishes');
while ($row = $dishes->fetch(PDO::FETCH_NUM)) {
    // записать в массив $row данные в виде строки
    // формата CSV. Функция fputcsv() добавляет
    // знак перевода строки в конце записываемой строки
    fputcsv($fh, $row);
}
fclose($fh);
```

Чтобы отправить веб-клиенту обратно страницу, состоящую только из данных в формате CSV, функции `fputcsv()` нужно дать команду направить данные в стандартный для PHP поток вывода вместо записи в файл. Необходимо также вызвать встроенную в PHP функцию `header()`, чтобы уведомить веб-клиента, что ему предполагается передать документ формата CSV, а не HTML. В примере 9.12 показано, каким образом функция `header()` вызывается с соответствующими аргументами.

#### Пример 9.12. Смена типа страницы на CSV

```php
// уведомить веб-клиента, что ему предполагается передать
// файл формата CSV
header('Content-Type: text/csv');

// уведомить веб-клиента, что содержимое файла формата CSV
// следует просматривать в отдельной программе
header('Content-Disposition: attachment; filename="dishes.csv"');
```

В примере 9.13 показана законченная программа, посылающая правильный заголовок в формате CSV, извлекающая строки из таблицы базы данных и выводящая их на экран. Результат, выводимый из этой программы, может быть непосредственно загружен в программу электронных таблиц из веб-браузера пользователя.

---

### Правая страница (страница 243)

#### Пример 9.13. Отправка файла формата CSV в браузер

```php
try {
    $db = new PDO('sqlite:/tmp/restaurant.db');
} catch (Exception $e) {
    print "Couldn't connect to database: " . $e->getMessage();
    exit();
}

// уведомить веб-клиента, что ему передается файл формата CSV
// под названием dishes.csv
header('Content-Type: text/csv');
header('Content-Disposition: attachment; filename="dishes.csv"');

// открыть файл с дескриптором потока вывода
$fh = fopen('php://output', 'wb');

// извлечь информацию из таблицы базы данных и
// вывести ее на экран
$dishes = $db->query('SELECT dish_name, price, is_spicy FROM dishes');
while ($row = $dishes->fetch(PDO::FETCH_NUM)) {
    fputcsv($fh, $row);
}
```

В коде из примера 9.13 в качестве аргумента функции `fputcsv()` передается `php://output` — встроенный дескриптор файла, специально предназначенный для отправки данных туда же, куда они выводятся по команде `print`. Чтобы сформировать более сложные электронные таблицы, включающие в себя формулы, изображения и средства форматирования, следует воспользоваться пакетом PHPExcel (https://packagist.org/packages/phpoffice/phpexcel) из библиотечного набора PHPOffice.

Подробнее о том, как пользоваться пакетами, речь пойдет в главе 16.

---

### Проверка полномочий доступа к файлам

Как упоминалось в начале этой главы, в программах на PHP можно организовать только чтение и запись файлов, если интерпретатор PHP разрешает это делать. Но при этом нельзя слепо полагаться на сообщения об ошибках, чтобы выяснить полномочия на доступ к файлу. В языке PHP предоставляются функции, с помощью которых можно определить полномочия отдельных программ.

---

### Нижние метки страниц
- **Левая страница:** 242 | Глава 9. Манипулирование файлами
- **Правая страница:** Проверка полномочий доступа к файлам | 243

---

Текст распознан максимально точно. Если есть вопросы или необходимы уточнения, сообщите!
query = 'распознай весь текст со скриншота максимально точно'
filepath = './assets/doc_mid.jpg'
model = "qwen/qwen2.5-vl-32b-instruct"
show_image(filepath)

png

res = response_img(query, filepath, model)
print(res['choices'][0]['message']['content'])
### Распознанный текст с изображения:

---

**ПРОТОКОЛ РАЗНОГЛАСИЙ**

к проекту типового договора поставки промышленных товаров № 85/5 от «25» января 2024 г., предложенного Поставщиком к заключению

#### Таганрог

Общество с ограниченной ответственностью «________», именуемое в дальнейшем «Поставщик», в лице Генерального Директора __________, действующего на основании Устава, с одной стороны, и ООО «________», в лице Представителя по доверенности __________, именуемое в дальнейшем «Покупатель», с другой стороны, при обобщенном упоминании — «Стороны», рассмотрели проект типового договора поставки, предложенного Поставщиком к заключению на 2024 год. Стороны подняли настолько противоразличий к нему, на нижеследующих условиях:

| № п/п | Редакция в соответствии с типовым Договором поставки, предложенного Поставщиком к подписанию Сторонами | Редакция, согласованная Сторонами, как окончательный текст Договора поставки |
| --- | --- | --- |
| 1 | Дополнить Раздел 1 «Предмет договора» следующим пунктом: | Пунктом следующего содержания: |
|  |  | 1.5. Квартальный ассортимент, количество, цена, общая стоимость, условия и порядок поставки и оплаты товара указываются в Завках Покупателя, являющихся Приложениями к настоящему Договору, которые подписывается Сторонами на каждую поставку партии товара и является неотъемлемой частью настоящего Договора. |
| 2 | Дополнить Раздел 1 «Предмет договора» следующим пунктом: | Пунктом следующего содержания: |
|  |  | 1.6. Согласованные и подписанные Сторонами Завки Покупателя к настоящему Договору, являются обязательными для исполнения Сторонами. |
| 3 | Дополнить Раздел 1 «Предмет договора» следующим пунктом: | Пунктом следующего содержания: |
|  |  | 1.7. Завки Покупателя формируются Поставщиком в течение 2-3 (Двух) рабочих дней на основании поступивших заказов от Покупателя. Завка также считается принятой Поставщиком, если в течение 24 часов с момента получения заказа Покупателя, Поставщик не уведомил Покупателя об отказе выполнить полностью или частично, либо приступил к ее исполнению. При отсутствии подтверждения получения заказа от Поставщика дата, получения соответствующего заказа считается дата его направления Поставщику. |
| 4 | Дополнить Раздел 1 «Предмет договора» следующим пунктом: | Пунктом следующего содержания: |
|  |  | 1.8. Заказ, направленный к обязательному исполнению. Отказ Поставщика от исполнения Заказа Покупателя является обоснованным только в случае наступления обстоятельств непреодолимой силы. |
| 5 | Дополнить Раздел 1 «Предмет договора» следующим пунктом: | Пунктом следующего содержания: |
|  |  | 1.9. При первой поставке товара Покупателю, Поставщик обязан предоставить надлежащим образом заверенную копию подписанного уполномоченными представителями Сторон и скрепленного печатями Сторон Приложения №1 (Спецификации/Прайс-лист) к настоящему Договору. При согласовании Сторонами изменений в ранее подписанное ими Приложение №1 путем оформления его в новой редакции (либо путем приложения к нему Спецификаций/Прайс-листов с указанием их порядкового номера, указание Приложения), Поставщик обязуется предоставить Покупателю надлежащим образом заверенную копию новой редакции Приложения №1 и/или копию(и) документа (документов) дополняющего(их) соответствующее Приложение одновременно с первой поставкой товара, соответствующего внесенным изменениям. |
| 6 | Пункт 2.2 - по тексту представленного Поставщиком проекта Договора поставки: | Изложить в следующей редакции: |
|  | 2.2. Поставщик обязуется поставить Товары в комплекте с относящейся к ним документацией, необходимой для осуществления торговли Товарами, в том числе с заверенными надлежащим образом | 2.2. Поставщик обязуется поставить Товары в комплекте с необходимой для осуществления торговли Товарами на территории Российской Федерации, в том числе: с заверенными надлежащим образом сертификатами соответствия, качества, гигиеническими сертификатами, Удостоверениями качества, декларациями. Состав необходимой документации определяется для каждой группы Товаров в отдельности в соответствии с требованиями действующего законодательства Российской Федерации, указанная документация должна быть на русском языке. К моменту |

---

### Объяснение:
1. **Структура документа**: Это протокол разногласий к проекту типового договора поставки промышленных товаров. Документ включает предложения по корректировке текста договора, предложенного Поставщиком.
2. **Участники**: Стороны — Поставщик (ООО) и Покупатель (ООО). Документ ведется в Таганроге.
3. **Основные изменения**:
   - Добавление пунктов в Раздел 1 «Предмет договора».
   - Уточнение условий поставки, включая:
     - Формирование Завок Покупателя.
     - Обязательность исполнения Заказов.
     - Порядок предоставления документации (включая сертификаты и декларации).
   - Изменение текста пункта 2.2 для соответствия требованиям торговли на территории РФ.

### Примечание:
Текст распознан максимально точно, но возможны незначительные опечатки или нечитаемые символы в оригинальном изображении.
query = 'переведи текст изображения на русский язык. заполни json с метаданными о изображении'
filepath = './assets/tweet.jpg'
model = "qwen/qwen2.5-vl-32b-instruct"
show_image(filepath)

png

res = response_img(query, filepath, model)
print(res['choices'][0]['message']['content'])
### Перевод текста на русский язык:

Я отказался от повышения по службе этой недели.

Мой босс был шокирован, не понимая, почему я отвергаю повышение на 30% после многих лет тяжелой работы и поздних ночей. Я объяснил, что боюсь, что зарабатывание столько денег может заставить меня потерять мотивацию и перестать работать так усердно.

В момент просветления он предложил вместо этого сократить зарплату на 20%, что я гордо принял. Я был рад помочь улучшить финансовые показатели компании и сохранить свою неуклонную приверженность посредственности.

Помните, друзья из LinkedIn, настоящий успех заключается в принятии красоты стагнации и никогда не становиться слишком финансово стабильным.

Подписывайтесь на меня за более полезными советами по карьере

#HumbleBrag #CareerGoals #Motivation #CorporateMartyr #LivingTheDream

---

### JSON с метаданными о изображении:

```json
{
  "author": "Alexander Cohen",
  "platform": "LinkedIn",
  "post_time": "now",
  "content": "Я отказался от повышения по службе этой недели. Мой босс был шокирован, не понимая, почему я отвергаю повышение на 30% после многих лет тяжелой работы и поздних ночей. Я объяснил, что боюсь, что зарабатывание столько денег может заставить меня потерять мотивацию и перестать работать так усердно. В момент просветления он предложил вместо этого сократить зарплату на 20%, что я гордо принял. Я был рад помочь улучшить финансовые показатели компании и сохранить свою неуклонную приверженность посредственности. Помните, друзья из LinkedIn, настоящий успех заключается в принятии красоты стагнации и никогда не становиться слишком финансово стабильным. Подписывайтесь на меня за более полезными советами по карьере",
  "hashtags": [
    "#HumbleBrag",
    "#CareerGoals",
    "#Motivation",
    "#CorporateMartyr",
    "#LivingTheDream"
  ],
  "interactions": {
    "likes": 0,
    "comments": 0,
    "shares": 0,
    "impressions": 4
  },
  "analytics": "Доступна"
}
```

### Объяснение:
1. **author**: Имя автора поста.
2. **platform**: Платформа, на которой опубликован пост (LinkedIn).
3. **post_time**: Время публикации (указано как "now").
4. **content**: Текст поста на русском языке.
5. **hashtags**: Хэштеги, использованные в посте.
6. **interactions**: Метрики взаимодействия с постом (лайки, комментарии, репосты, просмотры).
7. **analytics**: Указание на доступность аналитики по посту.

Приведем пример анализа фото, например с камеры видеонаблюдения.

query = 'что происходит на изображении?'
filepath = './assets/fight.jpg'
model = "qwen/qwen2.5-vl-32b-instruct"
show_image(filepath)

png

res = response_img(query, filepath, model)
print(res['choices'][0]['message']['content'])
На изображении происходит конфликт или нападение на улице. Ситуация выглядит следующим образом:

1. **Лежащий человек**: На земле лежит мужчина, который, похоже, находится в состоянии беспомощности или травмирован. Он лежит на боку, и его поза указывает на то, что он может быть связан или удерживается.

2. **Действия нападающих**: Над лежащим человеком стоят несколько человек, которые, вероятно, нападают или удерживают его. Один из них наклонился над ним, возможно, причиняя ему физическое насилие или связывая его.

3. **Свидетели или участники**: В группе людей, окружающих лежащего, есть несколько человек, которые могут быть нападающими или свидетелями. Один из них стоит рядом, держа руки на поясе, что может указывать на его участие в происходящем.

4. **Место действия**: Сцена происходит на улице, возможно, на пешеходном переходе или вблизи дороги. В фоне видны фонари, машины и светофоры, что указывает на городскую среду.

5. **Обстановка**: Атмосфера напряженная, и ситуация выглядит как нападение или агрессивное нападение на человека. Люди, окружающие лежащего, одеты в повседневную одежду, что делает их похожими на обычных прохожих или участников конфликта.

### Вывод:
На изображении происходит нападение или агрессивное нападение на человека, который находится на земле. Ситуация требует немедленного вмешательства правоохранительных органов или других заинтересованных лиц для предотвращения дальнейшего насилия.
query = 'изображение отражает драку или сцену насилиля или несчастный случай? Отвечай только да или нет'
filepath = './assets/fight.jpg'
model = "qwen/qwen2.5-vl-32b-instruct"
res = response_img(query, filepath, model)
print(res['choices'][0]['message']['content'])
Да.
query = 'посчитай количество людей на изображении. выведи только число в качестве ответа'
filepath = './assets/fight.jpg'
model = "qwen/qwen2.5-vl-32b-instruct"
res = response_img(query, filepath, model)
print(res['choices'][0]['message']['content'])
6

Как видим модель хорошо отработала все предложенные кейсы и вы можете использовать ее в работе. Данная модель содержит 72 млрд. параметров.

Более маленькие модели (7-10 млрд. параметров) отрабатывают существенно хуже и не применимы для сложных кейсов.

Средние модели 20-35 млрд параметров отрабатывают хуже данной на текущий момент (01.08.2025) но в целом тоже могут использоваться для решения практических задач.