<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Любовь Авдеева</title>
    <description>The latest articles on DEV Community by Любовь Авдеева (@strelok25dev).</description>
    <link>https://dev.to/strelok25dev</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4092083%2F44ad8a34-c4f0-4c56-b651-08be195fe431.jpg</url>
      <title>DEV Community: Любовь Авдеева</title>
      <link>https://dev.to/strelok25dev</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/strelok25dev"/>
    <language>en</language>
    <item>
      <title>How I Built a Reliable LLM Pipeline for Ad Creative Evaluation (with Strict Pydantic Contracts)</title>
      <dc:creator>Любовь Авдеева</dc:creator>
      <pubDate>Tue, 25 Aug 2026 14:02:13 +0000</pubDate>
      <link>https://dev.to/strelok25dev/how-i-built-a-reliable-llm-pipeline-for-ad-creative-evaluation-with-strict-pydantic-contracts-376j</link>
      <guid>https://dev.to/strelok25dev/how-i-built-a-reliable-llm-pipeline-for-ad-creative-evaluation-with-strict-pydantic-contracts-376j</guid>
      <description>&lt;p&gt;Art directors routinely spend 20–40 minutes per creative just checking brand guidelines, mandatory elements, and forbidden techniques.&lt;br&gt;&lt;br&gt;
I wanted to automate the first-pass review — without turning it into another unreliable ChatGPT wrapper.&lt;/p&gt;

&lt;p&gt;So I built &lt;strong&gt;CreativeAudit&lt;/strong&gt;: a production-oriented pipeline that evaluates ad creatives against a brief and returns a clear &lt;code&gt;PASS / NEEDS_REVISION / FAIL&lt;/code&gt; verdict.&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/strelok25-dev" rel="noopener noreferrer"&gt;
        strelok25-dev
      &lt;/a&gt; / &lt;a href="https://github.com/strelok25-dev/llm-creative-evaluator" rel="noopener noreferrer"&gt;
        llm-creative-evaluator
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Automated LLM pipeline for creative evaluation with Pydantic validation
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;CreativeAudit&lt;/h1&gt;
&lt;/div&gt;
&lt;p&gt;&lt;strong&gt;Automated ad creative evaluation against a brief using a local LLM.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;CreativeAudit takes a brief + creatives, builds a precise prompt, gets a structured evaluation from a language model, strictly validates the response, and returns a clear verdict (&lt;strong&gt;PASS / NEEDS_REVISION / FAIL&lt;/strong&gt;), weighted score, and explanation.&lt;/p&gt;
&lt;p&gt;This is not "just another AI chat". It is a production-oriented first-pass review pipeline: the machine catches routine and critical violations so humans only need to look at the borderline cases.&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Interface&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/strelok25-dev/llm-creative-evaluator/docs/screen_dashboard.jpg"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Fstrelok25-dev%2Fllm-creative-evaluator%2FHEAD%2Fdocs%2Fscreen_dashboard.jpg" alt="CreativeAudit Dashboard"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Campaign summary metrics, creative cards with verdict, detailed scores and model explanation. Smart-input mode converts free-form text into structured JSON with human review before evaluation starts.&lt;/em&gt;&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;The Problem&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;Manual first-pass creative review is slow, expensive and inconsistent:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;An art director spends 20–40 minutes per creative checking mandatory elements, forbidden techniques and tone of voice.&lt;/li&gt;
&lt;li&gt;A campaign with 10–30 creatives turns into hours of work that does not scale.&lt;/li&gt;
&lt;li&gt;…&lt;/li&gt;
&lt;/ul&gt;&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/strelok25-dev/llm-creative-evaluator" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;


&lt;h2&gt;
  
  
  The Real Problem with "Just Ask the LLM"
&lt;/h2&gt;

&lt;p&gt;A plain chat with an LLM has several fatal flaws for this use case:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Responses are inconsistent in format&lt;/li&gt;
&lt;li&gt;The model frequently forgets rules or hallucinates structure&lt;/li&gt;
&lt;li&gt;You get free-form text instead of machine-readable output&lt;/li&gt;
&lt;li&gt;Client data goes to an external cloud&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For any real workflow this is unacceptable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Design Goals
&lt;/h2&gt;

&lt;p&gt;I set a few hard requirements:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Strict output contracts&lt;/strong&gt; — invalid responses must never become scores&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Local inference&lt;/strong&gt; — no client data leaves the machine&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Prompts as code&lt;/strong&gt; — versioned and editable independently of the app&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Binary compliance&lt;/strong&gt; for critical rules (a creative either violates the brand book or it doesn’t)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Human-in-the-loop&lt;/strong&gt; on free-form input&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Architecture Overview
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Brief + Creatives → Jinja2 Prompt → Local LLM (Ollama)
                                      ↓
                               Pydantic Validation
                                      ↓
                          Score + Verdict + Feedback

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Architecture diagram showing the flow from Brief and Creatives through Jinja2 Prompt and Ollama, followed by Pydantic Validation, resulting in Score, Verdict, and Feedback&lt;/p&gt;

