


























1+import OpenAI from "openai";
2+import type { ChatCompletionCreateParamsNonStreaming } from "openai/resources/chat/completions.js";
3+import type { ResponseCreateParamsNonStreaming } from "openai/resources/responses/responses.js";
4+import { describe, expect, it } from "vitest";
5+import type { Context, Model } from "../llm/types.js";
6+import { isLiveTestEnabled } from "./live-test-helpers.js";
7+import {
8+buildOpenAICompletionsParams,
9+buildOpenAIResponsesParams,
10+} from "./openai-transport-stream.js";
11+12+const OPENAI_KEY = process.env.OPENAI_API_KEY ?? "";
13+const LIVE = isLiveTestEnabled(["OPENAI_LIVE_TEST"]) && Boolean(OPENAI_KEY);
14+const describeLive = LIVE ? describe : describe.skip;
15+16+const probeTools = [
17+{
18+name: "unreadable_probe",
19+description: "Unreadable probe.",
20+parameters: {
21+type: "object",
22+get properties(): never {
23+throw new Error("live unreadable nested schema getter");
24+},
25+},
26+},
27+{
28+name: "live_probe",
29+description: "Return the requested probe value.",
30+parameters: {
31+type: "object",
32+properties: { value: { type: "string" } },
33+required: ["value"],
34+additionalProperties: false,
35+},
36+},
37+];
38+39+const context = {
40+systemPrompt: "Call the requested function exactly once.",
41+messages: [
42+{
43+role: "user",
44+content: "Call live_probe with value exactly OPENAI_PROJECTION_OK.",
45+timestamp: 1,
46+},
47+],
48+tools: probeTools,
49+} satisfies Context;
50+51+describeLive("OpenAI tool projection live", () => {
52+const modelId = process.env.OPENCLAW_LIVE_OPENAI_TOOL_MODEL || "gpt-5.5";
53+const client = new OpenAI({ apiKey: OPENAI_KEY });
54+55+it("calls a healthy Responses function after quarantining an unreadable sibling", async () => {
56+const model = {
57+id: modelId,
58+name: modelId,
59+api: "openai-responses",
60+provider: "openai",
61+baseUrl: "https://api.openai.com/v1",
62+reasoning: true,
63+input: ["text"],
64+cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
65+contextWindow: 200000,
66+maxTokens: 256,
67+} satisfies Model<"openai-responses">;
68+const params = buildOpenAIResponsesParams(model, context, {
69+maxTokens: 128,
70+reasoning: "low",
71+toolChoice: { type: "function", name: "live_probe" },
72+});
73+74+const response = await client.responses.create({
75+ ...params,
76+stream: false,
77+} as unknown as ResponseCreateParamsNonStreaming);
78+const toolCall = response.output.find((item) => item.type === "function_call");
79+80+expect(toolCall).toMatchObject({
81+type: "function_call",
82+name: "live_probe",
83+});
84+expect(JSON.parse(toolCall?.arguments ?? "{}")).toEqual({
85+value: "OPENAI_PROJECTION_OK",
86+});
87+}, 45_000);
88+89+it("calls a healthy Chat Completions function after quarantining an unreadable sibling", async () => {
90+const model = {
91+id: modelId,
92+name: modelId,
93+api: "openai-completions",
94+provider: "openai",
95+baseUrl: "https://api.openai.com/v1",
96+reasoning: false,
97+input: ["text"],
98+cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
99+contextWindow: 200000,
100+maxTokens: 256,
101+} satisfies Model<"openai-completions">;
102+const params = buildOpenAICompletionsParams(model, context, {
103+maxTokens: 128,
104+toolChoice: {
105+type: "allowed_tools",
106+allowed_tools: {
107+mode: "required",
108+tools: [
109+{ type: "function", function: { name: "unreadable_probe" } },
110+{ type: "function", function: { name: "live_probe" } },
111+],
112+},
113+},
114+});
115+const { stream_options: _streamOptions, ...nonStreamingParams } = params;
116+117+const response = await client.chat.completions.create({
118+ ...nonStreamingParams,
119+stream: false,
120+} as unknown as ChatCompletionCreateParamsNonStreaming);
121+const toolCall = response.choices[0]?.message.tool_calls?.[0];
122+123+if (!toolCall || toolCall.type !== "function") {
124+throw new Error("OpenAI did not return the expected function tool call");
125+}
126+expect(toolCall).toMatchObject({
127+type: "function",
128+function: { name: "live_probe" },
129+});
130+expect(JSON.parse(toolCall.function.arguments)).toEqual({
131+value: "OPENAI_PROJECTION_OK",
132+});
133+}, 45_000);
134+});
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。