rrg92 commited on
Commit
c9fd6d7
·
1 Parent(s): 4582c12

Lot of enhacements

Browse files
Files changed (4) hide show
  1. Dockerfile +7 -3
  2. app.py +368 -10
  3. docker-compose.yml +13 -0
  4. requirements.txt +5 -1
Dockerfile CHANGED
@@ -5,17 +5,21 @@ ARG DEBIAN_FRONTEND=noninteractive
5
  # apt-get install --no-install-recommends -y sox libsox-fmt-all curl wget gcc git git-lfs build-essential libaio-dev libsndfile1 ssh ffmpeg && \
6
  # apt-get clean && apt-get -y autoremove
7
 
 
 
 
 
8
  WORKDIR /app
9
  COPY requirements.txt .
10
  RUN python -m pip install --verbose -r requirements.txt
11
  RUN python -m pip cache purge
12
 
13
 
14
- RUN python -m pip install spaces
15
-
16
- COPY . .
17
  RUN chmod +x InstallFromReadme.sh
18
  RUN ./InstallFromReadme.sh
19
 
 
20
 
21
  CMD ["python","app.py"]
 
5
  # apt-get install --no-install-recommends -y sox libsox-fmt-all curl wget gcc git git-lfs build-essential libaio-dev libsndfile1 ssh ffmpeg && \
6
  # apt-get clean && apt-get -y autoremove
7
 
8
+ RUN apt-get update
9
+
10
+ RUN python -m pip install spaces
11
+
12
  WORKDIR /app
13
  COPY requirements.txt .
14
  RUN python -m pip install --verbose -r requirements.txt
15
  RUN python -m pip cache purge
16
 
17
 
18
+ COPY InstallFromReadme.sh .
19
+ COPY README.md .
 
20
  RUN chmod +x InstallFromReadme.sh
21
  RUN ./InstallFromReadme.sh
22
 
23
+ COPY . .
24
 
25
  CMD ["python","app.py"]
app.py CHANGED
@@ -3,28 +3,386 @@ import spaces
3
  from sentence_transformers import SentenceTransformer
4
  from sentence_transformers.util import cos_sim
5
  from sentence_transformers.quantization import quantize_embeddings
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6
 
7
- print("Loading embedding model");
8
- dimensions = 768
9
- model = SentenceTransformer("mixedbread-ai/mxbai-embed-large-v1", truncate_dim=dimensions)
10
 
11
  @spaces.GPU
12
  def embed(text):
13
 
14
- query_embedding = model.encode(text, prompt_name="query")
15
  return query_embedding.tolist();
16
 
 
 
 
 
17
 
 
 
 
 
18
 
19
- with gr.Blocks() as demo:
20
- txtEmbed = gr.Text(label="Text to embed")
21
- btnEmbed = gr.Button("embed");
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
22
 
23
- search = gr.Text(label="Script to search")
24
 
25
- results = gr.Text(label="results");
26
 
27
- btnEmbed.click(embed, [txtEmbed], [results])
28
 
29
 
30
 
 
3
  from sentence_transformers import SentenceTransformer
4
  from sentence_transformers.util import cos_sim
5
  from sentence_transformers.quantization import quantize_embeddings
6
+ import pymssql
7
+ import os
8
+ import pandas as pd
9
+ from openai import OpenAI
10
+ from pydantic import BaseModel, Field
11
+ import json
12
+ from sentence_transformers import CrossEncoder
13
+ from torch import nn
14
+ import time
15
+
16
+
17
+ SqlServer = os.environ['SQL_SERVER']
18
+ SqlDatabase = os.environ['SQL_DB']
19
+ SqlUser = os.environ['SQL_USER']
20
+ SqlPass = os.environ['SQL_PASS']
21
+
22
+
23
+ OpenaiApiKey = os.environ.get("OPENAI_API_KEY")
24
+ OpenaiBaseUrl = os.environ.get("OPENAI_BASE_URL","https://generativelanguage.googleapis.com/v1beta/openai")
25
+
26
+
27
+ def sql(query,db=SqlDatabase, login_timeout = 120,onConnectionError = None):
28
+
29
+ start_time = time.time()
30
+
31
+ while True:
32
+ try:
33
+ cnxn = pymssql.connect(SqlServer,SqlUser,SqlPass,db, login_timeout = 5)
34
+ break;
35
+ except Exception as e:
36
+ if onConnectionError:
37
+ onConnectionError(e)
38
+
39
+ if time.time() - start_time > login_timeout:
40
+ raise TimeoutError("SQL Connection Timeout");
41
+
42
+ time.sleep(1) # Espera 1 segundo antes de tentar novamente
43
+
44
+
45
+ cursor = cnxn.cursor()
46
+ cursor.execute(query)
47
+
48
+ columns = [column[0] for column in cursor.description]
49
+ results = [dict(zip(columns, row)) for row in cursor.fetchall()]
50
+
51
+ return results;
52
+
53
 
 
 
 
54
 
