Skip to content
vp.Ven Petrov
WorkWritingAboutAt a glance
← Projects & notes
Product-description comparison benchExperiment · 3 min read

Comparing one model call with separate vision and writing steps

A small product-description tool lets me inspect the visual analysis before the copy is written, change the model used for each step and compare request measurements.

Ven PetrovLocal comparison prototype

This is a small tool I built for comparing ways to turn product images into descriptions. It has a React interface and a Node backend, with one combined model call or separate vision and writing calls. Each phase can use a different provider and model.

The useful difference is that the two-phase path returns the visual description as well as the finished copy. I can inspect what the first model said about the images before judging how the second model wrote about them.

Follow a detail through the two steps

Suppose a product is red but the finished description calls it blue. That is an illustrative example, not a recorded mistake from this project.

With only the final paragraph, it is hard to tell where the error entered. In the two-phase path, I can check the intermediate description. If that already says blue, the writing step received the wrong detail. If it says red and the final copy says blue, the detail changed during writing.

The implementation separates those requests:

Product images
  -> analyzeImages(): visual description
  -> formatDescription(): requested language, style and format
  -> final copy

analyzeImages() sends the images to a model that accepts visual input. formatDescription() receives the returned text; it doesn't inspect the images again. An error in the visual description can therefore carry into the final result. Splitting the calls makes that intermediate step visible, not automatically correct.

Keep the copy instructions out of the first request

The vision prompt leaves out free-form custom instructions and the requested output language. A controlled tone preset can still be included.

The writing prompt adds the language, tone, custom instructions and HTML or plain-text requirements. These are different jobs: describing the visible product and presenting that description in the requested form.

The single-call branch puts the images and those requirements into one request. Both routes remain available in the prototype. I haven't established that the extra call produces better copy, and leaving instructions out of one phase is not a guarantee against malicious instructions in the input.

Inspect the prompt as well as the paragraph

The newer two-phase API returns the system prompt, user prompt and response for both steps. It also returns provider/model choices, prompt-template versions, image count and each phase's response time and token usage. The newer single-call mode returns its combined prompts and totals; the older API has a smaller response without those prompt records.

That gives a comparison more context than a final paragraph alone. A changed template or model choice is part of the experiment, so it should travel with the result being compared.

Cost needs the same care. For OpenRouter, the code first checks for a returned usage cost:

if (result.provider === 'openrouter' && result.usage?.cost !== undefined) {
  return result.usage.cost;
}

When that isn't available, it estimates cost from input and output token counts using the configured price table. A provider-returned amount and an estimate have different origins. Current comparisons also need current prices; the saved configuration isn't a current price quote.

The local debug log retains input images, instructions and final descriptions for inspection. It is a simple JSON file, so it needs better handling before concurrent use. Its grouped provider statistics also repeat the whole request cost under each involved provider, so those groups should not be read as a phase-by-phase bill.

The comparison paths and measurements are implemented. What is still missing is a reviewed set of outputs from the same images, judged against the same product facts. Until that exists, this is a bench for asking which path works better, not evidence that one has won.

Project sources & checks
Implementation

The private image-description MVP contains a React/Vite frontend and Node/Express backend. llmProvider.js, promptBuilder.js and logger.js support the comparison paths.

Response scope

The newer API returns prompts; its two-phase mode returns intermediate analysis and per-phase time, token and cost fields. Its single-call branch returns combined prompts and totals. The legacy API returns description and metadata without prompts.

Example boundary

The product-color example explains how to read the two stages. It is illustrative, not a saved model response or a reported failure.

Unfinished evaluation

No matched-image quality result, SEO improvement, cost saving or production deployment has been verified. Configured prices are historical; grouped provider statistics attribute a full request cost to each involved provider.

Local logging limits

Debug logs retain inputs and final output in a JSON file using read-modify-write. This is not a durable concurrent trace store, and retained prompts do not guarantee identical model output.

On this page
Follow a detail through the two stepsKeep the copy instructions out of the first requestInspect the prompt as well as the paragraphSources & checks
ReactNode.jsOpenAIOpenRouterEvaluation

