Chanjeans commited on
Commit
fca4545
ยท
verified ยท
1 Parent(s): b89d00a

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +91 -58
app.py CHANGED
@@ -968,13 +968,54 @@ def make_user_profile_text(user_profile: dict) -> str:
968
 
969
 
970
  #####################################
971
- # 4) ์‚ฌ์šฉ์ž ์ž„๋ฒ ๋”ฉ + ์ถ”์ฒœ ๋กœ์ง
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
972
  #####################################
973
  def get_user_embedding(user_profile: dict):
974
  user_text = make_user_profile_text(user_profile)
975
  return model_bert.encode(user_text, convert_to_numpy=True)
976
 
977
  def extract_hobby(desc):
 
978
  match = re.search(r"\((.*?)\)", desc)
979
  return match.group(1) if match else ""
980
 
@@ -982,15 +1023,17 @@ def cosine_similarity(vec1, vec2):
982
  norm1 = np.linalg.norm(vec1)
983
  norm2 = np.linalg.norm(vec2)
984
  if norm1 == 0 or norm2 == 0:
985
- return 0
986
- return np.dot(vec1, vec2) / (norm1 * norm2)
987
 
988
  def recommend_content_based(user_profile, top_n=5):
989
  user_emb = get_user_embedding(user_profile)
990
  scored = []
991
 
992
- user_hobby = user_profile.get("hobby", "")
993
- user_detail_hobby = user_profile.get("detail_hobby", "")
 
 
994
  user_extroversion = user_profile.get("extroversion", "")
995
  user_feeling_thinking = user_profile.get("feeling_thinking", "")
996
 
@@ -999,18 +1042,28 @@ def recommend_content_based(user_profile, top_n=5):
999
  item_emb = item_embedding_dict[item_id]
1000
  sim = cosine_similarity(user_emb, item_emb)
1001
 
1002
- # ๊ฐ€์ค‘์น˜
1003
  weight = 1.0
1004
 
1005
- # (1) ์ทจ๋ฏธ
1006
- desc_hobby = extract_hobby(item["desc"]) # ์˜ˆ: (์šด๋™, ํ—ฌ์Šค)
1007
- if user_hobby and user_hobby in desc_hobby:
1008
- weight *= 1.05
1009
- if user_detail_hobby and user_detail_hobby in desc_hobby:
1010
- weight *= 1.2
 
 
 
 
 
 
1011
 
1012
  # (2) ์„ฑํ–ฅ
1013
- personality_match_count = sum(trait in item["personality"] for trait in [user_extroversion, user_feeling_thinking])
 
 
 
 
1014
  if personality_match_count == 1:
1015
  weight *= 1.15
1016
  elif personality_match_count == 2:
@@ -1019,6 +1072,7 @@ def recommend_content_based(user_profile, top_n=5):
1019
  final_score = sim * weight
1020
  scored.append((item, final_score))
1021
 
 
1022
  scored.sort(key=lambda x: x[1], reverse=True)
1023
  return scored[:top_n]
1024
 
@@ -1081,7 +1135,8 @@ def chat_response(user_input, mode="emotion", max_retries=5):
1081
  def home():
1082
  return {"message": "์•ˆ๋…•ํ•˜์„ธ์š”! ์ถ”์ฒœ & ์ฑ—๋ด‡ FastAPI ์„œ๋ฒ„์ž…๋‹ˆ๋‹ค."}
1083
 
1084
- # (2) ์ถ”์ฒœ Endpoint
 
1085
  class UserProfile(BaseModel):
1086
  extroversion: str
1087
  feeling_thinking: str
@@ -1090,7 +1145,6 @@ class UserProfile(BaseModel):
1090
 
1091
  @app.post("/recommend")
1092
  def recommend_api(profile: UserProfile):
1093
- # "์†”๋ฃจ์…˜ ์ œ๊ณต" ๋กœ์ง๊ณผ ๋™์ผ
1094
  top_items = recommend_content_based(profile.dict(), top_n=5)
1095
  results = []
1096
  for (item, score) in top_items:
@@ -1103,63 +1157,51 @@ def recommend_api(profile: UserProfile):
1103
  })
1104
  return {"recommendations": results}
1105
 
1106
- # (3) ์ฑ—๋ด‡ Endpoint (emotion/rational ์„ ํƒ ๊ฐ€๋Šฅ)
 
1107
  class ChatRequest(BaseModel):
1108
  user_input: str