&lt;p&gt;Key components:&lt;/p&gt;

&lt;p&gt;app/schemas.py — strict Pydantic v2 contracts&lt;/p&gt;

&lt;p&gt;prompts/*.j2 — prompts treated as versioned code&lt;/p&gt;

&lt;p&gt;app/main.py — orchestration (prompt assembly, LLM call, validation, scoring)&lt;/p&gt;

&lt;p&gt;demo/streamlit_app.py — thin UI layer&lt;/p&gt;

&lt;p&gt;The business logic is completely separated from the interface. You can call it from Streamlit, CLI, or any other service.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key Engineering Decisions
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Pydantic as a Hard Contract&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The model is non-deterministic.&lt;br&gt;
 I treat the Pydantic schema as a hard boundary: if the response doesn’t match the schema, it is rejected as an error — it never becomes a fake score.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Binary Scale for Critical Compliance&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For brand-book violations I use a binary 0 or 10 score.&lt;br&gt;
 There is no “slightly violated”. This removes a lot of model subjectivity on the highest-risk criterion.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Prompts as Code (Jinja2)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Prompts live in separate .j2 files.&lt;br&gt;
 This makes them versionable, reviewable, and easy to A/B test without touching application code.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Local Inference with Ollama&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Everything runs locally.&lt;br&gt;
 Switching between qwen2.5:7b, qwen2.5:14b or llama3.2 is a single config change.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Smart Input Mode (Human-in-the-Loop)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Managers rarely provide clean JSON.&lt;br&gt;
 They paste chat fragments and rough descriptions.&lt;br&gt;
 So the pipeline first uses an LLM to extract structured data, shows the result to the user for correction, and only then runs the evaluation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Scoring Model
&lt;/h2&gt;

&lt;p&gt;Each creative receives three scores:&lt;/p&gt;

&lt;p&gt;brand_alignment (1–10)&lt;/p&gt;

&lt;p&gt;constraint_compliance (0 or 10)&lt;/p&gt;

&lt;p&gt;message_clarity (1–10)&lt;/p&gt;

&lt;h2&gt;
  
  
  Final score is a weighted combination:
&lt;/h2&gt;

&lt;p&gt;total = brand × 0.4 + compliance × 0.3 + clarity × 0.3&lt;/p&gt;

&lt;p&gt;The verdict is derived from the total score and critical failures.&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing Strategy
&lt;/h2&gt;

&lt;p&gt;I wrote 38 unit tests.&lt;br&gt;
 The external LLM is fully mocked, so tests are deterministic and run in under 2 seconds.&lt;br&gt;
 They cover schema boundaries, malformed responses, connection failures, and scoring logic.&lt;/p&gt;

&lt;h2&gt;
  
  
  Results &amp;amp; Lessons
&lt;/h2&gt;

&lt;p&gt;The biggest wins:&lt;/p&gt;

&lt;p&gt;First-pass review time dropped from tens of minutes to seconds&lt;/p&gt;

&lt;p&gt;Output became consistent and machine-readable&lt;/p&gt;

&lt;p&gt;Critical brand violations are much harder to miss&lt;/p&gt;

&lt;p&gt;The hardest part wasn’t the LLM call — it was designing the contracts and failure modes so the system stays reliable when the model behaves badly.&lt;/p&gt;

&lt;h2&gt;
  
  
  What’s Next
&lt;/h2&gt;

&lt;p&gt;Multimodal support (evaluate the actual layouts, not only text)&lt;/p&gt;

&lt;p&gt;Model benchmarks (accuracy vs speed)&lt;/p&gt;

&lt;p&gt;REST API for integration into existing workflows&lt;/p&gt;

&lt;p&gt;Evaluation history and campaign analytics&lt;/p&gt;

&lt;p&gt;If you’re building production LLM pipelines, I’d love to hear how you handle structured output and hallucination control.&lt;/p&gt;

&lt;p&gt;How do you currently deal with unreliable LLM responses in your projects?&lt;/p&gt;

</description>
      <category>python</category>
      <category>llm</category>
      <category>pydantic</category>
      <category>ai</category>
    </item>
    <item>
      <title>AI Engineer. No fluff, just working products.</title>
      <dc:creator>Любовь Авдеева</dc:creator>
      <pubDate>Mon, 24 Aug 2026 11:08:37 +0000</pubDate>
      <link>https://dev.to/strelok25dev/image-2jfl</link>
      <guid>https://dev.to/strelok25dev/image-2jfl</guid>
      <description></description>
    </item>
  </channel>
</rss>
