惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

腾讯CDC
N
Netflix TechBlog - Medium
aimingoo的专栏
aimingoo的专栏
P
Proofpoint News Feed
F
Fortinet All Blogs
大猫的无限游戏
大猫的无限游戏
I
InfoQ
V
V2EX
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
有赞技术团队
有赞技术团队
G
Google Developers Blog
L
LangChain Blog
博客园_首页
M
MIT News - Artificial intelligence
H
Hackread – Cybersecurity News, Data Breaches, AI and More
月光博客
月光博客
IT之家
IT之家
量子位
宝玉的分享
宝玉的分享
S
SegmentFault 最新的问题
Stack Overflow Blog
Stack Overflow Blog
V
Visual Studio Blog
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
雷峰网
雷峰网

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant
Pet Recommendation App
Tom Jacobson · 2026-05-05 · via DEV Community

Building an AI-Powered Dog Breed Recommender with Flask, Nyckel, and Google Gemini

Overview

This application takes a photo or image URL of a dog and passes it to an external API to determine the breed. Once the breed is identified, a custom prompt is sent to Google Gemini and the results are returned to the user as a tailored list of care recommendations for that specific breed.

Tech stack: Python, Flask, Nyckel API, Google Gemini 1.5 Flash, Docker


Screenshots

Architecture

The application follows a simple two-step pipeline:

User Input (image/URL + optional fields)
        ↓
Nyckel Dog Breed Classifier API
        ↓
Google Gemini 1.5 Flash (LLM)
        ↓
Results Page (breed + care recommendations)

Enter fullscreen mode Exit fullscreen mode

The Flask app exposes two routes:

  • / — renders the input form and handles POST submissions (Query view)
  • /results — renders the breed and recommendations (Results view)

Project Structure

├── app.py          # Flask entry point, URL routing
├── query.py        # Query view: image processing, API calls
├── results.py      # Results view: parses and renders recommendations
├── templates/
│   ├── layout.html
│   ├── query.html  # Input form
│   └── results.html
├── static/css/
├── requirements.txt
└── Dockerfile

Enter fullscreen mode Exit fullscreen mode


How It Works

1. Handling User Input

The Query view accepts two input types — a file upload or an image URL — along with optional fields for age, activity level, and free-text notes.

url = request.form['url']
image_file = request.files.get('image file')
age = request.form['age']
activity = request.form['activity_level']
additional_info = request.form['additional_info']

Enter fullscreen mode Exit fullscreen mode

Uploaded files are saved to static/uploads/ with a timestamped filename to avoid conflicts:

cleaned_filename = f"{int(time.time())}_{random.randint(0, 1000)}_user_upload.jpg"

Enter fullscreen mode Exit fullscreen mode

2. Breed Classification — Nyckel API

The app sends the image to Nyckel's pretrained dog breed identifier. File uploads are sent as multipart form data; URLs are sent as JSON.

classifier = 'https://www.nyckel.com/v1/functions/dog-breed-identifier/invoke'

# File upload
with open(filename, 'rb') as img:
    result = requests.post(classifier, files={"data": img})

# URL
result = requests.post(classifier, json={"data": url})

breed = json.loads(result.text)["labelName"]

Enter fullscreen mode Exit fullscreen mode

Nyckel returns a labelName field containing the identified breed, which is passed to Gemini.

3. Generating Recommendations — Google Gemini

The generate_recommendations method builds a prompt using the detected breed and any optional fields the user provided, then calls Gemini 1.5 Flash.

prompt = f"""I am thinking about adopting a new dog. The dog is an {breed}.
Could you provide me with a succinct bulleted list of care recommendations
for my new dog? Please provide breed specific advice."""

if age:
    prompt += f"The dog is {age} years old."
if activity:
    prompt += f"The dog has a {activity} level."
if additional_info:
    prompt += f"Some additional information to consider is as follows {additional_info}"

Enter fullscreen mode Exit fullscreen mode

A formatting instruction is appended to keep the output consistent for parsing:

formatting_parameters = "Do not bold anything."
response = model.generate_content(prompt + formatting_parameters)

Enter fullscreen mode Exit fullscreen mode

4. Parsing and Rendering Results

Gemini returns a markdown-style bulleted list. The Results view uses a regex split on single asterisks to convert it into a Python list for clean rendering:

recommendation_list = re.split(r"\*(?!\*)", recommendation)

Enter fullscreen mode Exit fullscreen mode

The results template then iterates over the list and renders each item as an <li> element.


Setup & Configuration

Prerequisites

  • Python 3.9+
  • A Google Gemini API key

Local Setup

# Create and activate a virtual environment
python3 -m venv venv
venv\Scripts\activate        # Windows
source venv/bin/activate     # Linux/macOS

# Install dependencies
pip install -r requirements.txt

# Set your API key
$env:GOOGLE_API_KEY = "your_key_here"   # Windows
export GOOGLE_API_KEY="your_key_here"   # Linux/macOS

# Run the app
python app.py

Enter fullscreen mode Exit fullscreen mode

The app runs on http://localhost:8080 by default.


Docker Deployment

The included Dockerfile uses a python:3.9-slim base image and exposes port 8080.

FROM python:3.9-slim
WORKDIR /app
COPY . /app
RUN pip install --no-cache-dir -r requirements.txt
EXPOSE 8080
CMD ["python", "app.py"]

Enter fullscreen mode Exit fullscreen mode

docker build -t pet-recommendations .
docker run -e GOOGLE_API_KEY=your_key_here -p 8080:8080 pet-recommendations

Enter fullscreen mode Exit fullscreen mode


Key Design Decisions

  • Nyckel for classification — Nyckel offers a pretrained dog breed classifier that is free to use. Rather than building and training a custom model, it made more sense to use a solution that already existed.
  • Prompt composition — Not every user will know their dog's age or activity level, and requiring that information would add unnecessary friction. Optional fields are only added to the prompt when provided, so the app works out of the box without any extra input.
  • Filename sanitization — Uploaded files are renamed with a timestamp and random integer to prevent collisions. Without this, two users uploading a file with the same name would overwrite each other's image.

What I Would Change

1. Async API Calls

The API calls currently run synchronously with no feedback to the user — the page is unresponsive while waiting for both Nyckel and Gemini to respond. I'd restructure this using async Flask to show a loading indicator while the requests are processing.

2. Input Validation on Image Uploads

The app currently accepts any file without checking whether it is actually an image. Validating the file's MIME type before passing it to the classifier would prevent the app from breaking on invalid input like a Word document. A more involved improvement would be adding a check for whether the photo actually contains a dog, rather than returning a best-guess breed for an unrelated image.

3. Uploaded File Cleanup

Every uploaded image is saved to static/uploads/ with nothing to remove them afterward. In production this would become a storage problem over time. A simple fix would be a scheduled job to clear the folder every few days, or clearing it automatically when the application shuts down.


Conclusion

This project was an exercise in combining multiple AI tools to solve a specific problem. Building it gave me hands-on experience integrating third-party APIs and presenting the results through a web interface.