1109
- mode: str # `emotion` ๋˜๋Š” `rational`์„ ๋ช…ํ™•ํ•˜๊ฒŒ ์ž…๋ ฅ๋ฐ›๋„๋ก ์„ค์ •
1110
- RECOMMEND_KEYWORDS = [
1111
- "์ถ”์ฒœ", "์ถ”์ฒœํ•ด์ค˜", "์ทจ๋ฏธ ์ถ”์ฒœ"
1112
- ]
1113
 
1114
- @app.post("/chat")
1115
- def chat_api(req: ChatRequest):
1116
- user_text = req.user_input
1117
- mode = req.mode.lower() # ์ž…๋ ฅ์„ ์†Œ๋ฌธ์ž๋กœ ๋ณ€ํ™˜ํ•ด ํ†ต์ผ์„ฑ ์œ ์ง€
1118
- if mode not in ["emotion", "rational"]:
1119
- raise HTTPException(status_code=400, detail="mode๋Š” 'emotion' ๋˜๋Š” 'rational'์ด์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.")
1120
 
1121
- reply = chat_response(user_text, mode=mode)
1122
- return {"response": reply}
1123
- from pydantic import BaseModel
1124
- from typing import Optional
1125
 
1126
- # ๊ธฐ์กด ChatRequest ์— user_profile ์šฉ ํ•„๋“œ๋ฅผ ์ถ”๊ฐ€ํ•œ ๋ชจ๋ธ
1127
  class ChatOrRecommendRequest(BaseModel):
1128
  user_input: str # ์‚ฌ์šฉ์ž์˜ ์ฑ„ํŒ… ๋ฉ”์‹œ์ง€
1129
- mode: str # "emotion" ๋˜๋Š” "rational" (์ฑ—๋ด‡ ๋ชจ๋“œ)
1130
 
1131
- # ์ถ”์ฒœ์šฉ ํ”„๋กœํ•„ (์„ ํƒ์ ์œผ๋กœ ๋ฐ›์„ ์ˆ˜ ์žˆ๋„๋ก Optional)
1132
  extroversion: Optional[str] = None
1133
  feeling_thinking: Optional[str] = None
1134
- hobby: Optional[List[str]] = [] # ๊ธฐ๋ณธ๊ฐ’ ๋นˆ ๋ฆฌ์ŠคํŠธ
1135
- detail_hobby: Optional[List[str]] = [] # ๊ธฐ๋ณธ๊ฐ’ ๋นˆ ๋ฆฌ์ŠคํŠธ
1136
 
 
 
1137
  @app.post("/chat_or_recommend")
1138
  def chat_or_recommend(req: ChatOrRecommendRequest):
1139
  user_text = req.user_input
1140
  mode = req.mode.lower()
1141
 
1142
- # 1) ์ถ”์ฒœ ํ‚ค์›Œ๋“œ ํฌํ•จ ์—ฌ๋ถ€ ํ™•์ธ
1143
  if any(keyword in user_text for keyword in RECOMMEND_KEYWORDS):
1144
- # ์‚ฌ์šฉ์ž๊ฐ€ ์ถ”์ฒœ์„ ์›ํ•œ๋‹ค๊ณ  ํŒ๋‹จ
1145
- # ํ”„๋กœํ•„ ์ •๋ณด๊ฐ€ ์—†๋Š” ๊ฒฝ์šฐ๋ฅผ ๋Œ€๋น„ํ•ด์„œ ๊ธฐ๋ณธ๊ฐ’ ํ˜น์€ ์˜ˆ์™ธ์ฒ˜๋ฆฌ๋ฅผ ํ•  ์ˆ˜๋„ ์žˆ์Œ
1146
  if not req.hobby:
1147
- # ๋งŒ์•ฝ ํ”„๋กœํ•„์ด ์—†์œผ๋ฉด ์˜ˆ์™ธ ์ฒ˜๋ฆฌํ•˜๊ฑฐ๋‚˜,
1148
- # "ํ”„๋กœํ•„(์ทจ๋ฏธ/์„ฑํ–ฅ)์„ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”" ๋ผ๊ณ  ์•ˆ๋‚ดํ•  ์ˆ˜๋„ ์žˆ์Œ
1149
  raise HTTPException(
1150
  status_code=400,
1151
- detail="์ถ”์ฒœ์„ ์œ„ํ•ด hobby, detail_hobby, extroversion, feeling_thinking ์ •๋ณด๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค."
1152
  )
1153
-
1154
- # (A) ์ถ”์ฒœ API ๋กœ์ง ํ˜ธ์ถœ
1155
  user_profile = {
1156
  "extroversion": req.extroversion or "",
1157
  "feeling_thinking": req.feeling_thinking or "",
1158
- "hobby": req.hobby or "",
1159
- "detail_hobby": req.detail_hobby or "",
1160
  }
