mongodb://localhost:12404/?directConnection=true
python -m venv venvsource venv/bin/activate # Unix/Mac# hoặc venv\Scripts\activate cho Windows
requirements.txt
với:anthropic==0.47.2django-mongodb-backend==5.1.0b0python-dotenv==1.0.1voyageai==0.3.2
pip install -r requirements.txt
django-admin startproject cookbook --template https://github.com/mongodb-labs/django-mongodb-project/archive/refs/heads/5.0.x.zip
cookbook/settings.py
:DATABASES = { "default": django_mongodb_backend.parse_uri( "mongodb://localhost:12404/cookbook?directConnection=true" ),}
cookbook
) trong URI.cd cookbookpython manage.py runserver
recipes
python manage.py startapp recipes --template https://github.com/mongodb-labs/django-mongodb-app/archive/refs/heads/5.0.x.zip
recipes/models.py
:from django.db import modelsfrom django_mongodb_backend.fields import ArrayField, EmbeddedModelFieldfrom django_mongodb_backend.models import EmbeddedModelfrom django_mongodb_backend.managers import MongoManager
class Features(EmbeddedModel): preparation_time = models.CharField(max_length=100) complexity = models.CharField(max_length=100) prep_time = models.IntegerField() cuisine = models.CharField(max_length=100, null=True, blank=True)
class Recipe(models.Model): title = models.CharField(max_length=200) instructions = models.TextField(blank=True) features = EmbeddedModelField(Features, null=True, blank=True) ingredients = ArrayField(models.CharField(max_length=100), null=True, blank=True) embedding_ingredients = models.CharField(max_length=500, null=True, blank=True) voyage_embedding = models.JSONField(null=True, blank=True)
objects = MongoManager()
class Meta: db_table = "recipes" managed = False
def __str__(self): return f"Recipe {self.title}"
settings.py
, thêm vào:INSTALLED_APPS = [ "recipes.apps.RecipesConfig", "cookbook.apps.MongoAdminConfig", "cookbook.apps.MongoAuthConfig", "cookbook.apps.MongoContentTypesConfig", "django.contrib.sessions", "django.contrib.messages", "django.contrib.staticfiles",]
recipes/admin.py
:from django.contrib import adminfrom .models import Recipe
admin.site.register(Recipe)
python manage.py makemigrationspython manage.py migratepython manage.py createsuperuser
.env
.env
tại root:MONGO_URI=mongodb://localhost:12404/?directConnection=trueMONGO_DB=cookbookMONGO_COLLECTION=recipesVOYAGE_API_KEY=YOUR_VOYAGE_API_KEYANTHROPIC_API_KEY=YOUR_ANTHROPIC_API_KEY
import_json_recipes.py
:import osimport pymongoimport jsonfrom dotenv import load_dotenv
load_dotenv()
MONGO_URI = os.getenv("MONGO_URI", "mongodb://localhost:12404/?directConnection=true")MONGO_DB = os.getenv("MONGO_DB", "cookbook")
try: client = pymongo.MongoClient(MONGO_URI) db = client[MONGO_DB]
with open("bigger_sample.json", "r") as f: recipes_data = json.load(f)
for recipe in recipes_data: recipe_doc = { "title": recipe["title"], "ingredients": recipe["ingredients"], "instructions": recipe["instructions"], "embedding_ingredients": recipe["embedding_ingredients"], "features": recipe["features"], } db.recipes.insert_one(recipe_doc) print(f"Inserted {len(recipes_data)} recipes into MongoDB")except Exception as e: print(f"Error: {e}")
python import_json_recipes.py
generate_embeddings.py
:import osimport timeimport pymongoimport voyageaifrom dotenv import load_dotenvfrom tqdm import tqdm
load_dotenv()
MONGO_URI = os.getenv("MONGO_URI", "mongodb://localhost:12404/?directConnection=true")MONGO_DB = os.getenv("MONGO_DB", "cookbook")
try: client = pymongo.MongoClient(MONGO_URI) db = client[MONGO_DB] collection = db["recipes"]
vo = voyageai.Client()
docs = list(collection.find({"voyage_embedding": {"$exists": False}})) print(f"Found {len(docs)} documents without Voyage embeddings")
for doc in tqdm(docs, desc="Adding embeddings"): content = f"{doc['title']}. Ingredients: {doc['embedding_ingredients']}" try: embedding_result = vo.embed( [content], model="voyage-lite-01-instruct", input_type="document", ) embedding = embedding_result.embeddings[0] collection.update_one( {"_id": doc["_id"]}, {"$set": {"voyage_embedding": embedding}} ) time.sleep(25) # Để tránh vượt giới hạn API except Exception as e: print(f"Error generating embedding for recipe '{doc['title']}': {e}")
print(f"Successfully added embeddings")except Exception as e: print(f"Error: {e}")
base.html
<!doctype html><html lang="en"><head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1" /> <title>{% block title %}Django MongoDB Backend Recipes{% endblock %}</title> <script src="https://unpkg.com/@tailwindcss/browser@4"></script> {% block extra_css %}{% endblock %}</head><body class="flex flex-col min-h-screen bg-gray-50"> <main class="container mx-auto px-4 py-6 flex-grow"> {% block content %} <h1 class="text-3xl font-bold underline">Hello world!</h1> {% endblock %} </main> <footer class="bg-gray-800 text-white py-6"> <div class="container mx-auto px-4"> <p>© {% now "Y" %} Django + MongoDB <3</p> </div> </footer></body></html>
index()
from django.shortcuts import render
def index(request): return render(request, "index.html", {"message": "Recipes App"})
index.html
:{% extends 'base.html' %}{% block content %} <h1 class="text-3xl font-bold text-center">{{ message }}</h1>{% endblock %}
def recipe_list(request): recipes = Recipe.objects.all().order_by("title")[:20] return render(request, "recipe_list.html", {"recipes": recipes})
recipe_list.html
:{% extends 'base.html' %}{% block content %}<h1 class="text-3xl font-bold mb-6">Alphabetical list of recipes</h1><ul class="list-disc pl-5"> {% for recipe in recipes %} <li class="mb-1">{{ recipe.title }}</li> {% endfor %}</ul>{% endblock %}
from bson import ObjectIdfrom bson.errors import InvalidIdfrom django.http import Http404from django.shortcuts import get_object_or_404
def recipe_detail(request, recipe_id): try: object_id = ObjectId(recipe_id) except InvalidId: raise Http404(f"Invalid recipe ID: {recipe_id}")
recipe = get_object_or_404(Recipe, id=object_id) return render(request, "recipe_detail.html", {"recipe": recipe})
recipe_detail.html
:{% extends 'base.html' %}{% block title %}{{ recipe.title }}{% endblock %}{% block content %}<div class="max-w-2xl mx-auto py-4"> <h1 class="text-xl font-semibold mb-3">{{ recipe.title }}</h1> <div class="mb-4"> <h2 class="text-md font-medium mb-1">Ingredients</h2> <ul class="pl-4"> {% for ingredient in recipe.ingredients %} <li>{{ ingredient }}</li> {% endfor %} </ul> </div> <div> <h2 class="text-md font-medium mb-1">Instructions</h2> <div class="pl-4">{{ recipe.instructions|linebreaks }}</div> </div></div>{% endblock %}
def recipe_statistics(request): pipeline = [ {"$project": {"_id": 1, "cuisine": "$features.cuisine"}}, {"$group": {"_id": "$cuisine", "count": {"$sum": 1}}}, {"$sort": {"count": -1}}, {"$project": {"_id": 1, "cuisine": {"$ifNull": ["$_id", "Unspecified"]}, "count": 1}}, ] stats = Recipe.objects.raw_aggregate(pipeline) return render(request, "statistics.html", {"cuisine_stats": list(stats)})
statistics.html
:{% extends 'base.html' %}{% block content %}<h1 class="text-2xl font-bold mb-6">Recipe Statistics</h1><div> <h2 class="text-xl font-semibold mb-4">Recipes by Cuisine</h2> {% if cuisine_stats %} <ul> {% for item in cuisine_stats %} <li>{{ item.cuisine }}: {{ item.count }} recipe{{ item.count|pluralize }}</li> {% endfor %} </ul> {% else %} <p>No cuisine data available.</p> {% endif %}</div>{% endblock %}
title
, ingredients
, instructions
.voyage_embedding
với số chiều 1024, sử dụng độ đo cosine.import voyageai
def perform_vector_search(query_text, limit=10, num_candidates=None): if num_candidates is None: num_candidates = limit * 3 try: vo = voyageai.Client() query_embedding = vo.embed( [query_text], model="voyage-lite-01-instruct", input_type="query" ).embeddings[0]
results = Recipe.objects.raw_aggregate([ { "$vectorSearch": { "index": "recipe_vector_index", "path": "voyage_embedding", "queryVector": query_embedding, "numCandidates": num_candidates, "limit": limit, } }, { "$project": { "_id": 1, "title": 1, "ingredients": 1, "instructions": 1, "features": 1, "score": {"$meta": "vectorSearchScore"}, } }, ])
recipes = [] for recipe in results: recipes.append({ "id": str(recipe.id), "title": recipe.title, "ingredients": recipe.ingredients, "instructions": getattr(recipe, "instructions", ""), "features": getattr(recipe, "features", {}), "similarity_score": getattr(recipe, "score", 0), }) return recipes except Exception as e: print(f"Error in vector search: {str(e)}") return []
def ingredient_vector_search(request): query = request.GET.get("query", "") results = [] if query: ingredient_query = f"Ingredients: {query}" results = perform_vector_search(ingredient_query, limit=10) return render(request, "vector_search.html", {"query": query, "results": results})
vector_search.html
:{% extends 'base.html' %}{% block content %}<h1>Search Recipes by Ingredients</h1><form method="GET" action="{% url 'ingredient_search' %}"> <input type="text" name="query" placeholder="chicken, garlic, lemon" value="{{ query }}"> <button type="submit">Search</button></form>{% if query %} <h2>Results for "{{ query }}"</h2> {% if results %} {% for recipe in results %} <div> <h3>{{ recipe.title }}</h3> <p>Similarity: {{ recipe.similarity_score|floatformat:2 }}</p> <ul> {% for ing in recipe.ingredients %} <li>{{ ing }}</li> {% endfor %} </ul> <a href="{% url 'recipe_detail' recipe.id %}">View full recipe</a> </div> {% endfor %} {% else %} <p>No recipes found matching your ingredients.</p> {% endif %}{% endif %}{% endblock %}
from anthropic import Anthropicimport jsonimport os
ANTHROPIC_API_KEY = os.getenv("ANTHROPIC_API_KEY")
def get_claude_suggestions(user_ingredients, similar_recipes, max_suggestions=4): client = Anthropic(api_key=ANTHROPIC_API_KEY) prompt = f"""I have these ingredients: {", ".join(user_ingredients)}.Based on these ingredients, I need {max_suggestions} meal suggestions. Similar recipes: {json.dumps(similar_recipes, indent=2)}.For each suggestion, provide:1. Recipe name2. Ingredients used3. Possible substitutions4. Brief cooking instructions5. Difficulty levelBe concise and practical.""" response = client.messages.create( model="claude-3-haiku-20240307", max_tokens=1500, temperature=0.7, system="You are a helpful cooking assistant.", messages=[{"role": "user", "content": prompt}], ) suggestions_text = response.content[0].text # Parse the suggestions text into list raw_suggestions = [] current = "" for line in suggestions_text.split("\n"): if line.strip() and (line.strip()[0].isdigit() and line.strip()[1:3] in [". ", ") "]): if current: raw_suggestions.append(current.strip()) current = line else: current += "\n" + line if current: raw_suggestions.append(current.strip()) return raw_suggestions[:max_suggestions]
def ai_meal_suggestions(request): query = request.GET.get("ingredients", "") suggestions = [] error_message = None if query: try: ingredients_list = [i.strip() for i in query.split(",") if i.strip()] similar_recipes = perform_vector_search(f"Ingredients: {', '.join(ingredients_list)}", limit=10) if similar_recipes: recipes_data = [ {"title": r["title"], "ingredients": r["ingredients"], "score": r["similarity_score"], "id": r["id"]} for r in similar_recipes ] suggestions = get_claude_suggestions(ingredients_list, recipes_data) else: error_message = "No similar recipes found." except Exception as e: error_message = f"An error occurred: {e}" return render(request, "ai_suggestions.html", { "ingredients": query, "suggestions": suggestions, "error_message": error_message, })
ai_suggestions.html
:{% extends 'base.html' %}{% block content %}<h1>AI Meal Suggestions</h1><form method="GET" action="{% url 'ai_meal_suggestions' %}"> <textarea name="ingredients" placeholder="e.g., chicken, garlic">{{ ingredients }}</textarea> <button type="submit">Get Meal Suggestions</button></form>{% if error_message %} <div class="error">{{ error_message }}</div>{% endif %}{% if suggestions %} <h2>Suggested Meals</h2> {% for suggestion in suggestions %} <div class="suggestion">{{ suggestion|linebreaks }}</div> {% endfor %} <p><small>AI-generated suggestions based on your ingredients and similar recipes.</small></p>{% endif %}{% endblock %}