senemde/time-zone-tool-calling
0
1"""2Tool Calling Demo — Saat Dilimi Farkı3=====================================4 5Bir LLM'in kullanıcı sorusuna göre doğru fonksiyonları (Tool / Function6Calling) çağırmasını sağlayan, timeapi.io (ücretsiz, anahtarsız) API'sinden7gerçek veri çeken ve çağrılan araçları arayüzde açıkça gösteren bir uygulama.8 9Araçlar:10 - get_current_time(timezone) -> o an ilgili zaman diliminde saat + UTC farkı11 - time_difference(timezone1, timezone2) -> iki zaman dilimi arasındaki saat farkı12 13Not: timeapi.io'ya ulaşılamazsa Python'un yerleşik `zoneinfo` (IANA veritabanı)14kütüphanesine otomatik düşülür; böylece demo her koşulda çalışır.15 16Model: Hugging Face Inference Providers üzerinden tool-calling destekli bir17sohbet modeli (varsayılan: openai/gpt-oss-120b).18"""19 20import os21import json22from datetime import datetime23 24import requests25import gradio as gr26from huggingface_hub import InferenceClient27 28try:29 from zoneinfo import ZoneInfo # Python 3.9+30except ImportError: # çok eski sürümler için31 ZoneInfo = None32 33# ---------------------------------------------------------------------------34# ZeroGPU uyumluluğu35# ---------------------------------------------------------------------------36# HF Spaces ZeroGPU donanımı, başlatılabilmek için en az bir @spaces.GPU37# fonksiyonu ister. Bu uygulama GPU kullanmaz (yalnızca API çağırır); aşağıdaki38# fonksiyon sadece ZeroGPU'nun başlatma kontrolünü geçmek için vardır.39try:40 import spaces41 42 @spaces.GPU43 def _zerogpu_warmup():44 return True45except Exception:46 pass47 48MODEL_ID = os.environ.get("MODEL_ID", "openai/gpt-oss-120b")49HF_TOKEN = os.environ.get("HF_TOKEN") or os.environ.get("HUGGINGFACEHUB_API_TOKEN")50HF_PROVIDER = os.environ.get("HF_PROVIDER", "auto")51MAX_TURNS = 6 # Sonsuz döngüye karşı üst sınır52 53_client_kwargs = {"model": MODEL_ID, "token": HF_TOKEN}54if HF_PROVIDER:55 _client_kwargs["provider"] = HF_PROVIDER56client = InferenceClient(**_client_kwargs)57 58TIMEAPI_BASE = "https://timeapi.io/api"59 60 61def _fetch_zone(timezone: str) -> dict:62 """63 Önce timeapi.io public API'sinden dener; olmazsa yerel zoneinfo'ya düşer.64 Döner: {"timezone", "local_time", "utc_offset_hours"}65 """66 # 1) Birincil yol: public API67 try:68 r = requests.get(69 f"{TIMEAPI_BASE}/timezone/zone",70 params={"timeZone": timezone},71 timeout=10,72 )73 r.raise_for_status()74 data = r.json()75 offset_sec = data["currentUtcOffset"]["seconds"]76 local_raw = data.get("currentLocalTime", "")77 # "2026-07-30T15:30:00.123" -> "2026-07-30 15:30:00"78 local_time = local_raw.replace("T", " ").split(".")[0]79 return {80 "timezone": data.get("timeZone", timezone),81 "local_time": local_time,82 "utc_offset_hours": round(offset_sec / 3600, 2),83 "source": "timeapi.io",84 }85 except Exception:86 pass # API başarısız -> yedek yola geç87 88 # 2) Yedek yol: yerel IANA veritabanı89 if ZoneInfo is not None:90 try:91 now = datetime.now(ZoneInfo(timezone))92 return {93 "timezone": timezone,94 "local_time": now.strftime("%Y-%m-%d %H:%M:%S"),95 "utc_offset_hours": round(now.utcoffset().total_seconds() / 3600, 2),96 "source": "zoneinfo (yerel)",97 }98 except Exception:99 pass100 101 return {"error": f"'{timezone}' geçerli bir IANA zaman dilimi değil (örn. 'Europe/Istanbul')."}102 103 104def get_current_time(timezone: str) -> dict:105 """Belirtilen IANA zaman diliminde güncel saati ve UTC farkını döndürür."""106 return _fetch_zone(timezone)107 108 109def time_difference(timezone1: str, timezone2: str) -> dict:110 """İki IANA zaman dilimi arasındaki saat farkını hesaplar."""111 z1 = _fetch_zone(timezone1)112 z2 = _fetch_zone(timezone2)113 if "error" in z1:114 return z1115 if "error" in z2:116 return z2117 118 diff = round(z2["utc_offset_hours"] - z1["utc_offset_hours"], 2)119 if diff > 0:120 note = f"{z2['timezone']}, {z1['timezone']}'dan {abs(diff)} saat ileridedir."121 elif diff < 0:122 note = f"{z2['timezone']}, {z1['timezone']}'dan {abs(diff)} saat geridedir."123 else:124 note = f"{z1['timezone']} ve {z2['timezone']} aynı saat dilimindedir."125 126 return {127 "timezone1": z1["timezone"],128 "timezone2": z2["timezone"],129 "difference_hours": diff,130 "note": note,131 }132 133 134TOOL_FUNCS = {135 "get_current_time": get_current_time,136 "time_difference": time_difference,137}138 139 140TOOLS = [141 {142 "type": "function",143 "function": {144 "name": "get_current_time",145 "description": (146 "Belirtilen zaman diliminde o anki yerel saati ve UTC farkını "147 "döndürür."148 ),149 "parameters": {150 "type": "object",151 "properties": {152 "timezone": {153 "type": "string",154 "description": (155 "IANA zaman dilimi kimliği, örn. 'Europe/Istanbul', "156 "'America/New_York', 'Asia/Tokyo'. Kullanıcı şehir adı "157 "verirse uygun IANA kimliğine çevir."158 ),159 }160 },161 "required": ["timezone"],162 },163 },164 },165 {166 "type": "function",167 "function": {168 "name": "time_difference",169 "description": "İki zaman dilimi arasındaki saat farkını hesaplar.",170 "parameters": {171 "type": "object",172 "properties": {173 "timezone1": {174 "type": "string",175 "description": "Birinci IANA zaman dilimi, örn. 'America/New_York'.",176 },177 "timezone2": {178 "type": "string",179 "description": "İkinci IANA zaman dilimi, örn. 'Asia/Tokyo'.",180 },181 },182 "required": ["timezone1", "timezone2"],183 },184 },185 },186]187 188SYSTEM_PROMPT = (189 "Sen araç kullanabilen yardımcı bir asistansın. Saat ve zaman dilimi "190 "sorularında ASLA bilgi uydurma; her zaman verilen araçları "191 "(get_current_time, time_difference) çağırarak gerçek verilere ulaş. "192 "Kullanıcı şehir adı verirse ('New York', 'Tokyo', 'İstanbul'), bunu doğru "193 "IANA zaman dilimi kimliğine çevir (örn. 'America/New_York', 'Asia/Tokyo', "194 "'Europe/Istanbul'). Birden fazla yer sorulduğunda her biri için ayrı araç "195 "çağır. Son yanıtını kullanıcının diliyle, kısa ve net ver."196)197 198 199def _fmt_args(args: dict) -> str:200 return ", ".join(f"{k}={v!r}" for k, v in args.items())201 202 203def run_agent(user_message: str, history_messages: list):204 """Modeli araçlarla çalıştırır. (nihai_yanit, adim_izi) döner."""205 if not HF_TOKEN:206 return (207 "⚠️ HF_TOKEN ayarlı değil. HF Spaces > Settings > Secrets bölümünden "208 "`HF_TOKEN` ekleyin (huggingface.co/settings/tokens).",209 "",210 )211 212 messages = [{"role": "system", "content": SYSTEM_PROMPT}]213 messages.extend(history_messages)214 messages.append({"role": "user", "content": user_message})215 216 trace_lines = []217 218 for turn in range(1, MAX_TURNS + 1):219 try:220 resp = client.chat_completion(221 messages=messages,222 tools=TOOLS,223 tool_choice="auto",224 max_tokens=1024,225 temperature=0.2,226 )227 except Exception as exc:228 return f"Model çağrısı başarısız oldu: {exc}", "\n".join(trace_lines)229 230 msg = resp.choices[0].message231 tool_calls = msg.tool_calls or []232 233 if not tool_calls:234 final = msg.content or "(boş yanıt)"235 if trace_lines:236 trace_lines.append(f"\n[Turn {turn}] Nihai Yanıt:")237 trace_lines.append(final)238 return final, "\n".join(trace_lines)239 240 messages.append(241 {242 "role": "assistant",243 "content": msg.content or "",244 "tool_calls": [245 {246 "id": tc.id,247 "type": "function",248 "function": {249 "name": tc.function.name,250 "arguments": tc.function.arguments,251 },252 }253 for tc in tool_calls254 ],255 }256 )257 258 trace_lines.append(f"[Turn {turn}] Araç Çağrıları:")259 for tc in tool_calls:260 name = tc.function.name261 try:262 args = json.loads(tc.function.arguments or "{}")263 except json.JSONDecodeError:264 args = {}265 266 func = TOOL_FUNCS.get(name)267 result = func(**args) if func else {"error": f"Bilinmeyen araç: {name}"}268 269 trace_lines.append(f" -> {name}({_fmt_args(args)})")270 trace_lines.append(f" <- {result}")271 272 messages.append(273 {274 "role": "tool",275 "tool_call_id": tc.id,276 "name": name,277 "content": json.dumps(result, ensure_ascii=False),278 }279 )280 281 return "Adım sınırına ulaşıldı, yanıt tamamlanamadı.", "\n".join(trace_lines)282 283 284def respond(message, chat_history):285 history_messages = [286 {"role": t["role"], "content": t["content"]} for t in chat_history287 ]288 final, trace = run_agent(message, history_messages)289 chat_history = chat_history + [290 {"role": "user", "content": message},291 {"role": "assistant", "content": final},292 ]293 return chat_history, trace, ""294 295 296with gr.Blocks(title="Tool Calling — Saat Dilimi Farkı") as demo:297 gr.Markdown(298 "# 🕐 Tool Calling Demo — Saat Dilimi Farkı\n"299 "Model, sorunuza göre **`get_current_time`** ve **`time_difference`** "300 "araçlarını otomatik çağırır. Sağ tarafta arka planda çağrılan araçları "301 "ve adımları görebilirsiniz.\n\n"302 f"**Model:** `{MODEL_ID}` · **Veri kaynağı:** timeapi.io"303 )304 305 with gr.Row():306 with gr.Column(scale=3):307 chatbot = gr.Chatbot(height=420, label="Sohbet")308 msg = gr.Textbox(309 placeholder="Örn: New York ile Tokyo arasında kaç saat fark var, şu an saat kaç?",310 label="Sorunuz",311 )312 with gr.Row():313 send = gr.Button("Gönder", variant="primary")314 clear = gr.Button("Temizle")315 with gr.Column(scale=2):316 trace_box = gr.Textbox(317 label="🔧 Araç Çağrı Adımları (Tool Trace)",318 lines=22,319 interactive=False,320 )321 322 gr.Examples(323 examples=[324 "New York ile Tokyo arasında kaç saat fark var, şu an oralarda saat kaç?",325 "İstanbul'da şu an saat kaç?",326 "Londra mı ileride Los Angeles mı, aradaki fark nedir?",327 "Sidney ile Berlin arasındaki saat farkı kaç saat?",328 ],329 inputs=msg,330 )331 332 send.click(respond, [msg, chatbot], [chatbot, trace_box, msg])333 msg.submit(respond, [msg, chatbot], [chatbot, trace_box, msg])334 clear.click(lambda: ([], "", ""), None, [chatbot, trace_box, msg])335 336 337if __name__ == "__main__":338 demo.launch()339 