1161
  top_items = recommend_content_based(user_profile, top_n=5)
1162
-
1163
  results = []
1164
  for (item, score) in top_items:
1165
  results.append({
@@ -1169,24 +1211,15 @@ def chat_or_recommend(req: ChatOrRecommendRequest):
1169
  "personality": item["personality"],
1170
  "score": round(score, 4)
1171
  })
1172
-
1173
- return {
1174
- "mode": "recommend",
1175
- "recommendations": results
1176
- }
1177
 
1178
  else:
1179
- # 2) ์ถ”์ฒœ ํ‚ค์›Œ๋“œ๊ฐ€ ์—†์œผ๋ฉด ์ฑ—๋ด‡ ๋กœ์ง
1180
  if mode not in ["emotion", "rational"]:
1181
  raise HTTPException(
1182
  status_code=400,
1183
  detail="mode๋Š” 'emotion' ๋˜๋Š” 'rational'์ด์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค."
1184
  )
1185
 
1186
- # (B) ์ฑ—๋ด‡ ํ˜ธ์ถœ
1187
  reply = chat_response(user_text, mode=mode)
1188
- return {
1189
- "mode": "chat",
1190
- "response": reply
1191
- }
1192
-
 
968
 
969
 
970
  #####################################
971
+ # 2) ์•„์ดํ…œ ์ž„๋ฒ ๋”ฉ ๋กœ์ง
972
+ #####################################
973
+ def make_item_embedding_dict(items, model):
974
+ item_embedding_dict = {}
975
+ for item in items:
976
+ text = f"{item['title']} {item['desc']}"
977
+ emb = model.encode(text, convert_to_numpy=True)
978
+ item_embedding_dict[item['item_id']] = emb
979
+ return item_embedding_dict
980
+
981
+ item_embedding_dict = make_item_embedding_dict(items, model_bert)
982
+
983
+
984
+ #####################################
985
+ # 3) ์‚ฌ์šฉ์ž ํ”„๋กœํ•„ -> ๋ฌธ์žฅํ™” (๋‹ค์ค‘ ์ทจ๋ฏธ ๋Œ€์‘)
986
+ #####################################
987
+ def make_user_profile_text(user_profile: dict) -> str:
988
+ ext = user_profile.get("extroversion", "")
989
+ ft = user_profile.get("feeling_thinking", "")
990
+
991
+ # ๐ŸŸข hobby, detail_hobby๋ฅผ ๋ฆฌ์ŠคํŠธ๋กœ ๋ฐ›๋Š”๋‹ค๊ณ  ๊ฐ€์ •
992
+ hobby_list = user_profile.get("hobby", [])
993
+ detail_list = user_profile.get("detail_hobby", [])
994
+
995
+ # ๋ฆฌ์ŠคํŠธ๋ฅผ ๋ฌธ์ž์—ด๋กœ ํ•ฉ์น˜๊ธฐ
996
+ # ์˜ˆ: hobby_list=["์šด๋™","๋…์„œ"] โ†’ "์šด๋™, ๋…์„œ"
997
+ hobby_str = ", ".join(hobby_list) if hobby_list else ""
998
+ detail_str = ", ".join(detail_list) if detail_list else ""
999
+
1000
+ # ๊ฐ„๋‹จํžˆ ํ•˜๋‚˜์˜ ๋ฌธ์žฅ์œผ๋กœ ๊ตฌ์„ฑ
1001
+ # ํ•„์š”ํ•˜๋‹ค๋ฉด ์ทจ๋ฏธ๋งˆ๋‹ค ๋ณ„๋„ ๋ฌธ์žฅ ๋“ฑ์„ ์ถ”๊ฐ€ ๊ฐ€๋Šฅ
1002
+ text = (
1003
+ f"์ €๋Š” {ext}์ด๋ฉฐ {ft}ํ•œ ์„ฑํ–ฅ์„ ๊ฐ€์ง„ ์‚ฌ๋žŒ์ž…๋‹ˆ๋‹ค. "
1004
+ f"์ฃผ์š” ์ทจ๋ฏธ๋กœ๋Š” {hobby_str}๋ฅผ ์ฆ๊ธฐ๊ณ , "
1005
+ f"ํŠนํžˆ {detail_str} ๋ถ„์•ผ์— ๊ด€์‹ฌ์ด ๋งŽ์Šต๋‹ˆ๋‹ค."
1006
+ )
1007
+ return text
1008
+
1009
+
1010
+ #####################################
1011
+ # 4) ์‚ฌ์šฉ์ž ์ž„๋ฒ ๋”ฉ + ์ถ”์ฒœ ๋กœ์ง (๋‹ค์ค‘ ์ทจ๋ฏธ ๋Œ€์‘)
1012
  #####################################
