İçeriğe geç
Çılgın Yazılım

Uygulama İskeleti İleri

Yapay Zekâ Entegrasyonu (Claude API)

API anahtarı sunucuda kalır, her yanıtın jetonu ve maliyeti hesaplanır. Claude Messages API'sine SDK'sız cURL isteği, geçici/kalıcı hata ayrımı, üstel geri çekilme ve düşünme özeti — oturum girişli bir panelin içinde.

  • PHP 8
  • PDO
  • MySQL
  • Bootstrap 5
  • Ajax
  • Oturum Girişi
php-ai-integration/kunye.json
seviye
"İleri"
dosya
84
satir
~20.626
boyut
"2.2 MB"
veritabani
"cy_ai"
lisans
"MIT"
inceleme
131
begeni
0
yayin
""

Ekran görüntüleri 6 görsel

Bu örnek ne yapıyor?

  • API anahtarı yalnızca sunucuda; tarayıcıya hiç gönderilmez
  • Mesaj başına giriş/çıkış jetonu ve hesaplanmış maliyet kaydı
  • Sohbet toplamları mesaj toplamlarıyla birebir tutarlı
  • Giriş jetonunun neden her mesajda arttığı somut olarak görünür
  • Geçici (429/529/5xx) ve kalıcı (401/400) hata ayrımı
  • Üstel geri çekilme + jitter ile en fazla üç yeniden deneme
  • stop_reason ve refusal alanları ayrı ele alınır
  • Modelin düşünme özeti saklanır ve details ile katlanarak gösterilir
  • SDK yok: istek cURL ile atılır, yanıt elle ayrıştırılır

Nerede işe yarar?

  • Projesine yapay zekâ özelliği ekleyip maliyeti kontrol altında tutmak isteyenler
  • API anahtarını nereye koyacağını soran geliştiriciler
  • Jeton muhasebesini ve maliyet hesabını öğrenmek isteyenler
  • SDK kurmadan, saf PHP ile Messages API'sine bağlanmak isteyenler

Gereksinimler

  • PHP 8.0+ · MySQL 5.7+ / MariaDB 10.3+ · pdo_mysql · mbstring · curl · Anthropic API anahtarı · Apache mod_rewrite

Nasıl kurulur?

  1. Depoyu klonlayın veya ZIP olarak indirip web kökünüze açın
  2. database.sql dosyasını içe aktarın (veritabanını kendisi oluşturur)
  3. .env.example dosyasını .env adıyla kopyalayın
  4. .env içine ANTHROPIC_API_KEY değerinizi yazın (console.anthropic.com)
  5. Tarayıcıdan açıp [email protected] / Admin1234 ile giriş yapın

Veritabanı şeması projedeki database.sql dosyasında.

Nasıl çalışıyor?

Bu Proje Nedir?

Bir yapay zekâ API'sini projeye bağlamak, ilk bakışta tek bir curl çağrısıdır. Gerçekte dört soru hemen ardından gelir ve dördü de hafife alınırsa pahalıya patlar:

  • Anahtar nerede duracak? JavaScript'e koyarsanız, sayfayı açan herkes onu görür ve sizin hesabınıza istek atabilir.
  • Bu ay ne kadar harcadım? Fatura ayın sonunda gelir; o zamana kadar hiçbir fikriniz yoktur.
  • Neden ikinci soru birinciden pahalı? Çünkü model durum tutmaz: her istekte sohbetin tamamı yeniden gönderilir ve yeniden ücretlendirilir.
  • API hata verdiğinde ne olacak? 429 ve 529 geçicidir, tekrar denenmelidir; 401 kalıcıdır, denemenin anlamı yoktur. İkisini ayırt etmeyen kod ya boşuna uğraşır ya da erken pes eder.

Bu proje dördünü de ele alan bir entegrasyon katmanı kuruyor. Anahtar .env içinde durur ve yalnızca sunucuda kullanılır. Her yanıtın giriş/çıkış jetonu ve hesaplanmış maliyeti kaydedilir. Sohbet geçmişi veritabanındadır. Hata yönetimi geçici ile kalıcıyı ayırır ve geçici olanları üstel geri çekilmeyle yeniden dener.

Modelin "düşünme" özeti de saklanır ve arayüzde istenirse açılır — yanıtın nasıl kurulduğunu görmek, çıktıyı değerlendirmenin en pratik yoludur.

Sağlayıcı sınıfları hiçbir SDK kullanmaz; istek cURL ile atılır, yanıt elle ayrıştırılır.