55
  @spaces.GPU
56
  def embed(text):
57
 
58
+ query_embedding = Embedder.encode(text)
59
  return query_embedding.tolist();
60
 
61
+
62
+ @spaces.GPU
63
+ def rerank(query,documents, **kwargs):
64
+ return Reranker.rank(query, documents, **kwargs)
65
 
66
+ ClientOpenai = OpenAI(
67
+ api_key=OpenaiApiKey
68
+ ,base_url=OpenaiBaseUrl
69
+ )
70
 
71
+ def llm(messages, ResponseFormat = None, **kwargs):
72
+
73
+ fn = ClientOpenai.chat.completions.create
74
+
75
+ if ResponseFormat:
76
+ fn = ClientOpenai.beta.chat.completions.parse
77
+
78
+ params = {
79
+ 'model':"gemini-2.0-flash"
80
+ ,'n':1
81
+ ,'messages':messages
82
+ ,'response_format':ResponseFormat
83
+ }
84
+
85
+ params.update(kwargs);
86
+
87
+ response = fn(**params)
88
+
89
+ if params.get('stream'):
90
+ return response
91
+
92
+ return response.choices[0];
93
+
94
+ def ai(system,user, schema, **kwargs):
95
+ msg = [
96
+ {'role':"system",'content':system}
97
+ ,{'role':"user",'content':user}
98
+ ]
99
+
100
+ return llm(msg, schema, **kwargs);
101
+
102
+
103
+ def search(text, top = 10, onConnectionError = None):
104
+
105
+ EnglishText = text
106
+
107
+ embeddings = embed(text);
108
+
109
+ query = f"""
110
+ declare @search vector(1024) = '{embeddings}'
111
+
112
+ select top {top}
113
+ *
114
+ from (
115
+ select
116
+ RelPath
117
+ ,Similaridade = 1-CosDistance
118
+ ,ScriptContent = ChunkContent
119
+ ,ContentLength = LEN(ChunkContent)
120
+ ,CosDistance
121
+ from
122
+ (
123
+ select
124
+ *
125
+ ,CosDistance = vector_distance('cosine',embeddings,@search)
126
+ from
127
+ Scripts
128
+ ) C
129
+ ) v
130
+ order by
131
+ CosDistance
132
+ """
133
+
134
+ queryResults = sql(query, onConnectionError = onConnectionError);
135
+
136
+
137
+
138
+ return queryResults
139
+
140
+
141
+ print("Loading embedding model");
142
+ Embedder = SentenceTransformer("mixedbread-ai/mxbai-embed-large-v1")
143
+
144
+ print("Loading reranker");
145
+ Reranker = CrossEncoder("mixedbread-ai/mxbai-rerank-large-v1", activation_fn=nn.Sigmoid())
146
+
147
+ class rfTranslatedText(BaseModel):
148
+ text: str = Field(description='Translated text')
149
+
150
+ class rfGenericText(BaseModel):
151
+ text: str = Field(description='The text result')
152
+
153
+ def ChatFunc(message, history):
154
+
155
+
156
+ # Determinar se o user quer fazer uma nova pesquisa!
157
+ IsNewSearch = True;
158
+
159
+ messages = []
160
+ CurrentTable = None;
161
+
162
+ def ChatBotOutput():
163
+ return [messages,CurrentTable]
164
+
165
+ class BotMessage():
166
+ def __init__(self, *args, **kwargs):
167
+ self.Message = gr.ChatMessage(*args, **kwargs)
168
+ self.LastContent = None
169
+ messages.append(self.Message);
170
+
171
+ def __call__(self, content, noNewLine = False):
172
+ if not content:
173
+ return;
174
+
175
+ self.Message.content += content;
176
+ self.LastContent = None;
177
+
178
+ if not noNewLine:
179
+ self.Message.content += "\n";
180
+
181
+ return ChatBotOutput();
182
+
183
+ def update(self,content):
184
+
185
+ if not self.LastContent:
186
+ self.LastContent = self.Message.content
187
+
188
+ self.Message.content = self.LastContent +" "+content+"\n";
189
+
190
+ return ChatBotOutput();
191
+
192
+ def done(self):
193
+ self.Message.metadata['status'] = "done";
194
+ return ChatBotOutput();
195
+
196
+ def Reply(msg):
197
+ m = BotMessage(msg);
198
+ return ChatBotOutput();
199
+
200
+ m = BotMessage("",metadata={"title":"Procurando scripts...","status":"pending"});
201
+
202
+
203
+ def OnConnError(err):
204
+ print("Sql connection error:", err)
205
+
206
+
207
+ try:
208
+ # Responder algo sobre o historico!
209
+ if IsNewSearch:
210
+
211
+ yield m("Melhorando o prompt")
212
+
213
+
214
+ LLMResult = ai("""
215
+ Translate the user's message to English.
216
+ The message is a question related to a SQL Server T-SQL script that the user is searching for.
217
+ You only need to translate the message to English.
218
+ """,message, rfTranslatedText)
219
+ Question = LLMResult.message.parsed.text;
220
+
221
+ yield m(f"Melhorado: {Question}")
222
+
223
+ yield m("Procurando scripts...")
224
+ try:
225
+ FoundScripts = search(message, onConnectionError = OnConnError)
226
+ except:
227
+ yield m("Houve alguma falha ao executar a consulta no banco. Tente novamente. Se persistir, veja orientações na aba Help!")
228
+ return;
229
+
230
+ yield m("Fazendo o rerank");
231
+ doclist = [doc['ScriptContent'] for doc in FoundScripts]
232
+
233
+ # Faz o reranker!
234
+ for score in rerank(Question, doclist):
235
+ i = score['corpus_id'];
236
+ FoundScripts[i]['rank'] = str(score['score'])
237
+
238
+ RankedScripts = sorted(FoundScripts, key=lambda item: float(item['rank']), reverse=True)
239
+
240
+
241
+
242
+ ScriptTable = []
243
+ for script in RankedScripts:
244
+ link = "https://github.com/rrg92/sqlserver-lib/tree/main/" + script['RelPath']
245
+ script['link'] = link;
246
+
247
+ ScriptTable.append({
248
+ 'Link': f'<a title="{link}" href="{link}" target="_blank">{script["RelPath"]}</a>'
249
+ ,'Length': script['ContentLength']
250
+ ,'Cosine Similarity': script['Similaridade']
251
+ ,'Rank': script['rank']
252
+ })
253
+
254
+
255
+ CurrentTable = pd.DataFrame(ScriptTable)
256
+ yield m("Script encontrados, a aba Rank atualizada!")
257
+
258
+
259
+ WaitMessage = ai("""
260
+ You will analyze some T-SQL scripts in order to check which is best for the user.
261
+ You found scripts, presented them to the user, and now will do some work that takes time.
262
+ Generate a message to tell the user to wait while you work, in the same language as the user.
263
+ You will receive the question the user sent that triggered this process.
264
+ Use the user’s original question to customize the message.
265
+ """,message,rfGenericText).message.parsed.text
266
+
267
+ yield Reply(WaitMessage);
268
+
269
+ yield m(f"Analisando scripts...")
270
+
271
+
272
+ ResultJson = json.dumps(RankedScripts);
273
+
274
+ SystemPrompt = f"""
275
+ You are an assistant that helps users find the best T-SQL scripts for their specific needs.
276
+ These scripts were created by Rodrigo Ribeiro Gomes and are publicly available for users to query and use.
277
+
278
+ The user will provide a short description of what they are looking for, and your task is to present the most relevant scripts.
279
+
280
+ To assist you, here is a JSON object with the top matches based on the current user query:
281
+ {ResultJson}
282
+
283
+ ---
284
+ This JSON contains all the scripts that matched the user's input.
285
+ Analyze each script's name and content, and create a ranked summary of the best recommendations according to the user's need.
286
+
287
+ Only use the information available in the provided JSON. Do not reference or mention anything outside of this list.
288
+ You can include parts of the scripts in your answer to illustrate or give usage examples based on the user's request.
289
+
290
+ Re-rank the results if necessary, presenting them from the most to the least relevant.
291
+ You may filter out scripts that appear unrelated to the user query.
292
+
293
+ Respond in the user's original language.
294
+ ---
295
+ ### Output Rules
296
+
297
+ - Review each script and evaluate how well it matches the user’s request.
298
+ - Summarize each script, ordering from the most relevant to the least relevant.
299
+ - Write personalized and informative review text for each recommendation.
300
+ - If applicable, explain how the user should run the script, including parameters or sections (like `WHERE` clauses) they might need to customize.
301
+ - When referencing a script, include the link provided in the JSON — all scripts are hosted on GitHub.
302
+ """
303
+
304
+ ScriptPrompt = [
305
+ { 'role':'system', 'content':SystemPrompt }
306
+ ,{ 'role':'user', 'content':message }
307
+ ]
308
+
309
+
310
+
311
+
312
+ llmanswer = llm(ScriptPrompt, stream = True)
313
+ yield m.done()
314
+
315
+ answer = BotMessage("");
316
+
317
+ for chunk in llmanswer:
318
+ content = chunk.choices[0].delta.content
319
+ yield answer(content, noNewLine = True)
320
+ finally:
321
+ yield m.done()
322
+
323
+
324
+ resultTable = gr.Dataframe(datatype = ['html','number','number'], interactive = False, show_search = "search");
325
+
326
+ with gr.Blocks(fill_height=True) as demo:
327
+
328
+ with gr.Column():
329
+
330
+ with gr.Tab("Chat", scale = 1):
331
+ ChatTextBox = gr.Textbox(max_length = 100, info = "Que script precisa?", submit_btn = True);
332
+
333
+ gr.ChatInterface(
334
+ ChatFunc
335
+ ,additional_outputs=[resultTable]
336
+ ,type="messages"
337
+ ,textbox = ChatTextBox
338
+ )
339
+
340
+ with gr.Tab("Rank"):
341
+ resultTable.render();
342
+
343
+ with gr.Tab("Help"):
344
+ gr.Markdown("""
345
+ Bem-vindo ao Space SQL Server Lib
346
+ Este space permite que você encontre scripts SQL do https://github.com/rrg92/sqlserver-lib com base nas suas necessidades
347
+
348
+
349
+ ## Instruções de Uso
350
+ Apenas descreva o que você precisa no campo de chat e aguarde a IA analisar os melhores scripts do repositório para você.
351
+ Além de uma explicação feita pela IA, a aba "Rank", contém uma tabela com os scripts encontrados e seus respectictos rank.
352
+ A coluna Cosine Similarity é o nível de similaridades da sua pergunta com o script (calculado baseado nos embeddings do seu texto e do script).
353
+ A coluna Rank é um score onde quanto maior o valor mais relacionado ao seu texto o script é (calculado usando rerank/cross encoders). A tabela vem ordenada por essa coluna.
354
+
355
+
356
+ ## Fluxo básico
357
+ - Quando você digita o texto, iremos fazer uma busca usando embeddings em um banco Azure SQL Database
358
+ - Os embeddings são calculados usando um modelo carregado no proprio script, via ZeroGPU.
359
+ - Os top 20 resultados mais similares são retornados e então um rerank é feito
360
+ - O rerank também é feito por um modelo que roda no próprio script, em ZeroGPU
361
+ - Estes resultados ordenados por reran, são então enviados ao LLM para que analise e monte uma resposta para você.
362
+
363
+
364
+ ## Sobre o uso e eventuais erros
365
+ Eu tento usar o máximo de recursos FREE e open possíveis, e portanto, eventualmente, o Space pode falhar por alguma limitação.
366
+ Alguns possíveis pontos de falha:
367
+ - Créditos free do google ou rate limit
368
+ - Azure SQL database offline devido a crédito ou ao auto-pause (devido ao free tier)
369
+ - Limites de uso do ZeroGPU do Hugging Face.
370
+
371
+ Você pode me procurar no [linkedin](https://www.linkedin.com/in/rodrigoribeirogomes/), caso receba erroslimit
372
+
373
+ """)
374
+
375
+ with gr.Tab("Other", visible = False):
376
+ txtEmbed = gr.Text(label="Text to embed", visible=False)
377
+ btnEmbed = gr.Button("embed");
378
+ btnEmbed.click(embed, [txtEmbed], [txtEmbed])
379
+
380
+
381
+
382
+
383
 
 
384
 
 
385
 
 
386
 
387
 
388
 
docker-compose.yml CHANGED
@@ -1,5 +1,18 @@
 
 
 
1
  services:
2
  app:
 
 
 
 
 
 
 
 
 
 
3
  build:
4
  context: .
5
  dockerfile: Dockerfile
 
1
+ volumes:
2
+ hfdata:
3
+
4
  services:
5
  app:
6
+ environment:
7
+ SQL_SERVER: $SQL_SERVER
8
+ SQL_DB: $SQL_DB
9
+ SQL_USER: $SQL_USER
10
+ SQL_PASS: $SQL_PASS
11
+ HF_HOME: /hfdata
12
+ HF_TOKEN: $HF_TOKEN
13
+ OPENAI_API_KEY: $OPENAI_API_KEY
14
+ volumes:
15
+ - hfdata:/hfdata
16
  build:
17
  context: .
18
  dockerfile: Dockerfile
requirements.txt CHANGED
@@ -1 +1,5 @@
1
- sentence-transformers
 
 
 
 
 
1
+ sentence-transformers
2
+ pymssql
3
+ pandas
4
+ openai
5
+ pydantic