Files
ihaleist/tests/zai.test.mjs
T
hikmettanandClaude Fable 5.1 7fa71f1a4e
Quality gates / test-build (push) Canceled after 0s
Quality gates / container (push) Canceled after 0s
feat(ai): Z.ai GLM birincil sağlayıcı; OpenRouter yalnız yedek
OpenRouter anahtarı iptal olup bakiye eksiye düşünce şartname analizi,
TED çevirisi ve site taraması durmuştu. Artık ZAI_API_KEY ile GLM 5.3 ve
GLM 5.3 Flash doğrudan Z.ai Coding Plan üzerinden çağrılır; kalan modeller
OpenRouter anahtarı varsa yedek olarak denenir.

- server/zai.mjs: doğrudan istemci; glm-5.x için reasoning_effort:low,
  glm-4.x için thinking kapalı; süreç genelinde en fazla 2 eşzamanlı istek
  (kullanıcı istekleri arka plan çevirisinden öncelikli); geçici 429'da
  aynı modelle yeniden deneme, 1113 bakiye hatasında hemen sıradaki model.
- ai.mjs: 'zai' (GLM 5.3) sağlayıcısı eklendi, Z.ai yolu yeni istemciyi
  kullanıyor; varsayılan sıra zai → glmflash → gemini → …
- ted-ceviri.mjs: önce ZAI_TRANSLATE_MODELS (glm-5.3-flash, glm-5.3),
  sonra OpenRouter; 2'li eşzamanlı parça işleme.
- Admin model kataloğuna GLM 5.3 eklendi, kayıtlı zincirler sürüm 4'e taşınır.
- tests/zai.test.mjs: düşünme ayarı, 429/1113 davranışı, eşzamanlılık,
  analyzeSpec ve TED çevirisinin Z.ai'ye gittiği.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-10 23:11:41 +03:00