Ve en önemlisi: denemek için para gerekmiyor. Varsayılan sağlayıcı Google Gemini'dir ve ücretsiz katmanı vardır — aistudio.google.com/apikey adresinden kredi kartı vermeden, birkaç saniyede anahtar alırsınız.

Kimler için uygun?

  • Projesine yapay zekâ özelliği ekleyecek, ama maliyeti kontrol altında tutmak isteyenler
  • API anahtarını nereye koyacağını soran herkes
  • Jeton muhasebesinin nasıl yapıldığını öğrenmek isteyenler
  • SDK kurmadan, saf PHP ile bir yapay zekâ API'sine bağlanmak isteyenler
  • Sağlayıcıya kilitlenmeden, tek ayarla Gemini/Groq/Claude arasında geçiş yapmak isteyenler
  • Bootstrap 5 üzerine kurulu, tekrar kullanılabilir bir panel kalıbı arayanlar

Bu proje, Çılgın Yazılım Kütüphanesi altında yayınlanan açıklamalı, üretime hazır örneklerden biridir.


Bir isteğin yolculuğu

 TARAYICI                          SUNUCU (PHP)                    SAĞLAYICI API
 ────────                          ────────────                    ─────────────
 Kullanıcı mesajı yazar
    │  POST /api/chat/send
    │  (CSRF jetonu ile)
    ▼
                          ai_messages'a "user" satırı
                                   │
                                   │  Ai::fromEnv()->send()
                                   │  (gemini · groq · claude …)
                                   │
                                   │  1) SOHBETİN TAMAMINI topla
                                   │     (model durum tutmaz)
                                   │
                                   │  2) x-goog-api-key: <ANAHTAR> ◄─ ANAHTAR
                                   │     (Claude'da x-api-key)        BURADA KALIR
                                   │                                  tarayıcıya
                                   │  3) POST …:generateContent ──────► GİTMEZ
                                   │                                     │
                                   │                                     ▼
                                   │  ◄────────────────────── 200 / 429 / 529 / 4xx
                                   │
                                   │  4) 429 · 529 · 5xx  → GEÇİCİ
                                   │     üstel geri çekilme + jitter
                                   │     en fazla 3 kez yeniden dene
                                   │
                                   │     401 · 400        → KALICI
                                   │     denemeden vazgeç, açıkla
                                   │
                                   │  5) Yanıtı ayrıştır:
                                   │     text · thinking · stop_reason
                                   │     refusal · usage
                                   │
                                   │  6) MALİYET HESAPLA
                                   │     giriş/1M×5$ + çıkış/1M×25$
                                   ▼
                          ai_messages'a "assistant" satırı
                          (metin · düşünme · jetonlar · maliyet)
                                   │
                          ai_conversations toplamları güncellenir
                                   │
    ◄──────────────────────── JSON yanıt
    ▼
 Balon çizilir, jeton satırı basılır

Kritik Kararlar

1. API anahtarı asla tarayıcıya gitmez

İnternetteki örneklerin şaşırtıcı bir kısmı anahtarı JavaScript'e koyar ve fetch ile doğrudan API'ye gider. Bu, anahtarı sayfayı açan herkese vermektir: F12 → Sources yeterlidir. Anahtarınızla başkaları istek atar, faturayı siz ödersiniz.

Bu projede istek her zaman sunucudan atılır:

Tarayıcı → (kendi sunucunuz) → Sağlayıcı API

Anahtar .env dosyasındadır, .gitignore içindedir ve PHP dışına hiç çıkmaz. Tarayıcı yalnızca kendi sunucunuzla konuşur — CSRF korumalı, oturum gerektiren bir uç nokta üzerinden.

2. Her mesajın jetonu ve maliyeti kaydedilir

Fatura ayın sonunda gelir. O zamana kadar hangi özelliğin ne kadar harcadığını bilmiyorsanız, maliyeti yönetemezsiniz.

ai_messages: input_tokens · output_tokens · cost_usd
ai_conversations: total_tokens · total_cost

Sayılar API'nin usage alanından gelir — tahmin değil, ölçümdür. Maliyet ise fiyat tablosundan hesaplanır.

Sohbet satırındaki toplam, mesajların toplamına eşittir; iki yerde tutulan bir sayının tutarsız kalması, güvenilmez bir gösterge demektir.

3. Giriş jetonu neden her mesajda artıyor?