1013
  def get_user_embedding(user_profile: dict):
1014
  user_text = make_user_profile_text(user_profile)
1015
  return model_bert.encode(user_text, convert_to_numpy=True)
1016
 
1017
  def extract_hobby(desc):
1018
+ """ ์˜ˆ: "PT ์ „๋ฌธ ์ฝ”์น˜... (์šด๋™, ํ—ฌ์Šค)" -> "์šด๋™, ํ—ฌ์Šค" """
1019
  match = re.search(r"\((.*?)\)", desc)
1020
  return match.group(1) if match else ""
1021
 
 
1023
  norm1 = np.linalg.norm(vec1)
1024
  norm2 = np.linalg.norm(vec2)
1025
  if norm1 == 0 or norm2 == 0:
1026
+ return 0.0
1027
+ return float(np.dot(vec1, vec2) / (norm1 * norm2))
1028
 
1029
  def recommend_content_based(user_profile, top_n=5):
1030
  user_emb = get_user_embedding(user_profile)
1031
  scored = []
1032
 
1033
+ # ๐ŸŸข ๋‹ค์ค‘ ์ทจ๋ฏธ/์„ธ๋ถ€์ทจ๋ฏธ๋ฅผ ๋ฆฌ์ŠคํŠธ๋กœ ๊ฐ€์ •
1034
+ user_hobbies = user_profile.get("hobby", []) or []
1035
+ user_details = user_profile.get("detail_hobby", []) or []
1036
+
1037
  user_extroversion = user_profile.get("extroversion", "")
1038
  user_feeling_thinking = user_profile.get("feeling_thinking", "")
1039
 
 
1042
  item_emb = item_embedding_dict[item_id]
1043
  sim = cosine_similarity(user_emb, item_emb)
1044
 
1045
+ # ๊ธฐ๋ณธ ๊ฐ€์ค‘์น˜
1046
  weight = 1.0
1047
 
1048
+ # (1) ์ทจ๋ฏธ ๊ฐ€์ค‘์น˜
1049
+ desc_hobby = extract_hobby(item["desc"]) # ์˜ˆ: "(์šด๋™, ํ—ฌ์Šค)"
1050
+
1051
+ # [hobby] ๊ฐ ์š”์†Œ๊ฐ€ desc_hobby์— ์žˆ์œผ๋ฉด ๊ฐ€์ค‘์น˜ ๋ถ€์—ฌ
1052
+ for h in user_hobbies:
1053
+ if h in desc_hobby:
1054
+ weight *= 1.05
1055
+
1056
+ # [detail_hobby] ๊ฐ ์š”์†Œ๊ฐ€ desc_hobby์— ์žˆ์œผ๋ฉด ๊ฐ€์ค‘์น˜ ๋ถ€์—ฌ
1057
+ for dh in user_details:
1058
+ if dh in desc_hobby:
1059
+ weight *= 1.2
1060
 
1061
  # (2) ์„ฑํ–ฅ
1062
+ # ์„ฑํ–ฅ 2๊ฐœ(์™ธํ–ฅํ˜•/๋‚ดํ–ฅํ˜•, ๊ฐ์ •ํ˜•/์ด์„ฑํ˜•) ์ค‘ ๋ช‡ ๊ฐœ๊ฐ€ ๋งค์นญ๋˜๋Š”์ง€
1063
+ personality_match_count = sum(
1064
+ trait in item["personality"]
1065
+ for trait in [user_extroversion, user_feeling_thinking]
1066
+ )
1067
  if personality_match_count == 1:
1068
  weight *= 1.15
1069
  elif personality_match_count == 2:
 
1072
  final_score = sim * weight
1073
  scored.append((item, final_score))
1074
 
1075
+ # ์ ์ˆ˜๊ฐ€ ๋†’์€ ์ˆœ์œผ๋กœ ์ •๋ ฌ
1076
  scored.sort(key=lambda x: x[1], reverse=True)
1077
  return scored[:top_n]
1078
 
 
1135
  def home():
1136
  return {"message": "์•ˆ๋…•ํ•˜์„ธ์š”! ์ถ”์ฒœ & ์ฑ—๋ด‡ FastAPI ์„œ๋ฒ„์ž…๋‹ˆ๋‹ค."}
1137
 
1138
+
1139
+ # (2) ์ถ”์ฒœ์šฉ ๋ชจ๋ธ (๋‹จ์ผ hobby / detail_hobby) - ๊ธฐ์กด ํ˜ธํ™˜์šฉ
1140
  class UserProfile(BaseModel):
