






















@@ -0,0 +1,149 @@
1+import { describe, expect, it } from "vitest";
2+import { createOllamaEmbeddingProvider } from "./src/embedding-provider.js";
3+import { createOllamaStreamFn } from "./src/stream.js";
4+import { createOllamaWebSearchProvider } from "./src/web-search-provider.js";
5+6+const LIVE = process.env.OPENCLAW_LIVE_TEST === "1" && process.env.OPENCLAW_LIVE_OLLAMA === "1";
7+const OLLAMA_BASE_URL =
8+process.env.OPENCLAW_LIVE_OLLAMA_BASE_URL?.trim() || "http://127.0.0.1:11434";
9+const CHAT_MODEL = process.env.OPENCLAW_LIVE_OLLAMA_MODEL?.trim() || "llama3.2:latest";
10+const EMBEDDING_MODEL =
11+process.env.OPENCLAW_LIVE_OLLAMA_EMBED_MODEL?.trim() || "embeddinggemma:latest";
12+const PROVIDER_ID = process.env.OPENCLAW_LIVE_OLLAMA_PROVIDER_ID?.trim() || "ollama-live-custom";
13+const RUN_WEB_SEARCH = process.env.OPENCLAW_LIVE_OLLAMA_WEB_SEARCH !== "0";
14+15+async function collectStreamEvents<T>(stream: AsyncIterable<T>): Promise<T[]> {
16+const events: T[] = [];
17+for await (const event of stream) {
18+events.push(event);
19+}
20+return events;
21+}
22+23+describe.skipIf(!LIVE)("ollama live", () => {
24+it("runs native chat with a custom provider prefix and normalized tool schemas", async () => {
25+const streamFn = createOllamaStreamFn(OLLAMA_BASE_URL);
26+let payload:
27+| {
28+model?: string;
29+tools?: Array<{
30+function?: {
31+parameters?: {
32+properties?: Record<string, { type?: string }>;
33+};
34+};
35+}>;
36+}
37+| undefined;
38+39+const stream = streamFn(
40+{
41+id: `${PROVIDER_ID}/${CHAT_MODEL}`,
42+api: "ollama",
43+provider: PROVIDER_ID,
44+contextWindow: 8192,
45+} as never,
46+{
47+messages: [{ role: "user", content: "Reply exactly OK." }],
48+tools: [
49+{
50+name: "lookup_weather",
51+description: "Lookup weather for a city.",
52+parameters: {
53+properties: {
54+city: { enum: ["London", "Vienna"] },
55+units: { enum: ["metric", "imperial"] },
56+options: {
57+properties: {
58+includeWind: { type: "boolean" },
59+},
60+},
61+},
62+required: ["city"],
63+},
64+},
65+],
66+} as never,
67+{
68+maxTokens: 32,
69+temperature: 0,
70+onPayload: (body: unknown) => {
71+payload = body as NonNullable<typeof payload>;
72+},
73+} as never,
74+);
75+76+const events = await collectStreamEvents(await Promise.resolve(stream));
77+const error = events.find((event) => (event as { type?: string }).type === "error");
78+79+expect(error).toBeUndefined();
80+expect(events.some((event) => (event as { type?: string }).type === "done")).toBe(true);
81+expect(payload?.model).toBe(CHAT_MODEL);
82+const properties = payload?.tools?.[0]?.function?.parameters?.properties;
83+expect(properties?.city?.type).toBe("string");
84+expect(properties?.units?.type).toBe("string");
85+expect(properties?.options?.type).toBe("object");
86+}, 60_000);
87+88+it("embeds a batch through the current Ollama endpoint for custom providers", async () => {
89+const { client } = await createOllamaEmbeddingProvider({
90+config: {
91+models: {
92+providers: {
93+[PROVIDER_ID]: {
94+api: "ollama",
95+baseUrl: OLLAMA_BASE_URL,
96+apiKey: "ollama-local",
97+},
98+},
99+},
100+},
101+provider: PROVIDER_ID,
102+model: `${PROVIDER_ID}/${EMBEDDING_MODEL}`,
103+} as never);
104+105+const embeddings = await client.embedBatch(["hello", "world"]);
106+107+expect(embeddings).toHaveLength(2);
108+expect(embeddings[0]?.length ?? 0).toBeGreaterThan(0);
109+expect(embeddings[1]?.length).toBe(embeddings[0]?.length);
110+expect(Math.hypot(...embeddings[0])).toBeGreaterThan(0.99);
111+expect(Math.hypot(...embeddings[0])).toBeLessThan(1.01);
112+}, 45_000);
113+114+it.skipIf(!RUN_WEB_SEARCH)(
115+"searches through Ollama web search fallback endpoints",
116+async () => {
117+const provider = createOllamaWebSearchProvider();
118+const tool = provider.createTool({
119+config: {
120+models: {
121+providers: {
122+ollama: {
123+api: "ollama",
124+baseUrl: OLLAMA_BASE_URL,
125+apiKey: "ollama-local",
126+},
127+},
128+},
129+},
130+} as never);
131+if (!tool) {
132+throw new Error("Ollama web-search provider did not create a tool");
133+}
134+135+const result = (await tool.execute({
136+query: "OpenClaw documentation",
137+count: 1,
138+})) as {
139+provider?: string;
140+results?: Array<{ url?: string }>;
141+};
142+143+expect(result.provider).toBe("ollama");
144+expect(result.results?.length ?? 0).toBeGreaterThan(0);
145+expect(result.results?.[0]?.url).toMatch(/^https?:\/\//);
146+},
147+45_000,
148+);
149+});
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。