Skip to content

Commit 5b933d6

Browse files
committed
[feat] 태그 추출 모델
1 parent 015fba4 commit 5b933d6

3 files changed

Lines changed: 187 additions & 0 deletions

File tree

src/Models.py

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
import json
2+
import re
3+
import os
4+
from dotenv import load_dotenv
5+
import threading
6+
import json
7+
import google.generativeai as genai
8+
from groq import Groq
9+
10+
from src.TagCreater.READMEFetcher import GetREADME
11+
from src.TagCreater.TagMerger import MergeCleanTags
12+
13+
14+
results = {}
15+
16+
17+
# <think>와 </think> 사이의 내용을 포함하여 모두 제거
18+
def RemoveThink(text):
19+
cleaned = re.sub(r"<think>.*?</think>", "", text, flags=re.DOTALL)
20+
return cleaned
21+
22+
23+
# 응답에서 tags 키 아래의 JSON 목록을 추출하는 함수
24+
def ExtractJson(text):
25+
match = re.search(r'({\s*"tags"\s*:\s*\[.*?\]\s*})', text, re.DOTALL)
26+
if match:
27+
try:
28+
return json.loads(match.group(1))["tags"]
29+
except json.JSONDecodeError:
30+
print("JSON parsing failed.")
31+
return []
32+
raise ValueError("Valid JSON format not found.")
33+
34+
35+
# LLM 호출 함수
36+
def CallLLM(modelName, readmeContent, client):
37+
if not readmeContent:
38+
print(f"README.md not found. ({modelName})")
39+
return
40+
41+
try:
42+
chatCompletion = client.chat.completions.create(
43+
messages=[
44+
{
45+
"role": "system",
46+
"content": (
47+
"Extract key technologies in JSON format with this format only:\n"
48+
'{ "tags": ["tech1", "tech2", ...] }\n'
49+
"Return only JSON. No explanation."
50+
),
51+
},
52+
{"role": "user", "content": readmeContent[:1000]}, # 1000자 제한
53+
],
54+
model=modelName,
55+
temperature=0,
56+
)
57+
58+
content = chatCompletion.choices[0].message.content
59+
print(f"\n[{modelName}] Raw Output:\n{content}\n{'-'*50}")
60+
61+
results[modelName] = ExtractJson(RemoveThink(content))
62+
63+
except Exception as e:
64+
print(f"Exception in {modelName}: {e}")
65+
66+
67+
# Gemini 호출 함수
68+
def CallGemini(readme_content, gemini_model):
69+
if not readme_content:
70+
print("README.md not found. (Gemini)")
71+
return
72+
73+
try:
74+
prompt = (
75+
"Extract key technologies in JSON format like:\n"
76+
'{ "tags": ["tech1", "tech2", ...] }\n'
77+
"Return only JSON. No explanation."
78+
)
79+
response = gemini_model.generate_content(
80+
f"{prompt}\n\nUser: {readme_content[:1000]}" # 1000자 제한
81+
)
82+
83+
print(f"\n[Gemini] Raw Output:\n{response.text}\n{'-'*50}")
84+
85+
results["gemini"] = ExtractJson(RemoveThink(response.text))
86+
87+
except Exception as e:
88+
print(f"Gemini exception: {e}")
89+
90+
91+
# Model 실행 함수
92+
def ModelThreading(url):
93+
envPath = os.path.join(os.path.dirname(__file__), "..", ".env")
94+
load_dotenv(dotenv_path=os.path.abspath(envPath))
95+
GROQ_API_KEY = os.getenv("GROQ_API_KEY")
96+
GOOGLE_API_KEY = os.getenv("GOOGLE_API_KEY")
97+
GITHUB_TOKEN = os.getenv("GITHUB_TOKEN")
98+
99+
# Input
100+
readmeContent = GetREADME(url, GITHUB_TOKEN)
101+
102+
# 모델 부르기
103+
client = Groq(api_key=GROQ_API_KEY)
104+
genai.configure(api_key=GOOGLE_API_KEY)
105+
gemini = genai.GenerativeModel("gemini-2.0-flash-thinking-exp-01-21")
106+
107+
# LLM Thread에서 호출
108+
models = ["gemma2-9b-it", "llama-3.3-70b-versatile"]
109+
threads = [
110+
threading.Thread(target=CallLLM, args=(model, readmeContent, client))
111+
for model in models
112+
]
113+
for thread in threads:
114+
thread.start()
115+
116+
geminiThread = threading.Thread(target=CallGemini, args=(readmeContent, gemini))
117+
threads.append(geminiThread)
118+
geminiThread.start()
119+
120+
for thread in threads:
121+
thread.join()
122+
123+
# Output
124+
for model, tags in results.items():
125+
print(f"Model: {model}")
126+
print(tags)
127+
print("-" * 50)
128+
129+
FinalTags = MergeCleanTags(*[results[m] for m in results])
130+
resultJson = {"tags": FinalTags}
131+
132+
refiner = genai.GenerativeModel("gemini-2.0-flash-thinking-exp-01-21")
133+
response = refiner.generate_content(
134+
f'{json.dumps(resultJson, ensure_ascii=False)} Extract only key technologies in JSON format as "tags": []'
135+
)
136+
137+
return response

src/READMEFetcher.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import requests
2+
from urllib.parse import urlparse
3+
import base64
4+
5+
6+
# URL에서 owner와 repo 이름을 추출하는 함수
7+
def RepoInfo(repoURL):
8+
url = urlparse(repoURL)
9+
path = url.path.strip("/").split("/")
10+
return (path[0], path[1]) if len(path) >= 2 else (None, None)
11+
12+
13+
# GitHub API를 사용하여 README.md 파일을 가져오는 함수
14+
def GetREADME(repoURL, gitToken=None):
15+
owner, repo = RepoInfo(repoURL)
16+
if not owner or not repo:
17+
print("Invalid GitHub URL.")
18+
return None
19+
20+
apiURL = f"https://api.github.com/repos/{owner}/{repo}/readme"
21+
headers = {"Accept": "application/vnd.github.v3+json"}
22+
if gitToken:
23+
headers = {"Authorization": f"Bearer {gitToken}"} if gitToken else {}
24+
25+
response = requests.get(apiURL, headers=headers)
26+
if response.status_code == 200:
27+
return base64.b64decode(response.json()["content"]).decode("utf-8")
28+
29+
return None

src/TagMerger.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
from fuzzywuzzy import fuzz
2+
3+
4+
# Tag에서 중복 제거
5+
def MergeCleanTags(*tagLists, threshold=80):
6+
tags = []
7+
for tagList in tagLists:
8+
for item in tagList:
9+
if not any(
10+
fuzz.ratio(item.lower(), existing.lower()) >= threshold
11+
for existing in tags
12+
):
13+
tags.append(item)
14+
15+
# 줄임말 제거
16+
finalTags = tags[:]
17+
for item in tags:
18+
for other in tags:
19+
if item != other and other in item and other in finalTags:
20+
finalTags.remove(other)
21+
return finalTags

0 commit comments

Comments
 (0)