SPB Git forge

spb/polyllm

Public
15commits 1branches 0releases
2.2 MBsize
maindefault branch
13 days agolast push
TypeScript 97.4% SQL 1% JavaScript 0.9% CSS 0.6%
6.6 KB · 108 lines typescript
Raw Blame History
1// Probe 03: per model — (d) function-calling round trip with streaming, (e) structured output via responseJsonSchema.2// Plus (h) googleSearch grounding and code execution on selected models.3import { ai, MODELS, SUFFIX, save, errInfo, shortText, withRetry, sleep, PACE_MS } from "./lib.js";45const results: any = {};6const weatherTool = {7  functionDeclarations: [{8    name: "get_weather",9    description: "Get the current weather for a city.",10    parametersJsonSchema: { type: "object", properties: { city: { type: "string" }, unit: { type: "string", enum: ["C", "F"] } }, required: ["city"] },11  }],12};1314for (const model of MODELS) {15  const r: any = (results[model] = {});1617  // (d) function calling, streaming, then send functionResponse18  try {19    const contents: any[] = [{ role: "user", parts: [{ text: "What is the weather in Montreal right now? Use the tool." }] }];20    const stream = await withRetry(() => ai.models.generateContentStream({ model, contents, config: { tools: [weatherTool], maxOutputTokens: 2000 } }));21    const chunks: any[] = [];22    let modelParts: any[] = [];23    for await (const c of stream) {24      const parts = c.candidates?.[0]?.content?.parts ?? [];25      chunks.push({ parts: parts.map((p: any) => ({ keys: Object.keys(p), fc: p.functionCall, thought: p.thought, sig: p.thoughtSignature ? `${p.thoughtSignature.length} chars` : undefined })), finishReason: c.candidates?.[0]?.finishReason, usage: c.usageMetadata ? Object.keys(c.usageMetadata) : null });26      modelParts.push(...parts);27    }28    const fcParts = modelParts.filter((p) => p.functionCall);29    r.functionCall = { ok: true, chunkCount: chunks.length, chunks, functionCalls: fcParts.map((p) => p.functionCall), sigOnFcPart: fcParts.map((p) => !!p.thoughtSignature) };30    if (fcParts.length) {31      // Round trip: append model content (with thoughtSignature preserved) and functionResponse parts32      contents.push({ role: "model", parts: modelParts });33      contents.push({34        role: "user",35        parts: fcParts.map((p) => ({ functionResponse: { name: p.functionCall.name, id: p.functionCall.id, response: { temperatureC: 21, condition: "sunny" } } })),36      });37      await sleep(PACE_MS);38      const res2 = await withRetry(() => ai.models.generateContent({ model, contents, config: { tools: [weatherTool], maxOutputTokens: 2000 } }));39      r.functionCall.roundTrip = { text: shortText(res2), finishReason: res2.candidates?.[0]?.finishReason, usage: res2.usageMetadata };40    }41  } catch (e) {42    r.functionCall = { ok: false, error: errInfo(e) };43  }4445  // (e) structured output responseJsonSchema46  try {47    await sleep(PACE_MS);48    const res = await withRetry(() => ai.models.generateContent({49      model,50      contents: "Extract: 'Alice is 30 and lives in Paris.'",51      config: {52        maxOutputTokens: 2000,53        responseMimeType: "application/json",54        responseJsonSchema: { type: "object", properties: { name: { type: "string" }, age: { type: "integer" }, city: { type: "string" } }, required: ["name", "age", "city"], additionalProperties: false },55      },56    }));57    const txt = shortText(res);58    let parsed: any = null; try { parsed = JSON.parse(res.candidates?.[0]?.content?.parts?.filter((p: any) => !p.thought).map((p: any) => p.text).join("") ?? ""); } catch {}59    r.structured = { ok: true, text: txt, parsedOk: !!parsed, parsed, finishReason: res.candidates?.[0]?.finishReason };60  } catch (e) {61    r.structured = { ok: false, error: errInfo(e) };62  }63  await sleep(PACE_MS);64  console.log(model, "fc:", r.functionCall.ok ? `${r.functionCall.functionCalls.length} call(s) chunks=${r.functionCall.chunkCount} rt="${r.functionCall.roundTrip?.text?.slice(0, 50)}"` : r.functionCall.error.message.slice(0, 200), "| structured:", r.structured.ok ? r.structured.text.slice(0, 80) : r.structured.error.message.slice(0, 200));65}6667const EXTRAS = process.argv[3] === "extras";68// (h) Google Search grounding (3.x models; 2.5 is 404 for this key)69for (const model of EXTRAS ? [MODELS[0]] : []) {70  await sleep(PACE_MS);71  try {72    const res = await withRetry(() => ai.models.generateContent({ model, contents: "Who won the most recent FIFA World Cup final and what was the score? One sentence.", config: { tools: [{ googleSearch: {} }], maxOutputTokens: 3000 } }));73    const gm: any = res.candidates?.[0]?.groundingMetadata;74    results[`search:${model}`] = {75      ok: true, text: shortText(res),76      groundingMetadataKeys: gm ? Object.keys(gm) : null,77      webSearchQueries: gm?.webSearchQueries,78      groundingChunksSample: gm?.groundingChunks?.slice(0, 2),79      groundingSupportsSample: gm?.groundingSupports?.slice(0, 2),80      searchEntryPointKeys: gm?.searchEntryPoint ? Object.keys(gm.searchEntryPoint) : null,81      renderedContentLen: gm?.searchEntryPoint?.renderedContent?.length,82      usage: res.usageMetadata,83      partKeys: res.candidates?.[0]?.content?.parts?.map((p: any) => Object.keys(p)),84    };85  } catch (e) { results[`search:${model}`] = { ok: false, error: errInfo(e) }; }86  console.log("search", model, JSON.stringify(results[`search:${model}`]).slice(0, 300));87}8889// Code execution on one model90if (EXTRAS) try {91  await sleep(PACE_MS);92  const res = await withRetry(() => ai.models.generateContent({ model: MODELS[0], contents: "Compute the 30th Fibonacci number with Python and report it.", config: { tools: [{ codeExecution: {} }], maxOutputTokens: 3000 } }));93  results[`codeExecution:${MODELS[0]}`] = { ok: true, parts: res.candidates?.[0]?.content?.parts?.map((p: any) => ({ keys: Object.keys(p), executableCode: p.executableCode, codeExecutionResult: p.codeExecutionResult, text: p.text?.slice(0, 100) })), usage: res.usageMetadata };94} catch (e) { results[`codeExecution:${MODELS[0]}`] = { ok: false, error: errInfo(e) }; }95if (EXTRAS) console.log("codeExec", JSON.stringify(results[`codeExecution:${MODELS[0]}`]).slice(0, 400));9697// toolConfig mode ANY (+ streamFunctionCallArguments is Vertex-only: SDK throws before sending) on 3.898if (EXTRAS) try {99  await sleep(PACE_MS);100  const stream = await withRetry(() => ai.models.generateContentStream({ model: MODELS[0], contents: "Weather in Quebec City?", config: { tools: [weatherTool], toolConfig: { functionCallingConfig: { mode: "ANY" as any, allowedFunctionNames: ["get_weather"] } }, maxOutputTokens: 2000 } }));101  const chunks: any[] = [];102  for await (const c of stream) chunks.push(c.candidates?.[0]?.content?.parts);103  results[`fcModeAny:${MODELS[0]}`] = { ok: true, chunks };104} catch (e) { results[`fcModeAny:${MODELS[0]}`] = { ok: false, error: errInfo(e) }; }105if (EXTRAS) console.log("fcAny", JSON.stringify(results[`fcModeAny:${MODELS[0]}`]).slice(0, 500));106107save(`03-tools${SUFFIX}.json`, results);108