95 lines
5.1 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { dusunmeAyari, zaiChat, gecici429, zaiAktifIstek } from '../server/zai.mjs';
import { analyzeSpec, routeFor } from '../server/ai.mjs';
import { tedCevir } from '../server/ted-ceviri.mjs';
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 hata429 = (code) => new Response(JSON.stringify({ error: { code, message: 'x' } }), { status: 429 });
function ortam(vars) {
const eski = {};
for (const [k, v] of Object.entries(vars)) { eski[k] = process.env[k]; if (v === undefined) delete process.env[k]; else process.env[k] = v; }
return () => { for (const [k, v] of Object.entries(eski)) { if (v === undefined) delete process.env[k]; else process.env[k] = v; } };
}
const TEMEL = { ZAI_API_KEY: 'synthetic-test-only', ZAI_BASE_URL: ZAI_URL, ZAI_RETRY_BASE_MS: '0', ZAI_MAX_CONCURRENCY: '2', OPENROUTER_API_KEY: undefined, GEMINI_API_KEY: undefined };
test('düşünme ayarı modele göre: glm-5 reasoning_effort low, glm-4 thinking kapalı', () => {
const geri = ortam({ ZAI_REASONING_EFFORT: undefined });
try {
assert.deepEqual(dusunmeAyari('glm-5.3'), { reasoning_effort: 'low' });
assert.deepEqual(dusunmeAyari('glm-5.3-flash'), { reasoning_effort: 'low' });
assert.deepEqual(dusunmeAyari('glm-5.3', null), {});
assert.deepEqual(dusunmeAyari('glm-4.7-flash'), { thinking: { type: 'disabled' } });
} finally { geri(); }
});
test('geçici 429 aynı modelle yeniden denenir, bakiye hatası (1113) hemen fırlatılır', async () => {
const geri = ortam(TEMEL); const orijinal = globalThis.fetch;
try {
const govdeler = [];
globalThis.fetch = async (url, { body }) => { govdeler.push(JSON.parse(body)); return govdeler.length === 1 ? hata429('1302') : ok('{"a":1}'); };
const sonuc = await zaiChat('glm-5.3-flash', [{ role: 'user', content: 'x' }], { retries429: 2 });
assert.equal(sonuc, '{"a":1}');
assert.equal(govdeler.length, 2);
assert.equal(govdeler[1].model, 'glm-5.3-flash');
assert.equal(govdeler[1].reasoning_effort, 'low');
assert.deepEqual(govdeler[1].response_format, { type: 'json_object' });
let cagri = 0;
globalThis.fetch = async () => { cagri++; return hata429('1113'); };
await assert.rejects(zaiChat('glm-5.3', [{ role: 'user', content: 'x' }], { retries429: 3 }), /1113/);
assert.equal(cagri, 1);
assert.equal(gecici429('{"error":{"code":"1302"}}'), true);
} finally { globalThis.fetch = orijinal; geri(); }
});
test('eşzamanlı Z.ai isteği sınırı süreç genelinde 2 ile sınırlı', async () => {
const geri = ortam(TEMEL); const orijinal = globalThis.fetch;
try {
let acik = 0, tepe = 0;
globalThis.fetch = async () => { acik++; tepe = Math.max(tepe, acik); await new Promise((r) => setTimeout(r, 20)); acik--; return ok('{}'); };
await Promise.all([1, 2, 3, 4].map(() => zaiChat('glm-5.3-flash', [{ role: 'user', content: 'x' }])));
assert.equal(tepe, 2);
assert.equal(zaiAktifIstek(), 0);
} finally { globalThis.fetch = orijinal; geri(); }
});
test('analyzeSpec Z.ai anahtarı varken GLM 5.3\'ü doğrudan Z.ai\'den çağırır', async () => {
const geri = ortam(TEMEL); const orijinal = globalThis.fetch;
try {
assert.deepEqual(routeFor('zai'), { yol: 'direct', key: 'synthetic-test-only', model: 'glm-5.3' });
const istekler = [];
globalThis.fetch = async (url, { body }) => { istekler.push({ url, body: JSON.parse(body) }); return ok(JSON.stringify({ summary: 'özet', qualificationCriteria: [], criticalRequirements: [], risksAndPenalties: [], bidAnalysis: {} })); };
const out = await analyzeSpec({ text: 'Sentetik şartname metni', chain: ['zai'] });
assert.equal(istekler.length, 1);
assert.equal(istekler[0].url, ZAI_URL);
assert.equal(istekler[0].body.model, 'glm-5.3');
assert.equal(istekler[0].body.reasoning_effort, 'low');
assert.equal(out.model.provider, 'Z.ai');
assert.equal(out.model.yol, 'direct');
assert.equal(out.result.summary, 'özet');
} finally { globalThis.fetch = orijinal; geri(); }
});
test('TED çevirisi OpenRouter anahtarı olmadan Z.ai GLM 5.3 Flash ile çalışır', async () => {
const geri = ortam(TEMEL); const orijinal = globalThis.fetch;
try {
const istekler = [];
globalThis.fetch = async (url, { body }) => {
const b = JSON.parse(body); istekler.push({ url, model: b.model });
const ilanlar = JSON.parse(b.messages[1].content).ilanlar;
return ok(JSON.stringify({ ceviriler: ilanlar.map((i) => ({ id: i.id, baslik: `TR ${i.baslik}`, aciklama: '' })) }));
};
const tenders = [{ id: 't1', baslik: 'Austria – Works' }, { id: 't2', baslik: 'Poland – Supplies' }];
const bekleyen = await tedCevir(tenders, { blockMs: 2000 });
assert.equal(bekleyen, 0);
assert.equal(istekler.length, 1);
assert.equal(istekler[0].url, ZAI_URL);
assert.equal(istekler[0].model, 'glm-5.3-flash');
assert.equal(tenders[0].baslikTr, 'TR Austria – Works');
assert.equal(tenders[1].ceviriKaynagi, 'model');
} finally { globalThis.fetch = orijinal; geri(); }
});