Muhammet Enes Nas commited on
Commit
fa544c8
·
1 Parent(s): cd48b2c
Files changed (3) hide show
  1. README.md +12 -5
  2. app.py +322 -0
  3. requirements.txt +6 -0
README.md CHANGED
@@ -1,8 +1,8 @@
1
  ---
2
- title: Where Is Pharmacy
3
- emoji: 🔥
4
- colorFrom: purple
5
- colorTo: red
6
  sdk: gradio
7
  sdk_version: 6.21.0
8
  python_version: '3.12'
@@ -10,4 +10,11 @@ app_file: app.py
10
  pinned: false
11
  ---
12
 
13
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
1
  ---
2
+ title: Pharmacy Tool Calling
3
+ emoji: 🏥
4
+ colorFrom: green
5
+ colorTo: gray
6
  sdk: gradio
7
  sdk_version: 6.21.0
8
  python_version: '3.12'
 
10
  pinned: false
11
  ---
12
 
13
+ # Pharmacy Tool Calling Agent
14
+
15
+ This repository implements a Tool-Calling / Function-Calling agent using the fine-tuned LoRA model `menesnas/gemma_4_pharmacy_lora` integrated with **SerpApi Google Maps API**.
16
+
17
+ ## Features
18
+ - **Public API**: SerpApi Google Maps Engine
19
+ - **Tool Calling**: Triggers `get_nearby_pharmacies` tool based on user query.
20
+ - **Step Traceability**: Displays `Turn 1 (Tool Call)`, `Turn 2 (API Result)`, and `Turn 3 (Final Answer)`.
app.py ADDED
@@ -0,0 +1,322 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import json
3
+ import re
4
+ import requests
5
+ import torch
6
+ import gradio as gr
7
+ from transformers import AutoTokenizer, AutoModelForCausalLM
8
+ # ---------------------------------------------------------------------------
9
+ # 1. CONFIG & TOOLS SCHEMA
10
+ # ---------------------------------------------------------------------------
11
+ SERPAPI_KEY = os.getenv("SERPAPI_KEY", "")
12
+ MODEL_ID = "menesnas/gemma_4_pharmacy_merged"
13
+
14
+ TOOLS_SCHEMA = [
15
+ {
16
+ "type": "function",
17
+ "function": {
18
+ "name": "get_nearby_pharmacies",
19
+ "description": "Belirtilen ilçe, şehir veya adresteki en yakın eczaneleri arar.",
20
+ "parameters": {
21
+ "type": "object",
22
+ "properties": {
23
+ "location": {
24
+ "type": "string",
25
+ "description": "Eczane aranacak konum adı (ör. 'Kadıköy, İstanbul', 'Karaköy')",
26
+ }
27
+ },
28
+ "required": ["location"],
29
+ },
30
+ },
31
+ }
32
+ ]
33
+
34
+ # ---------------------------------------------------------------------------
35
+ # 2. API FUNCTION (SerpApi Google Maps + Fallback Mock Data)
36
+ # ---------------------------------------------------------------------------
37
+ def get_nearby_pharmacies(location: str, api_key: str = ""):
38
+ """SerpApi ile Google Maps üzerinden eczane arar. Key yoksa mock veri döner."""
39
+ key = api_key.strip() or SERPAPI_KEY
40
+ if not key:
41
+ # Fallback Mock Data (API Key girilmediyse veya boşsa ödev kontrolü için)
42
+ return {
43
+ "location": location,
44
+ "status": "DEMO_MOCK_DATA (SERPAPI_KEY Tanımlı Değil)",
45
+ "pharmacies": [
46
+ {"name": f"{location} Merkez Eczanesi", "address": f"{location} Cad. No:12", "rating": 4.8, "phone": "+90 216 555 0101", "open_state": "Açık"},
47
+ {"name": f"{location} Şifa Eczanesi", "address": f"{location} Sok. No:5", "rating": 4.6, "phone": "+90 216 555 0102", "open_state": "Nöbetçi"},
48
+ {"name": f"{location} Hayat Eczanesi", "address": f"{location} Meydan No:8", "rating": 4.5, "phone": "+90 216 555 0103", "open_state": "Açık"}
49
+ ]
50
+ }
51
+
52
+ url = "https://serpapi.com/search.json"
53
+ params = {
54
+ "engine": "google_maps",
55
+ "q": f"eczane {location}",
56
+ "type": "search",
57
+ "api_key": key,
58
+ }
59
+ try:
60
+ response = requests.get(url, params=params, timeout=10)
61
+ data = response.json()
62
+ results = []
63
+ for item in data.get("local_results", [])[:5]:
64
+ results.append({
65
+ "name": item.get("title", "Bilinmeyen Eczane"),
66
+ "address": item.get("address", "Adres yok"),
67
+ "rating": item.get("rating", "Puan yok"),
68
+ "phone": item.get("phone", "Telefon yok"),
69
+ "open_state": item.get("open_state", "Bilinmiyor"),
70
+ })
71
+ if not results:
72
+ return {"error": f"'{location}' konumunda eczane bulunamadı."}
73
+ return {"location": location, "pharmacies": results}
74
+ except Exception as e:
75
+ return {"error": f"API hatası: {str(e)}"}
76
+
77
+
78
+ # ---------------------------------------------------------------------------
79
+ # 3. MODEL LOADING
80
+ # ---------------------------------------------------------------------------
81
+
82
+ print("Model ve Tokenizer Yükleniyor...")
83
+
84
+ tokenizer = AutoTokenizer.from_pretrained(MODEL_ID)
85
+
86
+ try:
87
+ model = AutoModelForCausalLM.from_pretrained(
88
+ MODEL_ID,
89
+ torch_dtype=torch.float16,
90
+ device_map="auto",
91
+ )
92
+ except Exception:
93
+ model = AutoModelForCausalLM.from_pretrained(
94
+ MODEL_ID,
95
+ torch_dtype=torch.float32,
96
+ device_map="cpu",
97
+ )
98
+
99
+ model.eval()
100
+
101
+ print("✓ Model başarıyla yüklendi.")
102
+ print(type(tokenizer))
103
+ print(tokenizer)
104
+
105
+
106
+ def decode_tokens(output_tokens):
107
+ """Model çıktısı token id'lerini metne çevirir."""
108
+ return tokenizer.decode(
109
+ output_tokens,
110
+ skip_special_tokens=True,
111
+ ).strip()
112
+
113
+
114
+ # ---------------------------------------------------------------------------
115
+ # 4. AGENT DÖNGÜSÜ (TOOL CALLING PROCESS)
116
+ # ---------------------------------------------------------------------------
117
+ def run_pharmacy_agent(user_query: str, custom_api_key: str = ""):
118
+
119
+ execution_logs = []
120
+
121
+ system_prompt = f"""Sen sağlık ve eczane konusunda uzman bir asistansın.
122
+ Sana verilen araçları (tools) kullanarak kullanıcı sorularına yanıt vermelisin.
123
+
124
+ Mevcut Araçlar:
125
+ {json.dumps(TOOLS_SCHEMA, ensure_ascii=False, indent=2)}
126
+
127
+ ÇOK ÖNEMLİ:
128
+
129
+ Kullanıcı;
130
+
131
+ - eczane
132
+ - nöbetçi eczane
133
+ - en yakın eczane
134
+ - hangi eczaneler
135
+ - adres
136
+ - telefon
137
+ - konum
138
+
139
+ ile ilgili herhangi bir soru sorarsa
140
+
141
+ KESİNLİKLE doğrudan cevap verme.
142
+
143
+ KESİNLİKLE aşağıdaki JSON dışında hiçbir şey yazma.
144
+
145
+ {{
146
+ "name":"get_nearby_pharmacies",
147
+ "arguments":{{
148
+ "location":"..."
149
+ }}
150
+ }}
151
+
152
+ Doğrudan cevap vermek yasaktır.
153
+
154
+ {{
155
+ "name": "get_nearby_pharmacies",
156
+ "arguments": {{
157
+ "location": "konum_adı"
158
+ }}
159
+ }}
160
+ """
161
+
162
+ # -----------------------------
163
+ # TURN 1
164
+ # -----------------------------
165
+ messages = [
166
+ {
167
+ "role": "system",
168
+ "content": system_prompt,
169
+ },
170
+ {
171
+ "role": "user",
172
+ "content": user_query,
173
+ },
174
+ ]
175
+
176
+ text = tokenizer.apply_chat_template(
177
+ messages,
178
+ tokenize=False,
179
+ add_generation_prompt=True,
180
+ )
181
+
182
+ print(text)
183
+
184
+ inputs = tokenizer(
185
+ text=text,
186
+ return_tensors="pt",
187
+ ).to(model.device)
188
+
189
+ with torch.no_grad():
190
+ outputs = model.generate(
191
+ **inputs,
192
+ max_new_tokens=200,
193
+ do_sample=False,
194
+ temperature=0.2,
195
+ )
196
+
197
+ model_response = decode_tokens(outputs[0][inputs.input_ids.shape[1]:])
198
+
199
+ print(model_response)
200
+
201
+ json_match = re.search(r"\{.*\}", model_response, re.DOTALL)
202
+
203
+ if json_match and (
204
+ "get_nearby_pharmacies" in model_response
205
+ or "arguments" in model_response
206
+ ):
207
+
208
+ try:
209
+
210
+ tool_call = json.loads(json_match.group(0))
211
+
212
+ location = tool_call.get(
213
+ "arguments", {}
214
+ ).get("location", user_query)
215
+
216
+ execution_logs.append(
217
+ f"[Turn 1] Araç Çağrısı:\n"
218
+ f"-> get_nearby_pharmacies(location='{location}')"
219
+ )
220
+
221
+ api_result = get_nearby_pharmacies(
222
+ location,
223
+ custom_api_key,
224
+ )
225
+
226
+ execution_logs.append(
227
+ "\n[Turn 2] API Yanıtı:\n"
228
+ + json.dumps(
229
+ api_result,
230
+ ensure_ascii=False,
231
+ indent=2,
232
+ )
233
+ )
234
+
235
+ # -----------------------------
236
+ # TURN 2
237
+ # -----------------------------
238
+ second_messages = [
239
+ {
240
+ "role": "system",
241
+ "content": system_prompt,
242
+ },
243
+ {
244
+ "role": "assistant",
245
+ "content": model_response,
246
+ },
247
+ {
248
+ "role": "user",
249
+ "content":
250
+ f"""Tool sonucu:
251
+
252
+ {json.dumps(api_result, ensure_ascii=False)}
253
+
254
+ Bu bilgileri kullanıcıya anlaşılır ve maddeler halinde açıkla.
255
+ """,
256
+ },
257
+ ]
258
+
259
+ second_text = tokenizer.apply_chat_template(
260
+ second_messages,
261
+ tokenize=False,
262
+ add_generation_prompt=True,
263
+ )
264
+
265
+ second_inputs = tokenizer(
266
+ text=second_text,
267
+ return_tensors="pt",
268
+ ).to(model.device)
269
+
270
+ with torch.no_grad():
271
+
272
+ second_outputs = model.generate(
273
+ **second_inputs,
274
+ max_new_tokens=400,
275
+ do_sample=False,
276
+ temperature=0.2,
277
+ )
278
+
279
+ final_answer = decode_tokens(second_outputs[0][second_inputs.input_ids.shape[1]:])
280
+
281
+ execution_logs.append(
282
+ "\n[Turn 3] Nihai Yanıt:\n"
283
+ + final_answer
284
+ )
285
+
286
+ return "\n".join(execution_logs)
287
+
288
+ except Exception as e:
289
+
290
+ execution_logs.append(
291
+ f"⚠️ Tool parse hatası: {e}"
292
+ )
293
+
294
+ execution_logs.append(model_response)
295
+
296
+ return "\n".join(execution_logs)
297
+
298
+ else:
299
+
300
+ execution_logs.append(
301
+ "[Turn 1] Tool çağrılmadı."
302
+ )
303
+
304
+ execution_logs.append(model_response)
305
+
306
+ return "\n".join(execution_logs)
307
+
308
+ # ---------------------------------------------------------------------------
309
+ # 5. GRADIO INTERFACE
310
+ # ---------------------------------------------------------------------------
311
+ demo = gr.Interface(
312
+ fn=run_pharmacy_agent,
313
+ inputs=[
314
+ gr.Textbox(label="Sorgu / Konum", placeholder="Örn: Kadıköy'deki en yakın eczaneleri bul", lines=2),
315
+ ],
316
+ outputs=gr.Textbox(label="Tool Call İşlem Adımları (Turn 1 -> Turn 2 -> Turn 3)", lines=18),
317
+ title="🏥 Pharmacy Tool-Calling Agent (`menesnas/gemma_4_pharmacy_merged`)",
318
+ description="Gemma 4 Merged modeli ile SerpApi Google Maps entegrasyonu."
319
+ )
320
+
321
+ if __name__ == "__main__":
322
+ demo.launch()
requirements.txt ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ torch
2
+ transformers>=4.55
3
+ gradio
4
+ requests
5
+ accelerate
6
+ sentencepiece