Çünkü model durum tutmaz. "Önceki mesajımı hatırla" diye bir şey yoktur; her istekte sohbetin tamamını yeniden gönderirsiniz ve tamamı yeniden ücretlendirilir.

Örnek sohbette bunu somut görürsünüz: 412 → 890 → 1.503. Üçüncü soru, birincinin dört katı giriş jetonu tüketir — soru aynı uzunlukta olsa bile.

Bu, uzun sohbetlerin neden pahalılaştığını açıklar. Pratik sonuç: geçmişi sınırsız göndermeyin. Uzun sohbetlerde eski mesajları özetleyip özeti göndermek yaygın ve etkili bir kalıptır.

4. Geçici hata ile kalıcı hata ayrılır

$retryable = $status === 429 || $status === 529 || $status >= 500;
KodAnlamıNe yapılır
429Hız sınırıGeçici — geri çekilmeyle yeniden dene
529Servis yoğunGeçici — yeniden dene
5xxSunucu hatasıGeçici — yeniden dene
401Anahtar geçersizKalıcı — denemenin anlamı yok
400İstek hatalıKalıcı — istek düzeltilmeli

Ayrım yapmayan kod iki yönden de kaybeder: 401 için üç kez deneyip zaman kaybeder, 429 için hiç denemeyip kullanıcıya gereksiz hata gösterir.

Yeniden denemeler üstel geri çekilme + jitter ile yapılır. Jitter (rastgele sapma) önemlidir: aynı anda sınıra çarpan yüz istemci aynı anda tekrar denerse sınıra yine birlikte çarparlar.

5. stop_reason ve refusal göz ardı edilmez

Bir yanıt "başarılı" dönebilir ama tamamlanmamış olabilir:

  • stop_reason = "max_tokens" → yanıt cümlenin ortasında kesildi. Kullanıcıya eksik bir metni tam gibi göstermek yanlıştır.
  • refusal dolu → model bilerek yanıtlamadı. Bu bir hata değildir ve "sunucu hatası" diye gösterilmemelidir.

İkisi de ayrı ele alınır ve arayüzde ayrı biçimde gösterilir.

AI_MAX_TOKENS varsayılanı bu yüzden 16.000'dir. Düşük tutmak tasarruf gibi görünür ama kesilen yanıtı baştan sormak zorunda kalırsınız — yani iki kez ödersiniz.

6. "Düşünme" özeti saklanır, ama gizlenerek gösterilir

Modelin muhakemesi, çıktıyı değerlendirmenin en pratik yoludur: yanıt yanlışsa nerede saptığını görürsünüz.

Ama her zaman ekranda durması gerekmez; <details> etiketiyle katlanır. JavaScript gerekmez, erişilebilirlik kendiliğinden doğrudur.

7. Metin pre-wrap ile basılır, nl2br() ile değil

Model yanıtları kod örneği içerir ve kodun girintisi anlamlıdır.

.cy-msg__bubble { white-space: pre-wrap; }

pre-wrap hem satır sonlarını hem girintileri korur, uzun satırları da kırar. Üstüne nl2br() eklemek her satır sonunu iki kez uygular ve paragraf araları iki katına çıkar — kod içeren yanıtlarda ekran dağılır.

Aynı sebeple balonun <div> etiketiyle içeriği arasında boşluk bırakılmaz: pre-wrap, şablonun kendi girintisini de metin sayar ve her balonun ilk satırı sağa kaymış görünürdü.

8. Sohbet silinince mesajlar da gider

CONSTRAINT fk_msg_conv FOREIGN KEY (conversation_id)
    REFERENCES ai_conversations (id) ON DELETE CASCADE

Uygulama kodunda "önce mesajları sil, sonra sohbeti sil" yazılsaydı, o iki adımın arasında bir hata oluştuğunda yetim mesajlar kalırdı. Kısıtı veritabanına koymak bunu imkânsız kılar.


Neler Var?

API katmanı

  • Messages API'sine cURL ile istek — SDK yok
  • Anahtar yalnızca sunucuda
  • Üstel geri çekilme + jitter, en fazla 3 deneme
  • Geçici (429/529/5xx) ve kalıcı hata ayrımı
  • stop_reason ve refusal ayrı ele alınır
  • Model ve efor seviyesi .env'den
  • Yapılandırılabilir max_tokens

Muhasebe

  • Mesaj başına giriş/çıkış jetonu
  • Mesaj başına maliyet (fiyat tablosundan)
  • Sohbet başına toplam jeton ve maliyet
  • Panelde dört sayaç

