From 19cdaf0a855b75dad5236e3d835f7dfe9ce0c612 Mon Sep 17 00:00:00 2001 From: jacoblee93 Date: Thu, 9 Nov 2023 18:47:30 -0800 Subject: [PATCH 1/6] Update cookbook --- cookbook/openai_vision_multimodal.ipynb | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/cookbook/openai_vision_multimodal.ipynb b/cookbook/openai_vision_multimodal.ipynb index 5b317061265..b38df91b38d 100644 --- a/cookbook/openai_vision_multimodal.ipynb +++ b/cookbook/openai_vision_multimodal.ipynb @@ -22,13 +22,13 @@ "AIMessage {\n", " lc_serializable: \u001b[33mtrue\u001b[39m,\n", " lc_kwargs: {\n", - " content: \u001b[32m'The image features the word \"LangChain\" in black lettering. To the left of the text, there is a grap'\u001b[39m... 109 more characters,\n", - " additional_kwargs: { function_call: \u001b[90mundefined\u001b[39m }\n", + " content: \u001b[32m'The image says \"LangChain\". It includes a graphic of a parrot on the left, two interlinked rings (or'\u001b[39m... 105 more characters,\n", + " additional_kwargs: { function_call: \u001b[90mundefined\u001b[39m, tool_calls: \u001b[90mundefined\u001b[39m }\n", " },\n", " lc_namespace: [ \u001b[32m\"langchain\"\u001b[39m, \u001b[32m\"schema\"\u001b[39m ],\n", - " content: \u001b[32m'The image features the word \"LangChain\" in black lettering. To the left of the text, there is a grap'\u001b[39m... 109 more characters,\n", + " content: \u001b[32m'The image says \"LangChain\". It includes a graphic of a parrot on the left, two interlinked rings (or'\u001b[39m... 105 more characters,\n", " name: \u001b[90mundefined\u001b[39m,\n", - " additional_kwargs: { function_call: \u001b[90mundefined\u001b[39m }\n", + " additional_kwargs: { function_call: \u001b[90mundefined\u001b[39m, tool_calls: \u001b[90mundefined\u001b[39m }\n", "}" ] }, @@ -38,10 +38,10 @@ } ], "source": [ - "Deno.env.set(\"OPENAI_API_KEY\", \"\");\n", + "// Deno.env.set(\"OPENAI_API_KEY\", \"\");\n", "\n", - "import { ChatOpenAI } from \"npm:langchain@0.0.182-rc.0/chat_models/openai\";\n", - "import { HumanMessage } from \"npm:langchain@0.0.182-rc.0/schema\";\n", + "import { ChatOpenAI } from \"npm:langchain@0.0.185/chat_models/openai\";\n", + "import { HumanMessage } from \"npm:langchain@0.0.185/schema\";\n", "\n", "const chat = new ChatOpenAI({\n", " modelName: \"gpt-4-vision-preview\",\n", @@ -81,7 +81,7 @@ { "data": { "text/plain": [ - "\u001b[32m`Avast, ye be lookin' at words that spell out \"LangChain,\" with a colorful parrot on the port side an`\u001b[39m... 57 more characters" + "\u001b[32m`Arrr, matey! The image be showin' the text \"LangChain,\" with a colorful parrot on the port side and `\u001b[39m... 52 more characters" ] }, "execution_count": 2, @@ -90,8 +90,8 @@ } ], "source": [ - "import { ChatPromptTemplate, MessagesPlaceholder } from \"npm:langchain@0.0.182-rc.0/prompts\";\n", - "import { StringOutputParser } from \"npm:langchain@0.0.182-rc.0/schema/output_parser\";\n", + "import { ChatPromptTemplate, MessagesPlaceholder } from \"npm:langchain@0.0.185/prompts\";\n", + "import { StringOutputParser } from \"npm:langchain@0.0.185/schema/output_parser\";\n", "\n", "const prompt = ChatPromptTemplate.fromMessages([\n", " [\"system\", \"Answer all questions like a pirate.\"],\n", @@ -122,7 +122,7 @@ { "data": { "text/plain": [ - "\u001b[32m\"Arrr! That be a fine image of a hot dog ye have there! A grilled sausage nestled betwixt halves of a\"\u001b[39m... 84 more characters" + "\u001b[32m\"Arrr, matey! What ye be starin' at is a fine image of a hot dog, perched in a soft bun, ready to be \"\u001b[39m... 182 more characters" ] }, "execution_count": 3, From 25a9206c01c2444b69d9d6ac4e6d15ac559bf25d Mon Sep 17 00:00:00 2001 From: jacoblee93 Date: Fri, 10 Nov 2023 14:09:25 -0800 Subject: [PATCH 2/6] Adds format option to Ollama --- langchain/src/chat_models/ollama.ts | 4 +++ .../chat_models/tests/chatollama.int.test.ts | 27 ++++++++++++++++++- langchain/src/llms/ollama.ts | 4 +++ langchain/src/llms/tests/ollama.int.test.ts | 27 ++++++++++++++++++- langchain/src/util/ollama.ts | 2 ++ 5 files changed, 62 insertions(+), 2 deletions(-) diff --git a/langchain/src/chat_models/ollama.ts b/langchain/src/chat_models/ollama.ts index 6779b9cc432..2cdac5ad628 100644 --- a/langchain/src/chat_models/ollama.ts +++ b/langchain/src/chat_models/ollama.ts @@ -94,6 +94,8 @@ export class ChatOllama vocabOnly?: boolean; + format?: string; + constructor(fields: OllamaInput & BaseChatModelParams) { super(fields); this.model = fields.model ?? this.model; @@ -130,6 +132,7 @@ export class ChatOllama this.useMLock = fields.useMLock; this.useMMap = fields.useMMap; this.vocabOnly = fields.vocabOnly; + this.format = fields.format; } _llmType() { @@ -145,6 +148,7 @@ export class ChatOllama invocationParams(options?: this["ParsedCallOptions"]) { return { model: this.model, + format: this.format, options: { embedding_only: this.embeddingOnly, f16_kv: this.f16KV, diff --git a/langchain/src/chat_models/tests/chatollama.int.test.ts b/langchain/src/chat_models/tests/chatollama.int.test.ts index 45146e785fb..09ec23767eb 100644 --- a/langchain/src/chat_models/tests/chatollama.int.test.ts +++ b/langchain/src/chat_models/tests/chatollama.int.test.ts @@ -4,7 +4,10 @@ import { AIMessage, HumanMessage } from "../../schema/index.js"; import { LLMChain } from "../../chains/llm_chain.js"; import { PromptTemplate } from "../../prompts/prompt.js"; import { BufferMemory } from "../../memory/buffer_memory.js"; -import { BytesOutputParser } from "../../schema/output_parser.js"; +import { + BytesOutputParser, + StringOutputParser, +} from "../../schema/output_parser.js"; test.skip("test call", async () => { const ollama = new ChatOllama({}); @@ -129,3 +132,25 @@ test.skip("should stream through with a bytes output parser", async () => { console.log(chunks.join("")); expect(chunks.length).toBeGreaterThan(1); }); + +test.skip("JSON mode", async () => { + const TEMPLATE = `You are a pirate named Patchy. All responses must be in pirate dialect and in JSON format, with a property named "response" followed by the value. + + User: {input} + AI:`; + + // Infer the input variables from the template + const prompt = PromptTemplate.fromTemplate(TEMPLATE); + + const ollama = new ChatOllama({ + model: "llama2", + baseUrl: "http://127.0.0.1:11434", + format: "json", + }); + const outputParser = new StringOutputParser(); + const chain = prompt.pipe(ollama).pipe(outputParser); + const res = await chain.invoke({ + input: `Translate "I love programming" into German.`, + }); + expect(JSON.parse(res).response).toBeDefined(); +}); diff --git a/langchain/src/llms/ollama.ts b/langchain/src/llms/ollama.ts index 02ad2e64f01..ad359fc41e6 100644 --- a/langchain/src/llms/ollama.ts +++ b/langchain/src/llms/ollama.ts @@ -82,6 +82,8 @@ export class Ollama extends LLM implements OllamaInput { vocabOnly?: boolean; + format?: string; + constructor(fields: OllamaInput & BaseLLMParams) { super(fields); this.model = fields.model ?? this.model; @@ -119,6 +121,7 @@ export class Ollama extends LLM implements OllamaInput { this.useMLock = fields.useMLock; this.useMMap = fields.useMMap; this.vocabOnly = fields.vocabOnly; + this.format = fields.format; } _llmType() { @@ -128,6 +131,7 @@ export class Ollama extends LLM implements OllamaInput { invocationParams(options?: this["ParsedCallOptions"]) { return { model: this.model, + format: this.format, options: { embedding_only: this.embeddingOnly, f16_kv: this.f16KV, diff --git a/langchain/src/llms/tests/ollama.int.test.ts b/langchain/src/llms/tests/ollama.int.test.ts index 87151620144..d63d3cc0dbd 100644 --- a/langchain/src/llms/tests/ollama.int.test.ts +++ b/langchain/src/llms/tests/ollama.int.test.ts @@ -1,7 +1,10 @@ import { test } from "@jest/globals"; import { Ollama } from "../ollama.js"; import { PromptTemplate } from "../../prompts/prompt.js"; -import { BytesOutputParser } from "../../schema/output_parser.js"; +import { + BytesOutputParser, + StringOutputParser, +} from "../../schema/output_parser.js"; test.skip("test call", async () => { const ollama = new Ollama({}); @@ -86,3 +89,25 @@ test.skip("should stream through with a bytes output parser", async () => { console.log(chunks.join("")); expect(chunks.length).toBeGreaterThan(1); }); + +test.skip("JSON mode", async () => { + const TEMPLATE = `You are a pirate named Patchy. All responses must be in pirate dialect and in JSON format, with a property named "response" followed by the value. + + User: {input} + AI:`; + + // Infer the input variables from the template + const prompt = PromptTemplate.fromTemplate(TEMPLATE); + + const ollama = new Ollama({ + model: "llama2", + baseUrl: "http://127.0.0.1:11434", + format: "json", + }); + const outputParser = new StringOutputParser(); + const chain = prompt.pipe(ollama).pipe(outputParser); + const res = await chain.invoke({ + input: `Translate "I love programming" into German.`, + }); + expect(JSON.parse(res).response).toBeDefined(); +}); diff --git a/langchain/src/util/ollama.ts b/langchain/src/util/ollama.ts index c40b33b6f9b..b7d6a968f98 100644 --- a/langchain/src/util/ollama.ts +++ b/langchain/src/util/ollama.ts @@ -34,11 +34,13 @@ export interface OllamaInput { useMLock?: boolean; useMMap?: boolean; vocabOnly?: boolean; + format?: string; } export interface OllamaRequestParams { model: string; prompt: string; + format?: string; options: { embedding_only?: boolean; f16_kv?: boolean; From 538dbc90696f27f0f0e59fe0d5e6e29a826a69ac Mon Sep 17 00:00:00 2001 From: jacoblee93 Date: Fri, 10 Nov 2023 14:13:52 -0800 Subject: [PATCH 3/6] Fix type --- langchain/src/embeddings/ollama.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/langchain/src/embeddings/ollama.ts b/langchain/src/embeddings/ollama.ts index 0e4c5d10f36..e9268da76c7 100644 --- a/langchain/src/embeddings/ollama.ts +++ b/langchain/src/embeddings/ollama.ts @@ -1,7 +1,7 @@ import { OllamaInput, OllamaRequestParams } from "../util/ollama.js"; import { Embeddings, EmbeddingsParams } from "./base.js"; -type CamelCasedRequestOptions = Omit; +type CamelCasedRequestOptions = Omit; /** * Interface for OllamaEmbeddings parameters. Extends EmbeddingsParams and From cf2d957cea47db2df478b1070c71e27e946ae7af Mon Sep 17 00:00:00 2001 From: jacoblee93 Date: Fri, 10 Nov 2023 14:27:44 -0800 Subject: [PATCH 4/6] Format --- langchain/src/embeddings/ollama.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/langchain/src/embeddings/ollama.ts b/langchain/src/embeddings/ollama.ts index e9268da76c7..de9c77797df 100644 --- a/langchain/src/embeddings/ollama.ts +++ b/langchain/src/embeddings/ollama.ts @@ -1,7 +1,10 @@ import { OllamaInput, OllamaRequestParams } from "../util/ollama.js"; import { Embeddings, EmbeddingsParams } from "./base.js"; -type CamelCasedRequestOptions = Omit; +type CamelCasedRequestOptions = Omit< + OllamaInput, + "baseUrl" | "model" | "format" +>; /** * Interface for OllamaEmbeddings parameters. Extends EmbeddingsParams and From 2d07c58e9f9207955b806c61007a798b3962765f Mon Sep 17 00:00:00 2001 From: jacoblee93 Date: Fri, 10 Nov 2023 14:51:49 -0800 Subject: [PATCH 5/6] Factor out utility type --- langchain/src/chat_models/ollama.ts | 3 ++- langchain/src/document_loaders/fs/unstructured.ts | 7 +------ langchain/src/llms/ollama.ts | 3 ++- langchain/src/prompts/base.ts | 3 ++- langchain/src/schema/index.ts | 6 ++---- langchain/src/util/ollama.ts | 5 +++-- langchain/src/util/types.ts | 5 +++++ 7 files changed, 17 insertions(+), 15 deletions(-) create mode 100644 langchain/src/util/types.ts diff --git a/langchain/src/chat_models/ollama.ts b/langchain/src/chat_models/ollama.ts index 2cdac5ad628..66cecee740f 100644 --- a/langchain/src/chat_models/ollama.ts +++ b/langchain/src/chat_models/ollama.ts @@ -8,6 +8,7 @@ import { ChatGenerationChunk, ChatMessage, } from "../schema/index.js"; +import type { StringWithAutocomplete } from "../util/types.js"; /** * An interface defining the options for an Ollama API call. It extends @@ -94,7 +95,7 @@ export class ChatOllama vocabOnly?: boolean; - format?: string; + format?: StringWithAutocomplete<"json">; constructor(fields: OllamaInput & BaseChatModelParams) { super(fields); diff --git a/langchain/src/document_loaders/fs/unstructured.ts b/langchain/src/document_loaders/fs/unstructured.ts index 6301b629c77..58a41570161 100644 --- a/langchain/src/document_loaders/fs/unstructured.ts +++ b/langchain/src/document_loaders/fs/unstructured.ts @@ -8,6 +8,7 @@ import { import { getEnv } from "../../util/env.js"; import { Document } from "../../document.js"; import { BaseDocumentLoader } from "../base.js"; +import type { StringWithAutocomplete } from "../../util/types.js"; const UNSTRUCTURED_API_FILETYPES = [ ".txt", @@ -95,12 +96,6 @@ export type SkipInferTableTypes = */ type ChunkingStrategy = "None" | "by_title"; -/** - * Represents a string value with autocomplete suggestions. It is used for - * the `strategy` property in the UnstructuredLoaderOptions. - */ -type StringWithAutocomplete = T | (string & Record); - export type UnstructuredLoaderOptions = { apiKey?: string; apiUrl?: string; diff --git a/langchain/src/llms/ollama.ts b/langchain/src/llms/ollama.ts index ad359fc41e6..816cfe1e733 100644 --- a/langchain/src/llms/ollama.ts +++ b/langchain/src/llms/ollama.ts @@ -6,6 +6,7 @@ import { } from "../util/ollama.js"; import { CallbackManagerForLLMRun } from "../callbacks/manager.js"; import { GenerationChunk } from "../schema/index.js"; +import type { StringWithAutocomplete } from "../util/types.js"; /** * Class that represents the Ollama language model. It extends the base @@ -82,7 +83,7 @@ export class Ollama extends LLM implements OllamaInput { vocabOnly?: boolean; - format?: string; + format?: StringWithAutocomplete<"json">; constructor(fields: OllamaInput & BaseLLMParams) { super(fields); diff --git a/langchain/src/prompts/base.ts b/langchain/src/prompts/base.ts index 85be60b4300..4573a70316c 100644 --- a/langchain/src/prompts/base.ts +++ b/langchain/src/prompts/base.ts @@ -14,9 +14,10 @@ import { SerializedBasePromptTemplate } from "./serde.js"; import { SerializedFields } from "../load/map_keys.js"; import { Runnable } from "../schema/runnable/index.js"; import { BaseCallbackConfig } from "../callbacks/manager.js"; +import type { StringWithAutocomplete } from "../util/types.js"; export type TypedPromptInputValues = InputValues< - Extract | (string & Record) + StringWithAutocomplete> >; /** diff --git a/langchain/src/schema/index.ts b/langchain/src/schema/index.ts index 204c70279c0..cfb96dfab6b 100644 --- a/langchain/src/schema/index.ts +++ b/langchain/src/schema/index.ts @@ -1,6 +1,7 @@ import type { OpenAI as OpenAIClient } from "openai"; import { Document } from "../document.js"; import { Serializable, SerializedConstructor } from "../load/serializable.js"; +import type { StringWithAutocomplete } from "../util/types.js"; export const RUN_KEY = "__run"; @@ -622,10 +623,7 @@ export class ChatMessage export type BaseMessageLike = | BaseMessage - | [ - MessageType | "user" | "assistant" | (string & Record), - string - ] + | [StringWithAutocomplete, string] | string; export function isBaseMessage( diff --git a/langchain/src/util/ollama.ts b/langchain/src/util/ollama.ts index b7d6a968f98..2250585dd85 100644 --- a/langchain/src/util/ollama.ts +++ b/langchain/src/util/ollama.ts @@ -1,5 +1,6 @@ import { BaseLanguageModelCallOptions } from "../base_language/index.js"; import { IterableReadableStream } from "./stream.js"; +import type { StringWithAutocomplete } from "./types.js"; export interface OllamaInput { embeddingOnly?: boolean; @@ -34,13 +35,13 @@ export interface OllamaInput { useMLock?: boolean; useMMap?: boolean; vocabOnly?: boolean; - format?: string; + format?: StringWithAutocomplete<"json">; } export interface OllamaRequestParams { model: string; prompt: string; - format?: string; + format?: StringWithAutocomplete<"json">; options: { embedding_only?: boolean; f16_kv?: boolean; diff --git a/langchain/src/util/types.ts b/langchain/src/util/types.ts new file mode 100644 index 00000000000..5ea4d49ad8a --- /dev/null +++ b/langchain/src/util/types.ts @@ -0,0 +1,5 @@ +/** + * Represents a string value with autocompleted, but not required, suggestions. + */ + +export type StringWithAutocomplete = T | (string & Record); From f030c57fa213006bba4e34d9f2a60683925e763c Mon Sep 17 00:00:00 2001 From: jacoblee93 Date: Fri, 10 Nov 2023 16:04:18 -0800 Subject: [PATCH 6/6] Update docs --- docs/docs/integrations/chat/ollama.mdx | 10 ++++++ .../chat/integration_ollama_json_mode.ts | 32 +++++++++++++++++++ 2 files changed, 42 insertions(+) create mode 100644 examples/src/models/chat/integration_ollama_json_mode.ts diff --git a/docs/docs/integrations/chat/ollama.mdx b/docs/docs/integrations/chat/ollama.mdx index a9a693c41e7..476acef83bd 100644 --- a/docs/docs/integrations/chat/ollama.mdx +++ b/docs/docs/integrations/chat/ollama.mdx @@ -21,3 +21,13 @@ import CodeBlock from "@theme/CodeBlock"; import OllamaExample from "@examples/models/chat/integration_ollama.ts"; {OllamaExample} + +## JSON mode + +Ollama also supports a JSON mode that coerces model outputs to only return JSON. Here's an example of how this can be useful for extraction: + +import OllamaExample from "@examples/models/chat/integration_ollama_json_mode.ts"; + +{OllamaExample} + +You can see a simple LangSmith trace of this here: https://smith.langchain.com/public/92aebeca-d701-4de0-a845-f55df04eff04/r diff --git a/examples/src/models/chat/integration_ollama_json_mode.ts b/examples/src/models/chat/integration_ollama_json_mode.ts new file mode 100644 index 00000000000..f2b741e928a --- /dev/null +++ b/examples/src/models/chat/integration_ollama_json_mode.ts @@ -0,0 +1,32 @@ +import { ChatOllama } from "langchain/chat_models/ollama"; +import { ChatPromptTemplate } from "langchain/prompts"; + +const prompt = ChatPromptTemplate.fromMessages([ + [ + "system", + `You are an expert translator. Format all responses as JSON objects with two keys: "original" and "translated".`, + ], + ["human", `Translate "{input}" into {language}.`], +]); + +const model = new ChatOllama({ + baseUrl: "http://localhost:11434", // Default value + model: "llama2", // Default value + format: "json", +}); + +const chain = prompt.pipe(model); + +const result = await chain.invoke({ + input: "I love programming", + language: "German", +}); + +console.log(result); + +/* + AIMessage { + content: '{"original": "I love programming", "translated": "Ich liebe das Programmieren"}', + additional_kwargs: {} + } +*/