Connected to this

All writing
Between Us · Project story

Between Us: why I kept the first take and widened the shot

ctxforge · Project story

ctxforge: discovering what to build before coding

Ven PetrovSoftware engineer · Barcelona
GitHub ↗LinkedIn ↗Email ↗

Find a project or a note

17 results

Transfutur ERP · ExperienceERP for transport and logisticsAs the sole engineer, I built an ERP from zero for transport and logistics. It supported roughly €5–6 million in annual invoicing across two offices, and I kept it running for about a year.Financial review lab · Project storyExploring a financial report with Python and FastAPII started with one income change, then opened up the same 54-page report: products, regions, cash flow, core adjustments and a dataset whose figures lead back to the source.Crude-oil narrative tracker · Project storyCrude-oil research from global news and market dataI wanted help following global events that could matter for crude oil. The prototype combines news research with calculated market and position values, while keeping recent assessments separate from the longer numeric history.MNQ time-series research · ExperimentTime-series prediction experimentsAn exploration of high-resolution market data: choosing what to acquire, checking its limits, comparing time intervals and testing whether a prediction survived later data and the cost of acting on it.NQ/MNQ backtest engine · Technical noteA missing closing bar left the simulator’s short position openThe strategy reset for a new day while a shared counter still counted yesterday’s short. A small replay shows why closing the position has to happen before clearing its state.Small-cap edge research · Project storyTurning small-cap trading ideas into repeatable researchI asked Claude Code for a fresh research system that could test named price patterns, compare later periods and carry its methodology into new sessions. One candidate shows how features, entry timing and costs fit together.Gadgeon Europe applications · ExperienceConnected-device dashboards and mobile apps at Gadgeon EuropeI worked on Ionic and Angular applications that brought several APIs into dashboards for energy monitoring and connected-device control, with releases and maintenance across multiple client versions.ctxforge · Project storyctxforge: discovering what to build before codingA context discovery framework that asks an LLM to act like a senior engineering interviewer: understand the idea, uncover requirements and agree the scope before an agent starts building.Sociedades Inactivas enquiries · Technical noteSaving an enquiry before trying to send its emailOn the Sociedades Inactivas catalogue, a visitor can ask about a company or send a general enquiry. The form separates saving that request from delivering its notification, including when the visitor retries.Astro local-services website · Project storyA local-services site assembled from city, service and FAQ recordsThe Astro site describes flooring and other local services across different cities. Shared templates check availability, read the matching content and select one FAQ set for both visitors and structured data.3D Spaces website migration · Project storyMoving 3D Spaces to Next.js while its email stayed on the old serverI rebuilt the virtual-tour website with AI coding collaborators, preserving its content and URLs. The cutover also had to protect the mailbox, enquiry flow and existing analytics history.Capture CRM · Project storyConnecting classified listings to contacts in a small CRMA Chrome extension and Next.js dashboard bring classified listings, platform profiles and contact history together. Saving a listing again can add the person who was missing from an earlier import.Product-description comparison bench · ExperimentComparing one model call with separate vision and writing stepsA small product-description tool lets me inspect the visual analysis before the copy is written, change the model used for each step and compare request measurements.Between Us · Project storyBetween Us: why I kept the first take and widened the shotI compared dialogue takes, asked for wider shots and more physical acting, then changed the workflow when its studio limited the scenes I wanted to make.Restaurant-service voice agent · ExperimentDesigning a restaurant-service call around the real offerI worked through a Spanish outbound agent prompt, corrected a free visit to the actual paid service and compared how two drafts handled transfers and bookings.Vapi and Telnyx voice integration · Technical noteThe Vapi agent could speak, but it could not hear me clearlyMy Telnyx number connected to the assistant, but the caller audio was distorted. Three codec tests recovered the connection without fixing that original problem.Warloop: Echoes of Solmara · Project storyWarloop: turning generated character art into a playable prototypeGood-looking character images became tiny figures, a backwards walk and misplaced bosses in the game. I worked through the gap between generated art and playable behavior.
esc to close · Tab to move through results