Sohbet arayüzü

  • Sohbet listesi, jeton ve maliyet sütunlu
  • Kullanıcı/model balonları, kod girintisi korunur
  • Katlanabilir "düşünme özeti" (<details>)
  • Mesaj altında jeton ve maliyet satırı
  • Sohbet silme (yalnızca kendi sohbeti)
  • Anahtar yoksa açıklayıcı uyarı

Ortak altyapı

  • Oturum girişi, "beni hatırla", hız sınırı, CSRF
  • CSP (script-src 'self'), X-Frame-Options: DENY
  • Açık / koyu tema, hesaba kayıtlı
  • Mobilde alt navigasyon, yatay kaydırma yok
  • Kullanıcılar sayfasında canlı filtre (JS kapalıysa da çalışır)

Maliyet nasıl hesaplanıyor?

Fiyatlar milyon jeton başınadır ve her sağlayıcının kendi PRICING tablosundadır. Ücretsiz katmanda gerçek maliyetiniz sıfırdır; rakam yine de gösterilir, çünkü asıl soru "bugün ne ödedim?" değil, "bu uygulama yayına çıkarsa ne öderim?" sorusudur.

GeminiProvider::PRICING — ücretli katman:

ModelGirişÇıkış
gemini-2.5-flash-lite0,10 $0,40 $
gemini-2.5-flash0,30 $2,50 $
gemini-3.8-flash0,75 $3,75 $

ClaudeProvider::PRICING:

ModelGiriş (1M jeton)Çıkış (1M jeton)
claude-opus-55,00 $25,00 $
claude-sonnet-52,00 $10,00 $
claude-haiku-4-51,00 $5,00 $

public static function estimateCost(string $model, array $usage): float
{
    $rates = self::PRICING[$model] ?? null;
    if ($rates === null) { return 0.0; }

    return ($usage['input_tokens']  / 1000000) * $rates['input']
         + ($usage['output_tokens'] / 1000000) * $rates['output'];
}

Örnek: 412 giriş + 386 çıkış jetonu →

412 / 1.000.000 × 5  = 0,00206
386 / 1.000.000 × 25 = 0,00965
                       ─────────
                       0,01171 $

Demodaki sohbetlerde bu hesabı satır satır doğrulayabilirsiniz.

Bu bir tahmindir, fatura değildir. Fiyatlar koda gömülüdür ve değişebilir; ayrıca ön belleğe yazılan ve ön bellekten okunan jetonlar farklı oranlarla ücretlendirilir — burada yalnızca normal giriş ve çıkış hesaba katılır. Yine de büyüklük mertebesini görmek çok değerlidir: "bu sohbet 0,04 dolar" bilgisi, model veya efor seviyesi değiştirme kararını somut hâle getirir.

Tanımlı olmayan bir model adı için 0.0 döner — yanlış bir rakam göstermektense hiç göstermemek yeğdir.


Hata yönetimi

Sağlayıcının send() metodu her durumda anlaşılır bir sonuç döndürür; ham bir istisna arayüze sızmaz.

DurumKullanıcı ne görür
Anahtar tanımsız"API anahtarı tanımlı değil" + .env satırı
401"Anahtar geçersiz" — yeniden denenmez
429 (3 denemeden sonra)"Hız sınırına takıldınız ve yeniden denemeler de yetmedi."
529 / 5xx"Servis geçici olarak yoğun"
stop_reason = max_tokensYanıt gösterilir + kesildiği belirtilir
refusal doluModelin yanıtlamama gerekçesi, hata olarak değil
Ağ hatası / zaman aşımıGeri çekilmeyle yeniden denenir, sonra açıklanır

Güvenlik: Neyi, Nasıl Kapattık?

