fix(ted-ceviri): GLM harf bozulmasını onar, TED çevirisinde önce GLM 5.3
Canlı ölçüm (2026-09-11): glm-5.3-flash 22 başlığın 4'ünde kelime içi
büyük İ ve büyük harfe dönen kuyruk üretti ("Arazİ araçları",
"Elektrİklİ", "temİNİ"); açıklamalar temizdi.
- harfOnar: kelime küçük harfle devam ettikten sonra gelen büyükleri Türkçe
kuralıyla küçültür; tamamı büyük kısaltmalara dokunmaz. Model çıktısına
ve daha önce önbelleğe girmiş kayıtlara okunurken uygulanır.
- Çeviri prompt'una Türkçe yazım kuralı uyarısı eklendi.
- ZAI_TRANSLATE_MODELS varsayılanı glm-5.3 → glm-5.3-flash (paketler 6
ilanlık, hız kaybı küçük; başlıklar kullanıcıya doğrudan görünüyor).
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
+22
-3
@@ -51,7 +51,25 @@ function yaz(ikn, baslik_tr, aciklama_tr, kaynak) {
|
|||||||
// glm-5.3-flash OpenRouter üzerinden 71 sn düşünüp boş dönüyor (doğrudan
|
// glm-5.3-flash OpenRouter üzerinden 71 sn düşünüp boş dönüyor (doğrudan
|
||||||
// Z.ai'de reasoning_effort:low ile bu sorun yok, bkz. zai.mjs).
|
// Z.ai'de reasoning_effort:low ile bu sorun yok, bkz. zai.mjs).
|
||||||
const OR_MODELLER = ['deepseek/deepseek-v4-flash', 'google/gemini-3.7-flash', 'z-ai/glm-5.3-flash'];
|
const OR_MODELLER = ['deepseek/deepseek-v4-flash', 'google/gemini-3.7-flash', 'z-ai/glm-5.3-flash'];
|
||||||
const SISTEM = 'Sen kamu ihale terminolojisine hâkim bir çevirmensin. Verilen AB (TED) ihale başlık ve açıklamalarını doğal, resmî Türkçeye çevir. Başlık "Ülke – Konu – İlan adı" biçimindedir: ülke adını da Türkçeleştir (Austria→Avusturya). Kurum adlarını çevirme, sayıları/tarihleri aynen koru. Yalnızca JSON döndür: {"ceviriler":[{"id":"...","baslik":"...","aciklama":"..."}]} — id alanını aynen kopyala; açıklama yoksa boş string.';
|
const SISTEM = 'Sen kamu ihale terminolojisine hâkim bir çevirmensin. Verilen AB (TED) ihale başlık ve açıklamalarını doğal, resmî Türkçeye çevir. Başlık "Ülke – Konu – İlan adı" biçimindedir: ülke adını da Türkçeleştir (Austria→Avusturya). Kurum adlarını çevirme, sayıları/tarihleri aynen koru. Türkçe yazım kurallarına uy: kelime içinde büyük İ veya I kullanma (doğru: "elektrikli otobüsler", yanlış: "Elektrİklİ"), yalnız cümle başı ve özel adlar büyük harfle başlar, i/ı ayrımını koru (Türkiye, temini, istasyonları). Yalnızca JSON döndür: {"ceviriler":[{"id":"...","baslik":"...","aciklama":"..."}]} — id alanını aynen kopyala; açıklama yoksa boş string.';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GLM çıktısında görülen harf bozulmasını onarır (2026-09-11 canlı ölçümü:
|
||||||
|
* 22 başlığın 4'ünde "Arazİ araçları", "Elektrİklİ", "temİNİ" gibi kelime
|
||||||
|
* içi büyük İ ve büyük harfe dönen kuyruklar). Türkçede bir kelime küçük
|
||||||
|
* harfle devam ettikten sonra büyük harfe geri dönmez; bu yüzden ilk küçük
|
||||||
|
* harften sonraki büyükler Türkçe kuralıyla (İ→i, I→ı) küçültülür. Tamamı
|
||||||
|
* büyük kısaltmalara (NATURA, MEB) dokunmaz.
|
||||||
|
*/
|
||||||
|
export function harfOnar(metin) {
|
||||||
|
return String(metin || '').replace(/\p{L}+/gu, (kelime) => {
|
||||||
|
const ilkKucuk = kelime.search(/\p{Ll}/u);
|
||||||
|
if (ilkKucuk === -1) return kelime;
|
||||||
|
const kuyruk = kelime.slice(ilkKucuk + 1);
|
||||||
|
if (!/\p{Lu}/u.test(kuyruk)) return kelime;
|
||||||
|
return kelime.slice(0, ilkKucuk + 1) + kuyruk.toLocaleLowerCase('tr');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/** Çeviri yapılabilir mi: Z.ai ya da OpenRouter anahtarından biri yeter. */
|
/** Çeviri yapılabilir mi: Z.ai ya da OpenRouter anahtarından biri yeter. */
|
||||||
export const ceviriMumkun = () => Boolean(zaiKey() || openRouterKey());
|
export const ceviriMumkun = () => Boolean(zaiKey() || openRouterKey());
|
||||||
@@ -106,7 +124,7 @@ async function parcayiCevir(parca) {
|
|||||||
for (const t of parca) {
|
for (const t of parca) {
|
||||||
const x = map.get(String(t.id));
|
const x = map.get(String(t.id));
|
||||||
if (!x || !x.baslik) continue;
|
if (!x || !x.baslik) continue;
|
||||||
t.baslikTr = String(x.baslik).trim(); t.aciklamaTr = x.aciklama ? String(x.aciklama).trim() : null; t.ceviriKaynagi = 'model';
|
t.baslikTr = harfOnar(String(x.baslik).trim()); t.aciklamaTr = x.aciklama ? harfOnar(String(x.aciklama).trim()) : null; t.ceviriKaynagi = 'model';
|
||||||
if (t.ikn) yaz(t.ikn, t.baslikTr, t.aciklamaTr, 'model');
|
if (t.ikn) yaz(t.ikn, t.baslikTr, t.aciklamaTr, 'model');
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
@@ -128,7 +146,8 @@ export async function tedCevir(tenders, { blockMs = 12000 } = {}) {
|
|||||||
if (t.baslikTr) continue;
|
if (t.baslikTr) continue;
|
||||||
if (t.baslikTur) { t.baslikTr = t.baslikTur; t.aciklamaTr = t.aciklamaTur || null; t.ceviriKaynagi = 'ted'; continue; }
|
if (t.baslikTur) { t.baslikTr = t.baslikTur; t.aciklamaTr = t.aciklamaTur || null; t.ceviriKaynagi = 'ted'; continue; }
|
||||||
const c = t.ikn ? oku(t.ikn) : null;
|
const c = t.ikn ? oku(t.ikn) : null;
|
||||||
if (c && c.baslik_tr) { t.baslikTr = c.baslik_tr; t.aciklamaTr = c.aciklama_tr || null; t.ceviriKaynagi = 'onbellek'; continue; }
|
// harfOnar: düzeltme öncesi önbelleğe girmiş bozuk başlıklar da okunurken onarılır.
|
||||||
|
if (c && c.baslik_tr) { t.baslikTr = harfOnar(c.baslik_tr); t.aciklamaTr = c.aciklama_tr ? harfOnar(c.aciklama_tr) : null; t.ceviriKaynagi = 'onbellek'; continue; }
|
||||||
if (!devamEden.has(t.ikn)) bekleyen.push(t);
|
if (!devamEden.has(t.ikn)) bekleyen.push(t);
|
||||||
}
|
}
|
||||||
if (!ceviriMumkun()) return 0;
|
if (!ceviriMumkun()) return 0;
|
||||||
|
|||||||
+5
-2
@@ -14,11 +14,14 @@
|
|||||||
* ZAI_MAX_CONCURRENCY eşzamanlı istek sınırı (varsayılan 2)
|
* ZAI_MAX_CONCURRENCY eşzamanlı istek sınırı (varsayılan 2)
|
||||||
* ZAI_RETRIES_429 oran sınırında aynı modelle yeniden deneme (varsayılan 4)
|
* ZAI_RETRIES_429 oran sınırında aynı modelle yeniden deneme (varsayılan 4)
|
||||||
* ZAI_RETRY_BASE_MS yeniden deneme taban beklemesi (varsayılan 3000)
|
* ZAI_RETRY_BASE_MS yeniden deneme taban beklemesi (varsayılan 3000)
|
||||||
* ZAI_TRANSLATE_MODELS çeviri model listesi (virgülle; varsayılan glm-5.3-flash,glm-5.3)
|
* ZAI_TRANSLATE_MODELS çeviri model listesi (virgülle; varsayılan glm-5.3,glm-5.3-flash)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const VARSAYILAN_URL = 'https://api.z.ai/api/paas/v4/chat/completions';
|
const VARSAYILAN_URL = 'https://api.z.ai/api/paas/v4/chat/completions';
|
||||||
export const DEFAULT_ZAI_TRANSLATE_MODELS = ['glm-5.3-flash', 'glm-5.3'];
|
// TED başlıkları kullanıcıya doğrudan görünür ve paketler küçüktür (6 ilan);
|
||||||
|
// canlıda glm-5.3-flash başlıkların ~%20'sinde harf bozdu (kelime içi İ),
|
||||||
|
// bu yüzden önce GLM 5.3, Flash yedek.
|
||||||
|
export const DEFAULT_ZAI_TRANSLATE_MODELS = ['glm-5.3', 'glm-5.3-flash'];
|
||||||
|
|
||||||
/** Anahtar çağrı anında okunur; süreç yeniden başlatılmadan da ayar değişebilir. */
|
/** Anahtar çağrı anında okunur; süreç yeniden başlatılmadan da ayar değişebilir. */
|
||||||
export const zaiKey = () => (process.env.ZAI_API_KEY || '').trim() || null;
|
export const zaiKey = () => (process.env.ZAI_API_KEY || '').trim() || null;
|
||||||
|
|||||||
+11
-3
@@ -2,7 +2,7 @@ import { test } from 'node:test';
|
|||||||
import assert from 'node:assert/strict';
|
import assert from 'node:assert/strict';
|
||||||
import { dusunmeAyari, zaiChat, gecici429, zaiAktifIstek } from '../server/zai.mjs';
|
import { dusunmeAyari, zaiChat, gecici429, zaiAktifIstek } from '../server/zai.mjs';
|
||||||
import { analyzeSpec, routeFor } from '../server/ai.mjs';
|
import { analyzeSpec, routeFor } from '../server/ai.mjs';
|
||||||
import { tedCevir } from '../server/ted-ceviri.mjs';
|
import { tedCevir, harfOnar } from '../server/ted-ceviri.mjs';
|
||||||
|
|
||||||
const ZAI_URL = 'https://api.z.ai/api/coding/paas/v4/chat/completions';
|
const ZAI_URL = 'https://api.z.ai/api/coding/paas/v4/chat/completions';
|
||||||
const ok = (content) => new Response(JSON.stringify({ choices: [{ message: { content } }] }), { status: 200 });
|
const ok = (content) => new Response(JSON.stringify({ choices: [{ message: { content } }] }), { status: 200 });
|
||||||
@@ -73,7 +73,7 @@ test('analyzeSpec Z.ai anahtarı varken GLM 5.3\'ü doğrudan Z.ai\'den çağır
|
|||||||
} finally { globalThis.fetch = orijinal; geri(); }
|
} finally { globalThis.fetch = orijinal; geri(); }
|
||||||
});
|
});
|
||||||
|
|
||||||
test('TED çevirisi OpenRouter anahtarı olmadan Z.ai GLM 5.3 Flash ile çalışır', async () => {
|
test('TED çevirisi OpenRouter anahtarı olmadan önce Z.ai GLM 5.3 ile çalışır', async () => {
|
||||||
const geri = ortam(TEMEL); const orijinal = globalThis.fetch;
|
const geri = ortam(TEMEL); const orijinal = globalThis.fetch;
|
||||||
try {
|
try {
|
||||||
const istekler = [];
|
const istekler = [];
|
||||||
@@ -87,8 +87,16 @@ test('TED çevirisi OpenRouter anahtarı olmadan Z.ai GLM 5.3 Flash ile çalış
|
|||||||
assert.equal(bekleyen, 0);
|
assert.equal(bekleyen, 0);
|
||||||
assert.equal(istekler.length, 1);
|
assert.equal(istekler.length, 1);
|
||||||
assert.equal(istekler[0].url, ZAI_URL);
|
assert.equal(istekler[0].url, ZAI_URL);
|
||||||
assert.equal(istekler[0].model, 'glm-5.3-flash');
|
assert.equal(istekler[0].model, 'glm-5.3');
|
||||||
assert.equal(tenders[0].baslikTr, 'TR Austria – Works');
|
assert.equal(tenders[0].baslikTr, 'TR Austria – Works');
|
||||||
assert.equal(tenders[1].ceviriKaynagi, 'model');
|
assert.equal(tenders[1].ceviriKaynagi, 'model');
|
||||||
} finally { globalThis.fetch = orijinal; geri(); }
|
} finally { globalThis.fetch = orijinal; geri(); }
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('harfOnar kelime içi büyük İ/I bozulmasını düzeltir, kısaltmalara dokunmaz', () => {
|
||||||
|
assert.equal(harfOnar('Romanya – Elektrİklİ otobüsler – Elektrİklİ otobüs ve şarj İstasyonlarİ temİNİ'),
|
||||||
|
'Romanya – Elektrikli otobüsler – Elektrikli otobüs ve şarj İstasyonlari temini');
|
||||||
|
assert.equal(harfOnar('Denİz Habİtatlarının ve NATURA 2000 Alanlarının Tespİtİ'), 'Deniz Habitatlarının ve NATURA 2000 Alanlarının Tespiti');
|
||||||
|
assert.equal(harfOnar('İstanbul MEB İl Müdürlüğü'), 'İstanbul MEB İl Müdürlüğü');
|
||||||
|
assert.equal(harfOnar(null), '');
|
||||||
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user