Madras1 commited on
Commit
9f89048
·
verified ·
1 Parent(s): 461f5b2

Upload 4 files

Browse files
Files changed (4) hide show
  1. Dockerfile +21 -0
  2. README.md +27 -11
  3. app.py +274 -0
  4. requirements.txt +17 -0
Dockerfile ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.10-slim
2
+
3
+ WORKDIR /app
4
+
5
+ # Instalar dependências do sistema
6
+ RUN apt-get update && apt-get install -y \
7
+ build-essential \
8
+ && rm -rf /var/lib/apt/lists/*
9
+
10
+ # Copiar e instalar dependências Python
11
+ COPY requirements.txt .
12
+ RUN pip install --no-cache-dir -r requirements.txt
13
+
14
+ # Copiar código
15
+ COPY app.py .
16
+
17
+ # Expor porta do HuggingFace Spaces
18
+ EXPOSE 7860
19
+
20
+ # Rodar servidor
21
+ CMD ["uvicorn", "app:app", "--host", "0.0.0.0", "--port", "7860"]
README.md CHANGED
@@ -1,11 +1,27 @@
1
- ---
2
- title: StrandDemo
3
- emoji: 🌍
4
- colorFrom: green
5
- colorTo: gray
6
- sdk: docker
7
- pinned: false
8
- license: apache-2.0
9
- ---
10
-
11
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Strand Data Demo - HuggingFace Spaces README
2
+
3
+ ---
4
+ title: Strand Data Demo API
5
+ emoji: 🧬
6
+ colorFrom: purple
7
+ colorTo: cyan
8
+ sdk: docker
9
+ pinned: false
10
+ ---
11
+
12
+ # Strand Data Demo API
13
+
14
+ API de demonstração para classificação de qualidade de texto, Q&A e image captioning.
15
+
16
+ ## Endpoints
17
+
18
+ - `POST /classify-quality` - Classifica qualidade de texto (sBERT)
19
+ - `POST /qa` - Responde perguntas sobre um texto (LLM)
20
+ - `POST /caption` - Gera legenda para imagem (Vision LLM)
21
+
22
+ ## Configuração
23
+
24
+ Defina as seguintes secrets no Space:
25
+ - `CHUTES_API_KEY` - API key do Chutes.ai
26
+ - `OPENROUTER_API_KEY` - API key do OpenRouter (fallback)
27
+ - `SBERT_MODEL_NAME` - Nome do modelo sBERT no HuggingFace Hub
app.py ADDED
@@ -0,0 +1,274 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Strand Data - Demo Backend
3
+ Deploy em HuggingFace Spaces
4
+
5
+ Endpoints:
6
+ - POST /classify-quality: Classifica qualidade de texto com sBERT
7
+ - POST /qa: Q&A sobre texto usando LLM
8
+ - POST /caption: Gera descrição de imagem
9
+ """
10
+
11
+ import os
12
+ import base64
13
+ import httpx
14
+ from fastapi import FastAPI, HTTPException
15
+ from fastapi.middleware.cors import CORSMiddleware
16
+ from pydantic import BaseModel
17
+ from sentence_transformers import SentenceTransformer
18
+ import numpy as np
19
+ from typing import Optional
20
+
21
+ app = FastAPI(title="Strand Data Demo API")
22
+
23
+ # CORS para permitir requests do frontend
24
+ app.add_middleware(
25
+ CORSMiddleware,
26
+ allow_origins=["*"], # Em produção, restringir ao domínio do site
27
+ allow_credentials=True,
28
+ allow_methods=["*"],
29
+ allow_headers=["*"],
30
+ )
31
+
32
+ # ================================
33
+ # Configuração
34
+ # ================================
35
+
36
+ # API Keys (usar secrets do HuggingFace)
37
+ CHUTES_API_KEY = os.getenv("CHUTES_API_KEY", "")
38
+ OPENROUTER_API_KEY = os.getenv("OPENROUTER_API_KEY", "")
39
+
40
+ # Modelo sBERT (substitua pelo seu modelo no HF Hub)
41
+ SBERT_MODEL_NAME = os.getenv("SBERT_MODEL_NAME", "sentence-transformers/all-MiniLM-L6-v2")
42
+
43
+ # Carregar modelo sBERT
44
+ print(f"Carregando modelo sBERT: {SBERT_MODEL_NAME}")
45
+ sbert_model = SentenceTransformer(SBERT_MODEL_NAME)
46
+
47
+ # ================================
48
+ # Anchor Quality Embeddings
49
+ # ================================
50
+
51
+ # Exemplos de textos de alta qualidade (âncoras)
52
+ # Gabriel: substitua por textos reais do seu dataset de qualidade
53
+ HIGH_QUALITY_ANCHORS = [
54
+ "Este artigo apresenta uma análise detalhada dos métodos de aprendizado de máquina aplicados à visão computacional, com resultados quantitativos robustos.",
55
+ "O estudo demonstra correlação significativa entre as variáveis analisadas, utilizando metodologia rigorosa e amostra representativa.",
56
+ "A implementação do algoritmo proposto apresenta complexidade O(n log n), com benchmarks comparativos contra soluções estado-da-arte.",
57
+ ]
58
+
59
+ LOW_QUALITY_ANCHORS = [
60
+ "oi gente hj vou falar sobre ia eh mt legal ne",
61
+ "entao tipo assim a coisa funciona mais ou menos",
62
+ "nao sei explicar direito mas acho q eh isso ai",
63
+ ]
64
+
65
+ # Pré-computar embeddings das âncoras
66
+ print("Computando embeddings das âncoras de qualidade...")
67
+ high_quality_embeddings = sbert_model.encode(HIGH_QUALITY_ANCHORS)
68
+ low_quality_embeddings = sbert_model.encode(LOW_QUALITY_ANCHORS)
69
+
70
+ # Média dos embeddings para cada classe
71
+ high_quality_centroid = np.mean(high_quality_embeddings, axis=0)
72
+ low_quality_centroid = np.mean(low_quality_embeddings, axis=0)
73
+
74
+ # ================================
75
+ # Modelos de Request/Response
76
+ # ================================
77
+
78
+ class QualityRequest(BaseModel):
79
+ text: str
80
+
81
+ class QualityResponse(BaseModel):
82
+ quality: str # "high" ou "low"
83
+ score: float # 0-100, quanto maior mais qualidade
84
+ high_similarity: float
85
+ low_similarity: float
86
+
87
+ class QARequest(BaseModel):
88
+ context: str
89
+ question: str
90
+
91
+ class QAResponse(BaseModel):
92
+ answer: str
93
+
94
+ class CaptionRequest(BaseModel):
95
+ image_base64: str # Imagem em base64
96
+
97
+ class CaptionResponse(BaseModel):
98
+ caption: str
99
+
100
+ # ================================
101
+ # Funções Utilitárias
102
+ # ================================
103
+
104
+ def cosine_similarity(a: np.ndarray, b: np.ndarray) -> float:
105
+ """Calcula similaridade de cosseno entre dois vetores."""
106
+ return float(np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b)))
107
+
108
+ async def call_llm(prompt: str, system: str = "", max_tokens: int = 500) -> str:
109
+ """Chama LLM via Chutes ou OpenRouter."""
110
+
111
+ # Tentar Chutes primeiro
112
+ if CHUTES_API_KEY:
113
+ try:
114
+ async with httpx.AsyncClient(timeout=30) as client:
115
+ response = await client.post(
116
+ "https://llm.chutes.ai/v1/chat/completions",
117
+ headers={
118
+ "Authorization": f"Bearer {CHUTES_API_KEY}",
119
+ "Content-Type": "application/json"
120
+ },
121
+ json={
122
+ "model": "deepseek-ai/DeepSeek-V3-0324",
123
+ "messages": [
124
+ {"role": "system", "content": system} if system else None,
125
+ {"role": "user", "content": prompt}
126
+ ],
127
+ "max_tokens": max_tokens,
128
+ "temperature": 0.7
129
+ }
130
+ )
131
+ if response.status_code == 200:
132
+ return response.json()["choices"][0]["message"]["content"]
133
+ except Exception as e:
134
+ print(f"Erro Chutes: {e}")
135
+
136
+ # Fallback para OpenRouter
137
+ if OPENROUTER_API_KEY:
138
+ try:
139
+ async with httpx.AsyncClient(timeout=30) as client:
140
+ response = await client.post(
141
+ "https://openrouter.ai/api/v1/chat/completions",
142
+ headers={
143
+ "Authorization": f"Bearer {OPENROUTER_API_KEY}",
144
+ "Content-Type": "application/json"
145
+ },
146
+ json={
147
+ "model": "meta-llama/llama-3.3-70b-instruct",
148
+ "messages": [
149
+ {"role": "system", "content": system} if system else None,
150
+ {"role": "user", "content": prompt}
151
+ ],
152
+ "max_tokens": max_tokens
153
+ }
154
+ )
155
+ if response.status_code == 200:
156
+ return response.json()["choices"][0]["message"]["content"]
157
+ except Exception as e:
158
+ print(f"Erro OpenRouter: {e}")
159
+
160
+ raise HTTPException(status_code=503, detail="Nenhuma API de LLM disponível")
161
+
162
+ async def call_vision_llm(image_base64: str, prompt: str) -> str:
163
+ """Chama LLM multimodal para image captioning."""
164
+
165
+ # Usar Chutes com modelo multimodal
166
+ if CHUTES_API_KEY:
167
+ try:
168
+ async with httpx.AsyncClient(timeout=60) as client:
169
+ response = await client.post(
170
+ "https://llm.chutes.ai/v1/chat/completions",
171
+ headers={
172
+ "Authorization": f"Bearer {CHUTES_API_KEY}",
173
+ "Content-Type": "application/json"
174
+ },
175
+ json={
176
+ "model": "Qwen/Qwen2.5-VL-72B-Instruct",
177
+ "messages": [
178
+ {
179
+ "role": "user",
180
+ "content": [
181
+ {"type": "text", "text": prompt},
182
+ {"type": "image_url", "image_url": {"url": f"data:image/jpeg;base64,{image_base64}"}}
183
+ ]
184
+ }
185
+ ],
186
+ "max_tokens": 300
187
+ }
188
+ )
189
+ if response.status_code == 200:
190
+ return response.json()["choices"][0]["message"]["content"]
191
+ except Exception as e:
192
+ print(f"Erro Vision Chutes: {e}")
193
+
194
+ raise HTTPException(status_code=503, detail="API de visão não disponível")
195
+
196
+ # ================================
197
+ # Endpoints
198
+ # ================================
199
+
200
+ @app.get("/")
201
+ async def root():
202
+ return {"message": "Strand Data Demo API", "status": "online"}
203
+
204
+ @app.get("/health")
205
+ async def health():
206
+ return {"status": "healthy", "model_loaded": sbert_model is not None}
207
+
208
+ @app.post("/classify-quality", response_model=QualityResponse)
209
+ async def classify_quality(request: QualityRequest):
210
+ """
211
+ Classifica a qualidade de um texto usando sBERT.
212
+ Compara o embedding do texto com âncoras de alta/baixa qualidade.
213
+ """
214
+ # Gerar embedding do texto
215
+ text_embedding = sbert_model.encode(request.text)
216
+
217
+ # Calcular similaridade com cada centróide
218
+ high_sim = cosine_similarity(text_embedding, high_quality_centroid)
219
+ low_sim = cosine_similarity(text_embedding, low_quality_centroid)
220
+
221
+ # Normalizar para score 0-100
222
+ # Quanto mais próximo de alta qualidade e distante de baixa, maior o score
223
+ raw_score = (high_sim - low_sim + 1) / 2 # Normaliza para 0-1
224
+ score = round(raw_score * 100, 2)
225
+
226
+ quality = "high" if high_sim > low_sim else "low"
227
+
228
+ return QualityResponse(
229
+ quality=quality,
230
+ score=score,
231
+ high_similarity=round(high_sim, 4),
232
+ low_similarity=round(low_sim, 4)
233
+ )
234
+
235
+ @app.post("/qa", response_model=QAResponse)
236
+ async def question_answering(request: QARequest):
237
+ """
238
+ Responde perguntas sobre um texto usando LLM.
239
+ """
240
+ system_prompt = """Você é um assistente especializado em responder perguntas sobre textos.
241
+ Responda de forma precisa e concisa, baseando-se APENAS no contexto fornecido.
242
+ Se a resposta não estiver no contexto, diga "Não encontrei essa informação no texto."
243
+ Responda em português."""
244
+
245
+ prompt = f"""CONTEXTO:
246
+ {request.context}
247
+
248
+ PERGUNTA:
249
+ {request.question}
250
+
251
+ RESPOSTA:"""
252
+
253
+ answer = await call_llm(prompt, system_prompt, max_tokens=300)
254
+ return QAResponse(answer=answer.strip())
255
+
256
+ @app.post("/caption", response_model=CaptionResponse)
257
+ async def generate_caption(request: CaptionRequest):
258
+ """
259
+ Gera uma descrição/legenda para uma imagem.
260
+ """
261
+ prompt = """Descreva esta imagem em detalhes.
262
+ Inclua: objetos principais, cores, ações, ambiente/cenário.
263
+ Responda em português, em 2-3 frases."""
264
+
265
+ caption = await call_vision_llm(request.image_base64, prompt)
266
+ return CaptionResponse(caption=caption.strip())
267
+
268
+ # ================================
269
+ # Para rodar localmente
270
+ # ================================
271
+
272
+ if __name__ == "__main__":
273
+ import uvicorn
274
+ uvicorn.run(app, host="0.0.0.0", port=7860)
requirements.txt ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Strand Data Demo - Backend Dependencies
2
+
3
+ # FastAPI e servidor
4
+ fastapi==0.109.0
5
+ uvicorn[standard]==0.27.0
6
+ python-multipart==0.0.6
7
+
8
+ # HTTP client assíncrono
9
+ httpx==0.26.0
10
+
11
+ # Sentence Transformers (sBERT)
12
+ sentence-transformers==2.2.2
13
+ torch>=2.0.0
14
+
15
+ # Utilidades
16
+ numpy>=1.24.0
17
+ pydantic>=2.0.0