AçıkTipik hatalı kodBu projede
API anahtarı sızıntısıAnahtarı JavaScript'e koyup tarayıcıdan istek atmakİstek yalnızca sunucudan; anahtar .env içinde, .gitignore'da
Anahtarın depoya gitmesiconfig.php içine yazmak.env dosyası depoya gönderilmez; .env.example şablondur
Başkasının sohbetini okuma (IDOR)WHERE id = :idSorguya user_id de katılır; kullanıcı yalnızca kendi sohbetini görür ve siler
Yetim kayıtUygulama kodunda iki adımlı silmeON DELETE CASCADE — veritabanı garanti eder
Sonsuz maliyetmax_tokens sınırsız / geçmiş sınırsızAI_MAX_TOKENS ayarlanabilir; jeton ve maliyet her mesajda kaydedilir
Boşuna yeniden denemeHer hatada 3 kez denemek401/400 kalıcı sayılır, denenmez
Eşzamanlı yeniden deneme fırtınasıSabit aralıklı tekrarÜstel geri çekilme + jitter
XSSecho $message['content']Sunucuda e(); ayrıca CSP script-src 'self'
CSRFGizli alan yokHer POST'ta jeton; hash_equals()
SQL enjeksiyonu"... WHERE id = $id"Tüm sorgular hazır ifade; ATTR_EMULATE_PREPARES = false
Hata sızıntısıİstisna mesajını ekrana basmakAPP_DEBUG ortamdan türetilir; canlıda ayrıntı gösterilmez
Bozuk UTF-8'de sessiz JSON kaybıjson_encode($v)JSON_INVALID_UTF8_SUBSTITUTE

Yapılandırma

APP_DEBUG=true          # silerseniz: yerelde açık, canlıda kapalı
APP_URL=
APP_PRETTY_URLS=true

DB_HOST=127.0.0.1
DB_PORT=3306
DB_NAME=cy_ai
DB_USER=root
DB_PASS=

# --- YAPAY ZEKÂ SAĞLAYICISI ---
AI_PROVIDER=gemini
GEMINI_API_KEY=
••••••••
AI_MAX_TOKENS=••••••••
AyarNe yapar
AI_PROVIDERgemini · groq · openai-compatible · claude. Varsayılan gemini
AI_MODELKullanılacak model. Boş bırakırsanız sağlayıcının varsayılanı kullanılır
AI_MAX_TOKENSYanıtın en fazla kaç jeton olacağı. Düşük tutmayın: sınıra çarpan yanıt cümlenin ortasında kesilir ve baştan sormanız gerekir
AI_BASE_URLYalnızca openai-compatible için: hangi servise bağlanılacağı
AI_THINKINGGemini'de düşünme özeti istensin mi (varsayılan kapalı)
AI_EFFORTClaude'da düşünme derinliği. Yükseltmek daha iyi yanıt verir ama hem süreyi hem maliyeti artırır

Sağlayıcı seçmek

Uygulama sağlayıcıyı bilmez. App\Core\Ai\Ai::fromEnv() ayara bakıp doğru
sınıfı üretir; denetleyici ve arayüz aynı kalır. Sağlayıcı değiştirmek üç satırdır:

Sağlayıcı.envÜcretsiz katmanAnahtar
Google Gemini (varsayılan)

AI_PROVIDER=gemini
GEMINI_API_KEY=…
AI_MODEL=gemini-2.5-flash | var | aistudio.google.com/apikey |
| Groq | AI_PROVIDER=groq
GROQ_API_KEY=…
AI_MODEL=llama-3.3-70b-versatile | var | console.groq.com/keys |
| xAI (Grok) · OpenRouter · Ollama | AI_PROVIDER=openai-compatible
AI_API_KEY=…
AI_BASE_URL=https://api.x.ai/v1 | sağlayıcıya göre | sağlayıcının paneli |
| Anthropic Claude | AI_PROVIDER=claude
ANTHROPIC_API_KEY=…
AI_MODEL=claude-sonnet-5 | yok (ücretli) | console.anthropic.com |

Neden varsayılan ücretsiz bir sağlayıcı? Bu bir öğrenme örneğidir ve önündeki en
pahalı engel kredi kartıydı: kodu okumak isteyen çoğu kişi çalışır hâlini hiç
görmeden ayrılıyordu.

Yeni bir sağlayıcı eklemek HttpProvider sınıfını genişletip dört soruyu
cevaplamaktır: nereye (endpoint), hangi başlıkla (headers), hangi gövdeyle
(payload), gelen yanıt ortak biçime nasıl çevrilir (normalize). Yeniden deneme,
geri çekilme ve cURL ayarları zaten ortak katmandadır.


Dosya Yapısı