1141
  extroversion: str
1142
  feeling_thinking: str
 
1145
 
1146
  @app.post("/recommend")
1147
  def recommend_api(profile: UserProfile):
 
1148
  top_items = recommend_content_based(profile.dict(), top_n=5)
1149
  results = []
1150
  for (item, score) in top_items:
 
1157
  })
1158
  return {"recommendations": results}
1159
 
1160
+
1161
+ # (3) ์ฑ—๋ด‡์šฉ ๋ชจ๋ธ + ๋‹ค์ค‘ ์ทจ๋ฏธ/์„ธ๋ถ€์ทจ๋ฏธ ๋Œ€์‘
1162
  class ChatRequest(BaseModel):
1163
  user_input: str
1164
+ mode: str # "emotion" or "rational"
 
 
 
1165
 
1166
+ RECOMMEND_KEYWORDS = ["์ถ”์ฒœ", "์ถ”์ฒœํ•ด์ค˜", "์ทจ๋ฏธ ์ถ”์ฒœ"]
 
 
 
 
 
1167
 
 
 
 
 
1168
 
1169
+ # (4) ์ฑ—๋ด‡ + ์ถ”์ฒœ ์ž๋™ ๋ถ„๊ธฐ์šฉ ๋ชจ๋ธ
1170
  class ChatOrRecommendRequest(BaseModel):
1171
  user_input: str # ์‚ฌ์šฉ์ž์˜ ์ฑ„ํŒ… ๋ฉ”์‹œ์ง€
1172
+ mode: str # "emotion" ๋˜๋Š” "rational"
1173
 
1174
+ # ๐ŸŸข ๋‹ค์ค‘ ์„ ํƒ ๊ฐ€๋Šฅํ•˜๋„๋ก List[str]๋กœ ๋ณ€๊ฒฝ
1175
  extroversion: Optional[str] = None
1176
  feeling_thinking: Optional[str] = None
1177
+ hobby: Optional[List[str]] = None
1178
+ detail_hobby: Optional[List[str]] = None
1179
 
1180
+
1181
+ # (5) ์ž๋™ ๋ถ„๊ธฐ ์—”๋“œํฌ์ธํŠธ
1182
  @app.post("/chat_or_recommend")
1183
  def chat_or_recommend(req: ChatOrRecommendRequest):
1184
  user_text = req.user_input
1185
  mode = req.mode.lower()
1186
 
1187
+ # โ–ถ 1) "์ถ”์ฒœ" ํ‚ค์›Œ๋“œ ํฌํ•จ ์‹œ โ†’ ์ถ”์ฒœ
1188
  if any(keyword in user_text for keyword in RECOMMEND_KEYWORDS):
1189
+ # ํ”„๋กœํ•„ ๋ฏธ์ž…๋ ฅ ์‹œ ์˜ˆ์™ธ ์ฒ˜๋ฆฌ
1190
+ # (์—ฌ๊ธฐ์„œ๋Š” hobby / detail_hobby๊ฐ€ ์ตœ์†Œํ•œ 1๊ฐœ ์ด์ƒ ์žˆ๋‹ค๊ณ  ๊ฐ€์ •)
1191
  if not req.hobby:
 
 
1192
  raise HTTPException(
1193
  status_code=400,
1194
+ detail="์ถ”์ฒœ์„ ์œ„ํ•ด [hobby, detail_hobby, extroversion, feeling_thinking] ์ •๋ณด๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค."
1195
  )
1196
+
1197
+ # (A) ์ถ”์ฒœ ๋กœ์ง
1198
  user_profile = {
1199
  "extroversion": req.extroversion or "",
1200
  "feeling_thinking": req.feeling_thinking or "",
1201
+ "hobby": req.hobby, # list
1202
+ "detail_hobby": req.detail_hobby or [],
1203
  }
1204
  top_items = recommend_content_based(user_profile, top_n=5)
 
1205
  results = []
1206
  for (item, score) in top_items:
1207
  results.append({
 
1211
  "personality": item["personality"],
1212
  "score": round(score, 4)
1213
  })
1214
+ return {"mode": "recommend", "recommendations": results}
 
 
 
 
1215
 
1216
  else:
1217
+ # โ–ถ 2) ์ฑ—๋ด‡ ๋กœ์ง
1218
  if mode not in ["emotion", "rational"]:
1219
  raise HTTPException(
1220
  status_code=400,
1221
  detail="mode๋Š” 'emotion' ๋˜๋Š” 'rational'์ด์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค."
1222
  )
1223
 
 
1224
  reply = chat_response(user_text, mode=mode)
1225
+ return {"mode": "chat", "response": reply}