WildOjisan commited on
Commit
4c52fbf
ยท
1 Parent(s): bcffe48
Files changed (6) hide show
  1. .dockerignore +8 -0
  2. .gitignore +6 -0
  3. README.md +8 -4
  4. app.py +172 -0
  5. database_conn.py +52 -0
  6. requirements.txt +9 -0
.dockerignore ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ __pycache__/
2
+ *.pyc
3
+ *.pyo
4
+ *.pyd
5
+ *.log
6
+ .git
7
+ .gitignore
8
+ .venv/
.gitignore ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ __pycache__/
2
+ *.pyc
3
+ *.pyo
4
+ *.pyd
5
+ *.log
6
+ .venv/
README.md CHANGED
@@ -1,10 +1,14 @@
1
  ---
2
- title: Embeddinggemma 300m Fastapi
3
- emoji: ๐Ÿข
4
- colorFrom: yellow
5
- colorTo: indigo
6
  sdk: docker
7
  pinned: false
8
  ---
9
 
10
  Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
1
  ---
2
+ title: FastAPI Hgf Test1
3
+ emoji: ๐Ÿฆ€
4
+ colorFrom: green
5
+ colorTo: green
6
  sdk: docker
7
  pinned: false
8
  ---
9
 
10
  Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
11
+
12
+ py -3.10 -m uv venv venv
13
+
14
+ .\venv\Scripts\Activate.ps1
app.py ADDED
@@ -0,0 +1,172 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import FastAPI, Depends, HTTPException
2
+ from pydantic import BaseModel
3
+ import uvicorn
4
+ import asyncpg
5
+ from sentence_transformers import SentenceTransformer
6
+ import torch
7
+ from typing import List, Dict, Any, Union
8
+
9
+ from database_conn import connect_to_db, close_db_connection, get_db_connection
10
+
11
+ # 1. FastAPI ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ธ์Šคํ„ด์Šค ์ƒ์„ฑ
12
+ app = FastAPI(title="Simple FastAPI Demo")
13
+
14
+ # --- 1. Initialization and Model Loading ---
15
+ # Initialize the FastAPI application
16
+ app = FastAPI(
17
+ title="Sentence Transformer Embedding Gemma API",
18
+ description="An API for semantic similarity using google/embeddinggemma-300m.",
19
+ version="1.0.0"
20
+ )
21
+
22
+ # Load the SentenceTransformer model once when the application starts
23
+ # This is a synchronous operation, which is fine for app startup.
24
+ try:
25
+ model = SentenceTransformer("google/embeddinggemma-300m")
26
+ # Set the model to evaluation mode
27
+ model.eval()
28
+ print("Model 'google/embeddinggemma-300m' loaded successfully.")
29
+ except Exception as e:
30
+ print(f"Error loading model: {e}")
31
+ # In a real application, you might want to raise an exception or handle this more gracefully
32
+ # For simplicity, we'll let the app potentially fail if the model can't load.
33
+ model = None # Keep model as None if loading failed
34
+
35
+ # 2. ๋ฐ์ดํ„ฐ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ๋ฅผ ์œ„ํ•œ Pydantic ๋ชจ๋ธ ์ •์˜
36
+ # ํด๋ผ์ด์–ธํŠธ๋กœ๋ถ€ํ„ฐ ๋ฐ›์„ ์š”์ฒญ(Request) ๋ฐ์ดํ„ฐ ๊ตฌ์กฐ๋ฅผ ์ •์˜ํ•ฉ๋‹ˆ๋‹ค.
37
+ class Item(BaseModel):
38
+ name: str
39
+ price: float
40
+ is_offer: bool | None = None
41
+
42
+ @app.on_event("startup")
43
+ async def startup_event():
44
+ """์„œ๋ฒ„ ์‹œ์ž‘ ์‹œ DB ์—ฐ๊ฒฐ ํ’€ ์ดˆ๊ธฐํ™”"""
45
+ await connect_to_db()
46
+
47
+ @app.on_event("shutdown")
48
+ async def shutdown_event():
49
+ """์„œ๋ฒ„ ์ข…๋ฃŒ ์‹œ DB ์—ฐ๊ฒฐ ํ’€ ์ข…๋ฃŒ"""
50
+ await close_db_connection()
51
+
52
+ # --- API ์—”๋“œํฌ์ธํŠธ ์ •์˜ ---
53
+
54
+ # 3. ๋ฃจํŠธ ์—”๋“œํฌ์ธํŠธ (GET /)
55
+ @app.get("/")
56
+ def read_root():
57
+ result={"success":True,"data":None,"msg":""}
58
+ try:
59
+ result["data"]="ok"
60
+ return result
61
+ except Exception as e:
62
+ result["success"] = False
63
+ result["msg"]=f"server error. {e!r}"
64
+ return result
65
+
66
+
67
+ class MakeTextEmbedding(BaseModel):
68
+ """
69
+ Input structure for the similarity endpoint.
70
+ """
71
+ query: str
72
+ documents: List[str]
73
+
74
+ # ์ถœ๋ ฅ ํด๋ž˜์Šค: ๊ฒฐ๊ณผ๊ฐ€ ์„ฑ๊ณต ์—ฌ๋ถ€์™€ ์ž„๋ฒ ๋”ฉ ๋ฐ์ดํ„ฐ ๋ฆฌ์ŠคํŠธ๋ฅผ ํฌํ•จํ•˜๋„๋ก ์ •์˜
75
+ class EmbeddingOutput(BaseModel):
76
+ success: bool
77
+ msg: str
78
+ # ์ž„๋ฒ ๋”ฉ ๊ฒฐ๊ณผ๋Š” ์ค‘์ฒฉ ๋ฆฌ์ŠคํŠธ ํ˜•ํƒœ๊ฐ€ ๋ฉ๋‹ˆ๋‹ค. (๋ฌธ์„œ ์ˆ˜, ์ž„๋ฒ ๋”ฉ ์ฐจ์›)
79
+ data: Union[List[List[float]], None] = None
80
+
81
+
82
+ @app.post("/make_text_embedding", summary="Calculate semantic similarity and find the best match")
83
+ async def calculate_similarity(data: MakeTextEmbedding):
84
+ result={"success":True,"data":None,"msg":""}
85
+ try:
86
+ if model is None:
87
+ result["success"] = False
88
+ result["msg"]=f"Model not loaded. Service is unavailable."
89
+ return result
90
+
91
+ # The 'with torch.no_grad():' block is essential for efficient inference
92
+ with torch.no_grad():
93
+ # Encode the query (single vector)
94
+ document_embeddings = model.encode_document(data.documents)
95
+ embeddings_list = document_embeddings.tolist()
96
+
97
+ result["data"]=embeddings_list
98
+ return result
99
+ except Exception as e:
100
+ result["success"] = False
101
+ result["msg"]=f"server error. {e!r}"
102
+ return result
103
+
104
+
105
+
106
+
107
+
108
+
109
+ # ----------------------------------------------------
110
+ # 2. API ์—”๋“œํฌ์ธํŠธ: SELECT NOW() (์Œฉ ์ฟผ๋ฆฌ ์‹คํ–‰)
111
+ # ----------------------------------------------------
112
+ @app.get("/time", response_model=Dict[str, Any])
113
+ async def get_db_time(
114
+ # get_db_connection์„ ํ†ตํ•ด asyncpg.Connection ๊ฐ์ฒด๋งŒ ์ฃผ์ž…๋ฐ›์Šต๋‹ˆ๋‹ค.
115
+ conn: asyncpg.Connection = Depends(get_db_connection)
116
+ ):
117
+ """
118
+ DB์— ์ ‘์†ํ•˜์—ฌ ํ˜„์žฌ ์‹œ๊ฐ„์„ ์กฐํšŒํ•˜๋Š” ์Œฉ SQL ์ฟผ๋ฆฌ(SELECT NOW())๋ฅผ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค.
119
+ """
120
+ result = {"success": True, "data": None, "msg": ""}
121
+
122
+ try:
123
+ # **์—ฌ๊ธฐ์„œ ์Œฉ SQL ์ฟผ๋ฆฌ๋ฅผ ์ง์ ‘ ์ž‘์„ฑํ•˜๊ณ  ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค.**
124
+ query = "SELECT NOW();"
125
+
126
+ # ์ฟผ๋ฆฌ ์‹คํ–‰ (fetchval()์€ ์ฟผ๋ฆฌ ๊ฒฐ๊ณผ์˜ ์ฒซ ํ–‰, ์ฒซ ์—ด์˜ ๊ฐ’๋งŒ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.)
127
+ # asyncpg๊ฐ€ DB ์‹œ๊ฐ„์„ Python datetime ๊ฐ์ฒด๋กœ ๋ณ€ํ™˜ํ•ด ์ค๋‹ˆ๋‹ค.
128
+ records = await conn.fetch(query)
129
+ data_list_of_dicts: List[Dict[str, Any]] = [
130
+ # dict(record)๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ Record ๊ฐ์ฒด๋ฅผ ์ผ๋ฐ˜ ํŒŒ์ด์ฌ ๋”•์…”๋„ˆ๋ฆฌ๋กœ ๋ณ€ํ™˜
131
+ dict(record) for record in records
132
+ ]
133
+
134
+ # ๊ฒฐ๊ณผ๋ฅผ ๋ฌธ์ž์—ด๋กœ ๋ณ€ํ™˜ํ•˜์—ฌ JSON ์‘๋‹ต์— ๋‹ด์Šต๋‹ˆ๋‹ค.
135
+ result["data"] = data_list_of_dicts[0]
136
+
137
+ except Exception as e:
138
+ # ์ฟผ๋ฆฌ ์‹คํ–‰ ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด 500 ์—๋Ÿฌ๋ฅผ ๋ฐ˜ํ™˜
139
+ result["success"] = False
140
+ result["msg"] = f"Database query error: {e!r}"
141
+
142
+ return result
143
+
144
+ # 4. ์ฟผ๋ฆฌ ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ ๋ฐ›๋Š” ์—”๋“œํฌ์ธํŠธ (GET /items/{item_id})
145
+ @app.get("/items")
146
+ def read_item(q: str | None = None):
147
+ """
148
+ ํŠน์ • ID์˜ ์•„์ดํ…œ ์ •๋ณด๋ฅผ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค.
149
+
150
+ :param item_id: ์•„์ดํ…œ์˜ ๊ณ ์œ  ID (๊ฒฝ๋กœ ๋งค๊ฐœ๋ณ€์ˆ˜)
151
+ :param q: ์„ ํƒ์ ์ธ ๊ฒ€์ƒ‰ ๋ฌธ์ž์—ด (์ฟผ๋ฆฌ ๋งค๊ฐœ๋ณ€์ˆ˜)
152
+ """
153
+ return {"q": q, "description": "This is a query test."}
154
+
155
+ # 5. ์š”์ฒญ ๋ณธ๋ฌธ(Body)์„ ๋ฐ›๋Š” ์—”๋“œํฌ์ธํŠธ (POST /items/)
156
+ @app.post("/items/")
157
+ def create_item(item: Item):
158
+ """
159
+ ์ƒˆ๋กœ์šด ์•„์ดํ…œ์„ ์ƒ์„ฑํ•˜๊ณ  ์ •๋ณด๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.
160
+
161
+ :param item: Item Pydantic ๋ชจ๋ธ์— ์ •์˜๋œ ๋ฐ์ดํ„ฐ ๊ตฌ์กฐ
162
+ """
163
+ # ์‹ค์ œ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์ €์žฅํ•˜๋Š” ๋Œ€์‹ , ๊ฐ„๋‹จํ•œ ์ฒ˜๋ฆฌ๋ฅผ ์ˆ˜ํ–‰
164
+ if item.price > 100.0:
165
+ item.name = f"Premium {item.name}"
166
+
167
+ return {"message": "Item created successfully", "item_data": item}
168
+
169
+
170
+ if __name__ == "__main__":
171
+ # --reload ์˜ต์…˜์„ ์ถ”๊ฐ€ํ•˜์—ฌ ์ฝ”๋“œ๊ฐ€ ๋ณ€๊ฒฝ๋  ๋•Œ๋งˆ๋‹ค ์ž๋™ ์žฌ์‹œ์ž‘๋˜๊ฒŒ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค.
172
+ uvicorn.run("app:app", host="0.0.0.0", port=8000, reload=True)
database_conn.py ADDED
@@ -0,0 +1,52 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # database_conn.py
2
+
3
+ import asyncpg
4
+ from typing import AsyncGenerator
5
+
6
+ # [์ฃผ์˜] ์ด ์ •๋ณด๋Š” ์‹ค์ œ ์„œ๋น„์Šค์—์„œ๋Š” ํ™˜๊ฒฝ ๋ณ€์ˆ˜๋กœ ๊ด€๋ฆฌํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
7
+ DB_USER = "neondb_owner"
8
+ DB_PASSWORD = "npg_rFA9DgloybO0" # ์‹ค์ œ ๋น„๋ฐ€๋ฒˆํ˜ธ ์‚ฌ์šฉ
9
+ DB_HOST = "ep-rapid-truth-ad2e6rnq-pooler.c-2.us-east-1.aws.neon.tech"
10
+ DB_NAME = "neondb"
11
+ DB_PORT = 5432
12
+ DB_SSL = 'require'
13
+
14
+ # ๊ธ€๋กœ๋ฒŒ DB ์—ฐ๊ฒฐ ํ’€ ๋ณ€์ˆ˜
15
+ pool: asyncpg.Pool = None
16
+
17
+ # ์„œ๋ฒ„ ์‹œ์ž‘ ์‹œ ํ˜ธ์ถœ (FastAPI @app.on_event("startup")์—์„œ ์‚ฌ์šฉ)
18
+ async def connect_to_db():
19
+ global pool
20
+ print("DB ์—ฐ๊ฒฐ ํ’€ ์ดˆ๊ธฐํ™” ์ค‘...")
21
+ try:
22
+ pool = await asyncpg.create_pool(
23
+ user=DB_USER,
24
+ password=DB_PASSWORD,
25
+ host=DB_HOST,
26
+ database=DB_NAME,
27
+ port=DB_PORT,
28
+ ssl=DB_SSL
29
+ )
30
+ print("DB ์—ฐ๊ฒฐ ์„ฑ๊ณต.")
31
+ except Exception as e:
32
+ print(f"DB ์—ฐ๊ฒฐ ์‹คํŒจ: {e}")
33
+ raise e
34
+
35
+ # ์„œ๋ฒ„ ์ข…๋ฃŒ ์‹œ ํ˜ธ์ถœ (FastAPI @app.on_event("shutdown")์—์„œ ์‚ฌ์šฉ)
36
+ async def close_db_connection():
37
+ global pool
38
+ if pool:
39
+ print("DB ์—ฐ๊ฒฐ ํ’€ ์ข…๋ฃŒ ์ค‘...")
40
+ await pool.close()
41
+ print("DB ์—ฐ๊ฒฐ ํ’€ ์ข…๋ฃŒ ์™„๋ฃŒ.")
42
+
43
+ # API ์—”๋“œํฌ์ธํŠธ์— DB ์—ฐ๊ฒฐ ๊ฐ์ฒด๋ฅผ ์ฃผ์ž…ํ•˜๋Š” ํ•จ์ˆ˜ (FastAPI Depends์—์„œ ์‚ฌ์šฉ)
44
+ async def get_db_connection() -> AsyncGenerator[asyncpg.Connection, None]:
45
+ """DB ์—ฐ๊ฒฐ ํ’€์—์„œ ์ปค๋„ฅ์…˜์„ ๊ฐ€์ ธ์™€ ์—”๋“œํฌ์ธํŠธ์— ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค."""
46
+ global pool
47
+ if not pool:
48
+ raise ConnectionError("DB ์—ฐ๊ฒฐ ํ’€์ด ์ดˆ๊ธฐํ™”๋˜์ง€ ์•Š์•˜์Šต๋‹ˆ๋‹ค.")
49
+
50
+ # async with ๊ตฌ๋ฌธ์œผ๋กœ ์—ฐ๊ฒฐ์„ ๊ฐ€์ ธ์™€ yield ํ›„, ์ž๋™์œผ๋กœ ํ’€์— ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.
51
+ async with pool.acquire() as connection:
52
+ yield connection
requirements.txt ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ fastapi
2
+ uvicorn[standard]
3
+ sqlalchemy
4
+ asyncpg # ๋น„๋™๊ธฐ PostgreSQL ๋“œ๋ผ์ด๋ฒ„ (psycopg2-binary ๋Œ€์‹  ์‚ฌ์šฉ)
5
+ pydantic-settings # ํ™˜๊ฒฝ ๋ณ€์ˆ˜ ๊ด€๋ฆฌ๋ฅผ ์œ„ํ•œ Pydantic ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ
6
+ python-dotenv # ๋กœ์ปฌ ๊ฐœ๋ฐœ ํ™˜๊ฒฝ์—์„œ .env ํŒŒ์ผ ๋กœ๋“œ๋ฅผ ์œ„ํ•ด ํ•„์š” (์„ ํƒ ์‚ฌํ•ญ)
7
+ pydantic # fastapi req ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ
8
+ scikit-learn
9
+ sentence-transformers