ai-integration-system/
│
├── index.php                     Ön denetleyici — TEK giriş noktası
├── database.sql                  Şema + 51 kullanıcı + 3 sohbet + 14 mesaj
├── .env.example
│
├── app/
│   ├── Core/
│   │   ├── Ai/                   ★ SAĞLAYICI KATMANI
│   │   │   ├── Ai.php                Fabrika — AI_PROVIDER'a bakar
│   │   │   ├── Provider.php          Arayüz + ortak yanıt sözleşmesi
│   │   │   ├── HttpProvider.php      cURL · yeniden deneme · geri çekilme
│   │   │   ├── GeminiProvider.php    Varsayılan — ücretsiz katman
│   │   │   ├── OpenAiCompatibleProvider.php  Groq · xAI · OpenRouter · Ollama
│   │   │   └── ClaudeProvider.php    Anthropic Messages API
│   │   ├── Auth.php · Session.php · Csrf.php · RateLimiter.php
│   │   ├── Database.php          PDO (EMULATE_PREPARES = false)
│   │   ├── Env.php               .env okuyucu + isLocalHost()
│   │   └── ...
│   │
│   ├── Http/Controllers/
│   │   ├── ChatController.php    Sohbet listesi, detay, silme
│   │   ├── Api/ChatApiController.php   Mesaj gönderme (AJAX)
│   │   └── Auth · Dashboard · User
│   │
│   ├── Repositories/ConversationRepository.php
│   └── Support/helpers.php
│
├── views/
│   ├── chat/index.php            Sohbet listesi + sayaçlar
│   ├── chat/show.php             ★ Balonlar · düşünme özeti · jeton satırı
│   └── ...
│
├── assets/
│   ├── css/  cilginyazilim.css (marka) · admin.css · feature.css
│   └── js/   chat.js · app.js · login.js · users.js
│
├── config/config.php
├── routes/web.php
└── docs/screenshots/

Veritabanı Şeması

ai_conversations

SütunTipİşi
idINT UNSIGNEDBirincil anahtar
user_idINT UNSIGNEDSohbet kimin (ON DELETE CASCADE)
titleVARCHAR(150)Listede görünen ad
total_tokensINT UNSIGNEDGiriş + çıkış toplamı
total_costDECIMAL(12,8)Toplam maliyet (USD)
created_at · updated_atDATETIMEAçılış ve son mesaj anı

ai_messages

SütunTipİşi
idBIGINT UNSIGNEDBirincil anahtar
conversation_idINT UNSIGNEDHangi sohbet (ON DELETE CASCADE)
roleENUM('user','assistant')Kim yazdı
contentMEDIUMTEXTMesaj metni
thinkingMEDIUMTEXT NULLModelin muhakemesi (varsa)
input_tokens · output_tokensINT UNSIGNEDAPI'nin usage alanından
cost_usdDECIMAL(12,8)Bu mesajın maliyeti
created_atDATETIMEYazılma anı
KararNeden
DECIMAL(12,8), FLOAT değilPara hiçbir zaman kayan noktalı sayıyla tutulmaz; FLOAT toplamları sessizce kaydırır. Sekiz ondalık, tek bir mesajın kesirli sentini taşır
Toplamlar sohbet satırında da varListe sayfası her satır için mesaj tablosunu toplasaydı N+1 sorgu doğardı
thinking ayrı sütunYanıt metniyle karıştırılmamalı; ayrıca isteğe bağlı gösterilir
role ENUMİki değer vardır ve üçüncüsü bir hatadır; veritabanı bunu kendisi engeller
Kullanıcı mesajında jeton 0Ücretlendirme, o mesajı da içeren API çağrısının yanıt satırında toplanır; iki yerde saymak toplamı bozardı
ON DELETE CASCADESohbet silinince mesajlar da gider; yetim satır imkânsız olur

SSS

API anahtarını JavaScript'e koysam ne olur?

Sayfayı açan herkes onu görür. F12 → Sources yeterlidir; ağ sekmesinde istek başlıklarında da durur.

Sonuç: anahtarınızla başkaları istek atar, faturayı siz ödersiniz. Anahtarı iptal edip yenisini üretmekten başka çareniz kalmaz.

Kural basit: API anahtarı sunucudan çıkmaz. Tarayıcı yalnızca kendi sunucunuzla konuşur.

Maliyeti nasıl düşürürüm?

Dört kaldıraç var, etkileri sırasıyla:

  1. Sağlayıcı. En büyük fark burada: Gemini ve Groq'un ücretsiz katmanları vardır. Bir yan projede fatura hiç başlamayabilir.
  2. Model. Aynı ailede bile fark büyüktür: gemini-2.5-flash-lite çıkışta gemini-2.5-flash'in altıda biri, claude-haiku-4-5 ise claude-opus-5'in beşte biri fiyattadır. Sınıflandırma, özetleme, biçimlendirme gibi işlerde aradaki kalite farkı çoğu zaman fark edilmez.
  3. Gönderdiğiniz geçmiş. Giriş jetonu her mesajda birikir. Uzun sohbetlerde eski mesajları özetleyip özeti gönderin.
  4. Efor seviyesi. Claude'da AI_EFFORT düşürmek düşünme jetonlarını azaltır.

