Spaces:
Sleeping
Sleeping
adding api
Browse files- Dockerfile +23 -0
- embed_data.py +124 -0
- main.py +208 -0
Dockerfile
ADDED
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
FROM python:3.9-slim
|
2 |
+
|
3 |
+
RUN useradd -m -u 1000 user
|
4 |
+
USER user
|
5 |
+
ENV PATH="/home/user/.local/bin:$PATH"
|
6 |
+
|
7 |
+
WORKDIR /app
|
8 |
+
|
9 |
+
# Copy requirements first to leverage Docker cache
|
10 |
+
COPY --chown=user ./requirements.txt requirements.txt
|
11 |
+
RUN pip install --no-cache-dir --upgrade -r requirements.txt
|
12 |
+
|
13 |
+
# Copy the application code
|
14 |
+
COPY --chown=user . /app
|
15 |
+
|
16 |
+
# Set environment variables
|
17 |
+
ENV PORT=8000
|
18 |
+
|
19 |
+
# Expose the port
|
20 |
+
EXPOSE ${PORT}
|
21 |
+
|
22 |
+
# Run the application
|
23 |
+
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "7860"]
|
embed_data.py
ADDED
@@ -0,0 +1,124 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import json
|
2 |
+
import os
|
3 |
+
from pinecone import Pinecone, ServerlessSpec
|
4 |
+
import numpy as np
|
5 |
+
from openai import OpenAI
|
6 |
+
|
7 |
+
# Load environment variables
|
8 |
+
from dotenv import load_dotenv
|
9 |
+
load_dotenv()
|
10 |
+
|
11 |
+
# Get API keys from environment variables
|
12 |
+
PINECONE_API_KEY = os.getenv('PINECONE_API_KEY')
|
13 |
+
OPENAI_API_KEY = os.getenv('OPENAI_API_KEY')
|
14 |
+
|
15 |
+
if not PINECONE_API_KEY:
|
16 |
+
raise ValueError("PINECONE_API_KEY environment variable not set")
|
17 |
+
if not OPENAI_API_KEY:
|
18 |
+
raise ValueError("OPENAI_API_KEY environment variable not set")
|
19 |
+
|
20 |
+
# Initialize OpenAI client
|
21 |
+
openai_client = OpenAI(api_key=OPENAI_API_KEY)
|
22 |
+
|
23 |
+
# Define the embedding model using OpenAI
|
24 |
+
class OpenAIEmbedder:
|
25 |
+
def __init__(self, model_name="text-embedding-3-small"):
|
26 |
+
self.model_name = model_name
|
27 |
+
self.client = openai_client
|
28 |
+
self.embedding_dimension = 1536 # Dimension of text-embedding-3-small
|
29 |
+
|
30 |
+
def encode(self, texts):
|
31 |
+
if isinstance(texts, str):
|
32 |
+
texts = [texts]
|
33 |
+
|
34 |
+
# Get embeddings from OpenAI
|
35 |
+
response = self.client.embeddings.create(
|
36 |
+
input=texts,
|
37 |
+
model=self.model_name
|
38 |
+
)
|
39 |
+
|
40 |
+
# Extract embeddings from response
|
41 |
+
embeddings = [item.embedding for item in response.data]
|
42 |
+
return np.array(embeddings)
|
43 |
+
|
44 |
+
# Initialize Pinecone client
|
45 |
+
def initialize_pinecone():
|
46 |
+
pc = Pinecone(api_key=PINECONE_API_KEY)
|
47 |
+
|
48 |
+
# Define index name
|
49 |
+
index_name = "ebikes-search"
|
50 |
+
|
51 |
+
# Check if index already exists
|
52 |
+
existing_indexes = pc.list_indexes().names()
|
53 |
+
if index_name not in existing_indexes:
|
54 |
+
# Create index with 1536 dimensions (matches text-embedding-3-small)
|
55 |
+
pc.create_index(
|
56 |
+
name=index_name,
|
57 |
+
dimension=1536,
|
58 |
+
metric="cosine",
|
59 |
+
spec=ServerlessSpec(cloud="aws", region="us-west-2")
|
60 |
+
)
|
61 |
+
print(f"Created new index: {index_name}")
|
62 |
+
|
63 |
+
# Connect to the index
|
64 |
+
index = pc.Index(index_name)
|
65 |
+
return index
|
66 |
+
|
67 |
+
# Load the e-bikes data
|
68 |
+
def load_ebikes_data(file_path="ebikes_data.json"):
|
69 |
+
with open(file_path, 'r') as f:
|
70 |
+
data = json.load(f)
|
71 |
+
return data.get('ebikes', [])
|
72 |
+
|
73 |
+
# Create embeddings and upload to Pinecone
|
74 |
+
def create_and_upload_embeddings(ebikes_data, encoder, pinecone_index):
|
75 |
+
# Prepare data for indexing
|
76 |
+
ids = []
|
77 |
+
descriptions = []
|
78 |
+
metadata = []
|
79 |
+
|
80 |
+
for bike in ebikes_data:
|
81 |
+
ids.append(bike['id'])
|
82 |
+
descriptions.append(bike['description'])
|
83 |
+
metadata.append({
|
84 |
+
'id': bike['id'],
|
85 |
+
'name': bike['name'],
|
86 |
+
'type': bike['type'],
|
87 |
+
'description': bike['description'][:100] + "..." # Adding truncated description to metadata
|
88 |
+
})
|
89 |
+
|
90 |
+
# Create embeddings
|
91 |
+
embeddings = encoder.encode(descriptions)
|
92 |
+
|
93 |
+
# Prepare vectors for Pinecone
|
94 |
+
vectors_to_upsert = []
|
95 |
+
for i in range(len(ids)):
|
96 |
+
vector = {
|
97 |
+
'id': ids[i],
|
98 |
+
'values': embeddings[i].tolist(),
|
99 |
+
'metadata': metadata[i]
|
100 |
+
}
|
101 |
+
vectors_to_upsert.append(vector)
|
102 |
+
|
103 |
+
# Upsert vectors to Pinecone
|
104 |
+
pinecone_index.upsert(vectors=vectors_to_upsert)
|
105 |
+
print(f"Uploaded {len(vectors_to_upsert)} embeddings to Pinecone")
|
106 |
+
|
107 |
+
# Main function to run the embedding creation process
|
108 |
+
def main():
|
109 |
+
# Initialize the embedding model
|
110 |
+
encoder = OpenAIEmbedder()
|
111 |
+
|
112 |
+
# Initialize Pinecone
|
113 |
+
pinecone_index = initialize_pinecone()
|
114 |
+
|
115 |
+
# Load ebikes data
|
116 |
+
ebikes_data = load_ebikes_data()
|
117 |
+
|
118 |
+
# Create and upload embeddings
|
119 |
+
create_and_upload_embeddings(ebikes_data, encoder, pinecone_index)
|
120 |
+
|
121 |
+
print("Embedding creation and upload completed successfully!")
|
122 |
+
|
123 |
+
if __name__ == "__main__":
|
124 |
+
main()
|
main.py
ADDED
@@ -0,0 +1,208 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import json
|
2 |
+
import os
|
3 |
+
from typing import List, Dict, Any, Optional
|
4 |
+
from pydantic import BaseModel
|
5 |
+
import uvicorn
|
6 |
+
from fastapi import FastAPI, HTTPException
|
7 |
+
from pinecone import Pinecone , ServerlessSpec
|
8 |
+
import numpy as np
|
9 |
+
from openai import OpenAI
|
10 |
+
|
11 |
+
# Load environment variables
|
12 |
+
from dotenv import load_dotenv
|
13 |
+
load_dotenv()
|
14 |
+
|
15 |
+
# Get API keys from environment variables
|
16 |
+
PINECONE_API_KEY = os.getenv('PINECONE_API_KEY')
|
17 |
+
OPENAI_API_KEY = os.getenv('OPENAI_API_KEY')
|
18 |
+
|
19 |
+
if not PINECONE_API_KEY:
|
20 |
+
raise ValueError("PINECONE_API_KEY environment variable not set")
|
21 |
+
if not OPENAI_API_KEY:
|
22 |
+
raise ValueError("OPENAI_API_KEY environment variable not set")
|
23 |
+
|
24 |
+
# Create FastAPI app
|
25 |
+
app = FastAPI(title="E-Bikes Semantic Search API",
|
26 |
+
description="API for finding similar e-bikes based on semantic search",
|
27 |
+
version="1.0.0")
|
28 |
+
|
29 |
+
# Request and response models
|
30 |
+
class SearchRequest(BaseModel):
|
31 |
+
description: str
|
32 |
+
top_k: int = 3
|
33 |
+
|
34 |
+
class BikeMatch(BaseModel):
|
35 |
+
id: str
|
36 |
+
name: str
|
37 |
+
type: str
|
38 |
+
description: str
|
39 |
+
score: float
|
40 |
+
|
41 |
+
class SearchResponse(BaseModel):
|
42 |
+
matches: List[BikeMatch]
|
43 |
+
|
44 |
+
# Initialize OpenAI client
|
45 |
+
openai_client = OpenAI(api_key=OPENAI_API_KEY)
|
46 |
+
|
47 |
+
# Define the embedding model using OpenAI
|
48 |
+
class OpenAIEmbedder:
|
49 |
+
def __init__(self, model_name="text-embedding-3-small"):
|
50 |
+
self.model_name = model_name
|
51 |
+
self.client = openai_client
|
52 |
+
self.embedding_dimension = 1536 # Dimension of text-embedding-3-small
|
53 |
+
|
54 |
+
def encode(self, texts):
|
55 |
+
if isinstance(texts, str):
|
56 |
+
texts = [texts]
|
57 |
+
|
58 |
+
# Get embeddings from OpenAI
|
59 |
+
response = self.client.embeddings.create(
|
60 |
+
input=texts,
|
61 |
+
model=self.model_name
|
62 |
+
)
|
63 |
+
|
64 |
+
# Extract embeddings from response
|
65 |
+
embeddings = [item.embedding for item in response.data]
|
66 |
+
return np.array(embeddings)
|
67 |
+
|
68 |
+
# Initialize Pinecone client
|
69 |
+
def initialize_pinecone():
|
70 |
+
pc = Pinecone(api_key=PINECONE_API_KEY)
|
71 |
+
|
72 |
+
# Define index name
|
73 |
+
index_name = "ebikes-search"
|
74 |
+
|
75 |
+
# Check if index already exists
|
76 |
+
existing_indexes = pc.list_indexes().names()
|
77 |
+
if index_name not in existing_indexes:
|
78 |
+
# Create index with 1536 dimensions (matches text-embedding-3-small)
|
79 |
+
pc.create_index(
|
80 |
+
name=index_name,
|
81 |
+
dimension=1536,
|
82 |
+
metric="cosine",
|
83 |
+
spec=ServerlessSpec(cloud="aws", region="us-east-1")
|
84 |
+
)
|
85 |
+
print(f"Created new index: {index_name}")
|
86 |
+
|
87 |
+
# Connect to the index
|
88 |
+
try:
|
89 |
+
index = pc.Index(index_name)
|
90 |
+
return index
|
91 |
+
except Exception as e:
|
92 |
+
print(f"Error connecting to Pinecone index: {e}")
|
93 |
+
raise
|
94 |
+
|
95 |
+
# Load the e-bikes data
|
96 |
+
def load_ebikes_data(file_path="data.json"):
|
97 |
+
try:
|
98 |
+
with open(file_path, 'r') as f:
|
99 |
+
data = json.load(f)
|
100 |
+
return data.get('ebikes', [])
|
101 |
+
except Exception as e:
|
102 |
+
print(f"Error loading e-bikes data: {e}")
|
103 |
+
return []
|
104 |
+
|
105 |
+
# Create embeddings and upload to Pinecone
|
106 |
+
def create_and_upload_embeddings(ebikes_data, encoder, pinecone_index):
|
107 |
+
# Prepare data for indexing
|
108 |
+
ids = []
|
109 |
+
descriptions = []
|
110 |
+
metadata = []
|
111 |
+
|
112 |
+
for bike in ebikes_data:
|
113 |
+
ids.append(bike['id'])
|
114 |
+
descriptions.append(bike['description'])
|
115 |
+
metadata.append({
|
116 |
+
'id': bike['id'],
|
117 |
+
'name': bike['name'],
|
118 |
+
'type': bike['type'],
|
119 |
+
'description': bike['description']
|
120 |
+
})
|
121 |
+
|
122 |
+
# Create embeddings
|
123 |
+
embeddings = encoder.encode(descriptions)
|
124 |
+
|
125 |
+
# Prepare vectors for Pinecone
|
126 |
+
vectors_to_upsert = []
|
127 |
+
for i in range(len(ids)):
|
128 |
+
vector = {
|
129 |
+
'id': ids[i],
|
130 |
+
'values': embeddings[i].tolist(),
|
131 |
+
'metadata': metadata[i]
|
132 |
+
}
|
133 |
+
vectors_to_upsert.append(vector)
|
134 |
+
|
135 |
+
# Upsert vectors to Pinecone
|
136 |
+
pinecone_index.upsert(vectors=vectors_to_upsert)
|
137 |
+
print(f"Uploaded {len(vectors_to_upsert)} embeddings to Pinecone")
|
138 |
+
|
139 |
+
# Global variables for model and Pinecone index
|
140 |
+
encoder = None
|
141 |
+
pinecone_index = None
|
142 |
+
|
143 |
+
# Initialize data at startup
|
144 |
+
@app.on_event("startup")
|
145 |
+
async def startup_event():
|
146 |
+
global encoder, pinecone_index
|
147 |
+
|
148 |
+
print("Initializing OpenAI embedder...")
|
149 |
+
encoder = OpenAIEmbedder()
|
150 |
+
|
151 |
+
print("Connecting to Pinecone...")
|
152 |
+
pinecone_index = initialize_pinecone()
|
153 |
+
|
154 |
+
print("Loading e-bikes data...")
|
155 |
+
ebikes_data = load_ebikes_data("data.json")
|
156 |
+
|
157 |
+
if not ebikes_data:
|
158 |
+
print("No e-bikes data found, skipping embedding creation")
|
159 |
+
return
|
160 |
+
|
161 |
+
print("Creating and uploading embeddings...")
|
162 |
+
create_and_upload_embeddings(ebikes_data, encoder, pinecone_index)
|
163 |
+
|
164 |
+
print("API startup completed successfully!")
|
165 |
+
|
166 |
+
@app.get("/health")
|
167 |
+
async def health_check():
|
168 |
+
"""Health check endpoint"""
|
169 |
+
return {"status": "healthy"}
|
170 |
+
|
171 |
+
@app.post("/search", response_model=SearchResponse)
|
172 |
+
async def search_ebikes(request: SearchRequest):
|
173 |
+
"""
|
174 |
+
Search for e-bikes similar to the provided description
|
175 |
+
|
176 |
+
This endpoint uses semantic search to find e-bikes that match the user's description.
|
177 |
+
"""
|
178 |
+
try:
|
179 |
+
# Create embedding for the query
|
180 |
+
query_embedding = encoder.encode(request.description)[0]
|
181 |
+
|
182 |
+
# Query Pinecone
|
183 |
+
results = pinecone_index.query(
|
184 |
+
vector=query_embedding.tolist(),
|
185 |
+
top_k=request.top_k,
|
186 |
+
include_metadata=True
|
187 |
+
)
|
188 |
+
|
189 |
+
# Parse results
|
190 |
+
matches = []
|
191 |
+
for match in results.matches:
|
192 |
+
bike_match = BikeMatch(
|
193 |
+
id=match.metadata.get('id'),
|
194 |
+
name=match.metadata.get('name'),
|
195 |
+
type=match.metadata.get('type'),
|
196 |
+
description=match.metadata.get('description'),
|
197 |
+
score=float(match.score)
|
198 |
+
)
|
199 |
+
matches.append(bike_match)
|
200 |
+
|
201 |
+
return SearchResponse(matches=matches)
|
202 |
+
|
203 |
+
except Exception as e:
|
204 |
+
print(f"Error during search: {e}")
|
205 |
+
raise HTTPException(status_code=500, detail=f"Search failed: {str(e)}")
|
206 |
+
|
207 |
+
if __name__ == "__main__":
|
208 |
+
uvicorn.run("main:app", host="0.0.0.0", port=8000, reload=True)
|