Hangisinin işe yaradığını görmek için paneldeki maliyet sayacına bakın — ölçmeden optimize etmeyin.

Yanıt cümlenin ortasında kesiliyor

stop_reason = "max_tokens" demektir: yanıt AI_MAX_TOKENS sınırına çarptı.

.env içindeki değeri yükseltin. Düşük tutmak tasarruf gibi görünür ama kesilen yanıtı baştan sormak zorunda kalırsınız — yani iki kez ödersiniz. Varsayılan 16.000 çoğu iş için yeterlidir.

Çok uzun yanıtlar için akış (streaming) gerekir; bu örnekte akışsız istek kullanılmıştır.

Neden resmi SDK'yı kullanmadınız?

Bu proje Messages API'sinin nasıl çalıştığını göstermek için yazıldı: hangi başlıklar gidiyor, yanıt nasıl ayrışıyor, hangi hata kodu geçici.

Sağlayıcı sınıfları her adımı yorumlarla açıklar; ortak HTTP ve yeniden deneme katmanı HttpProvider içinde bir kez yazılmıştır. Ayrıca Composer bağımlılığı olmaması, paylaşımlı hosting'e atıp çalıştırabilmeniz demektir.

Üretimde SDK kullanmak isterseniz, artık onun ne yaptığını biliyor olacaksınız.

429 alıyorum, ne yapmalıyım?

429 hız sınırıdır ve geçicidir. Uygulama zaten üstel geri çekilmeyle en fazla 3 kez yeniden dener.

Üç deneme de yetmiyorsa istek hızınız hesabınızın sınırının üstünde demektir. Seçenekler: istekleri kuyruğa alıp yavaşça göndermek, daha küçük bir model kullanmak veya hesap limitinizi yükseltmek.

Kuyruk örneği için İş Kuyruğu ve Worker Sistemi'ne bakın.

Sohbet geçmişini sınırsız göndermek zorunda mıyım?

Hayır ve göndermemelisiniz. Model durum tutmadığı için her istekte tüm geçmiş yeniden ücretlendirilir; 50 mesajlık bir sohbette 51. soru çok pahalıya gelir.

Yaygın kalıp: son N mesajı ham gönderin, daha eskileri tek bir özet mesajına indirin. Özeti üretmek de bir API çağrısıdır ama bir kez ödenir.


Canlı Ortama Alırken

  • [ ] .env içinde APP_DEBUG=false (veya satırı tümüyle silin)
  • [ ] API anahtarı yalnızca .env içinde; depoda değil
  • [ ] .env dosyasının tarayıcıdan erişilemediğini doğrulayın (403 dönmeli)
  • [ ] AI_MODEL ve AI_MAX_TOKENS bütçenize göre ayarlanmış mı?
  • [ ] Kullanıcı başına günlük istek sınırı düşünün (bu örnekte yoktur)
  • [ ] Maliyet sayaçlarını düzenli izleyin
  • [ ] HTTPS zorunlu olsun
  • [ ] Veritabanı için root olmayan bir kullanıcı açın
  • [ ] Demo hesaplarının parolalarını değiştirin veya hesapları silin

Sorun Giderme

BelirtiSebepÇözüm
"API anahtarı tanımlı değil".env boş veya okunmuyorSeçili sağlayıcının anahtar satırını kontrol edin (GEMINI_API_KEY, GROQ_API_KEY, ANTHROPIC_API_KEY)
401 geliyorAnahtar geçersiz veya iptal edilmişKonsoldan yeni anahtar üretin
429 sürekliHız sınırıİstekleri yavaşlatın veya kuyruğa alın
Yanıt kesiliyormax_tokens sınırıAI_MAX_TOKENS değerini yükseltin
Maliyet 0,00 görünüyorÜcretsiz katmandasınız (doğru) ya da AI_MODEL fiyat tablosunda yokGerekirse sağlayıcının PRICING tablosuna modeli ekleyin
Bağlantı zaman aşımıcurl eklentisi yok veya giden bağlantı kapalıphp -m \| grep curl; sunucu güvenlik duvarını kontrol edin
Türkçe karakterler bozukBağlantı karakter kümesicharset=utf8mb4 olduğunu doğrulayın
Tüm adresler 404mod_rewrite kapalıAçın veya APP_PRETTY_URLS=false yapın

Yol Haritası

  • [ ] Akış (streaming) desteği — yanıt yazılırken göstermek
  • [ ] Kullanıcı başına günlük jeton/maliyet kotası
  • [ ] Uzun sohbetlerde otomatik özetleme
  • [ ] Sistem istemi (system prompt) yönetimi
  • [ ] Araç kullanımı (tool use) örneği
  • [ ] Ön bellek (prompt caching) jetonlarının ayrı sayılması

Katkı

Hata bildirimi ve öneriler için issue açabilirsiniz.

Lisans

MIT — ticari projelerinizde de özgürce kullanabilirsiniz.


Kaynak kod ağaçtan bir dosya seçin

ai-integration-system
Dosya seçilmedi

İncelemek istediğiniz dosyayı ağaçtan seçin.

Güvenlik gereği kaynak dosyalardaki parola, API anahtarı ve benzeri gizli değerler gösterilmeden önce maskelenir (••••••••).

Sık sorulan sorular

API anahtarını JavaScript'e koysam ne olur?

Sayfayı açan herkes onu görür; F12 ile kaynak sekmesi yeterlidir. Sonuç: anahtarınızla başkaları istek atar, faturayı siz ödersiniz. Kural basittir: API anahtarı sunucudan çıkmaz, tarayıcı yalnızca kendi sunucunuzla konuşur.

Maliyeti nasıl düşürürüm?

Üç kaldıraç var. Model seçimi en büyüğüdür: claude-haiku-4-5 çıkışta claude-opus-5'in beşte biri fiyattadır. İkincisi gönderdiğiniz geçmiş: giriş jetonu her mesajda birikir, uzun sohbetlerde eski mesajları özetleyip özeti gönderin. Üçüncüsü efor seviyesi. Hangisinin işe yaradığını görmek için paneldeki maliyet sayacına bakın.

Giriş jetonu neden her mesajda artıyor?

Çünkü model durum tutmaz. "Önceki mesajımı hatırla" diye bir şey yoktur; her istekte sohbetin tamamı yeniden gönderilir ve yeniden ücretlendirilir. Örnek sohbette bunu somut görürsünüz: 412, sonra 890, sonra 1.503 jeton.

Yanıt cümlenin ortasında kesiliyor, neden?

stop_reason değeri max_tokens demektir: yanıt AI_MAX_TOKENS sınırına çarpmıştır. .env içindeki değeri yükseltin. Düşük tutmak tasarruf gibi görünür ama kesilen yanıtı baştan sormak zorunda kalırsınız, yani iki kez ödersiniz.

Neden resmi SDK kullanılmadı?

Bu proje Messages API'sinin nasıl çalıştığını göstermek için yazıldı: hangi başlıklar gidiyor, yanıt nasıl ayrışıyor, hangi hata kodu geçici. Ayrıca Composer bağımlılığı olmaması, paylaşımlı hosting'e atıp çalıştırabilmeniz demektir.

Demoda neden yanıt üretemiyorum?

Demo sunucusunda API anahtarı tanımlı değildir; herkese açık bir demoda gerçek bir anahtar bulundurmak faturayı ziyaretçilere açmak olurdu. Ekrandaki üç sohbet arayüzü dolu göstermek için elle yazılmış örneklerdir; jeton ve maliyet sayıları gerçek fiyat tablosuyla hesaplanmıştır.

Bu örneğin daha gelişmişi projenizde mi lazım?

İhtiyacınızı anlatın; bu yapıyı sizin sisteminize uyarlayalım ya da size özel geliştirelim.

Projenizi Anlatın

Yorumlar

Henüz yorum yok. Takıldığınız bir yer varsa ilk soruyu siz sorun.

Soru sorun veya yorum yazın

Yorumunuz onaylandıktan sonra yayınlanır. Teknik sorularınıza ekibimiz yanıt verir.

Yayınlanmaz; yalnızca gerektiğinde size ulaşmak için.

En az 10 karakter.

İlgili kod örnekleri

Orta

uygulama-iskeleti/php-multi-language-i18n

Çoklu Dil (i18n) Sistemi

  • PHP 8
  • PDO
  • MySQL
  • Bootstrap 5
  • +2

150 inceleme 48 canlı açılış

Tüm kod örnekleri