repositories
loading repo index
repositories
loading repo index
repository
loading code, commits, and activity
public Clawd ADK gateway launch mirror
stars
latest
clone command
git clone gitlawb://did:key:z6Mkq5mY...iFZ5/my-project-publ...git clone gitlawb://did:key:z6Mkq5mY.../my-project-publ...2fa351d6docs: add automaton and perps launch sources16d ago| #1 | import { BaseLanguageModel } from "@langchain/core/language_models/base"; |
| #2 | import { |
| #3 | AIMessage, |
| #4 | HumanMessage, |
| #5 | SystemMessage, |
| #6 | BaseMessage, |
| #7 | } from "@langchain/core/messages"; |
| #8 | import { z } from "zod"; |
| #9 | import { LLM, LLMResponse } from "./base"; |
| #10 | import { LLMConfig, Message } from "../types/index"; |
| #11 | // Import the schemas directly into LangchainLLM |
| #12 | import { FactRetrievalSchema, MemoryUpdateSchema } from "../prompts"; |
| #13 | // Import graph tool argument schemas |
| #14 | import { |
| #15 | GraphExtractEntitiesArgsSchema, |
| #16 | GraphRelationsArgsSchema, |
| #17 | GraphSimpleRelationshipArgsSchema, // Used for delete tool |
| #18 | } from "../graphs/tools"; |
| #19 | |
| #20 | const convertToLangchainMessages = (messages: Message[]): BaseMessage[] => { |
| #21 | return messages.map((msg) => { |
| #22 | const content = |
| #23 | typeof msg.content === "string" |
| #24 | ? msg.content |
| #25 | : JSON.stringify(msg.content); |
| #26 | switch (msg.role?.toLowerCase()) { |
| #27 | case "system": |
| #28 | return new SystemMessage(content); |
| #29 | case "user": |
| #30 | case "human": |
| #31 | return new HumanMessage(content); |
| #32 | case "assistant": |
| #33 | case "ai": |
| #34 | return new AIMessage(content); |
| #35 | default: |
| #36 | console.warn( |
| #37 | `Unsupported message role '${msg.role}' for Langchain. Treating as 'human'.`, |
| #38 | ); |
| #39 | return new HumanMessage(content); |
| #40 | } |
| #41 | }); |
| #42 | }; |
| #43 | |
| #44 | export class LangchainLLM implements LLM { |
| #45 | private llmInstance: BaseLanguageModel; |
| #46 | private modelName: string; |
| #47 | |
| #48 | constructor(config: LLMConfig) { |
| #49 | if (!config.model || typeof config.model !== "object") { |
| #50 | throw new Error( |
| #51 | "Langchain provider requires an initialized Langchain instance passed via the 'model' field in the LLM config.", |
| #52 | ); |
| #53 | } |
| #54 | if (typeof (config.model as any).invoke !== "function") { |
| #55 | throw new Error( |
| #56 | "Provided Langchain 'instance' in the 'model' field does not appear to be a valid Langchain language model (missing invoke method).", |
| #57 | ); |
| #58 | } |
| #59 | this.llmInstance = config.model as BaseLanguageModel; |
| #60 | this.modelName = |
| #61 | (this.llmInstance as any).modelId || |
| #62 | (this.llmInstance as any).model || |
| #63 | "langchain-model"; |
| #64 | } |
| #65 | |
| #66 | async generateResponse( |
| #67 | messages: Message[], |
| #68 | response_format?: { type: string }, |
| #69 | tools?: any[], |
| #70 | ): Promise<string | LLMResponse> { |
| #71 | const langchainMessages = convertToLangchainMessages(messages); |
| #72 | let runnable: any = this.llmInstance; |
| #73 | const invokeOptions: Record<string, any> = {}; |
| #74 | let isStructuredOutput = false; |
| #75 | let selectedSchema: z.ZodSchema<any> | null = null; |
| #76 | let isToolCallResponse = false; |
| #77 | |
| #78 | // --- Internal Schema Selection Logic (runs regardless of response_format) --- |
| #79 | const systemPromptContent = |
| #80 | (messages.find((m) => m.role === "system")?.content as string) || ""; |
| #81 | const userPromptContent = |
| #82 | (messages.find((m) => m.role === "user")?.content as string) || ""; |
| #83 | const toolNames = tools?.map((t) => t.function.name) || []; |
| #84 | |
| #85 | // Prioritize tool call argument schemas |
| #86 | if (toolNames.includes("extract_entities")) { |
| #87 | selectedSchema = GraphExtractEntitiesArgsSchema; |
| #88 | isToolCallResponse = true; |
| #89 | } else if (toolNames.includes("establish_relationships")) { |
| #90 | selectedSchema = GraphRelationsArgsSchema; |
| #91 | isToolCallResponse = true; |
| #92 | } else if (toolNames.includes("delete_graph_memory")) { |
| #93 | selectedSchema = GraphSimpleRelationshipArgsSchema; |
| #94 | isToolCallResponse = true; |
| #95 | } |
| #96 | // Check for memory prompts if no tool schema matched |
| #97 | else if ( |
| #98 | systemPromptContent.includes("Personal Information Organizer") && |
| #99 | systemPromptContent.includes("extract relevant pieces of information") |
| #100 | ) { |
| #101 | selectedSchema = FactRetrievalSchema; |
| #102 | } else if ( |
| #103 | userPromptContent.includes("smart memory manager") && |
| #104 | userPromptContent.includes("Compare newly retrieved facts") |
| #105 | ) { |
| #106 | selectedSchema = MemoryUpdateSchema; |
| #107 | } |
| #108 | |
| #109 | // --- Apply Structured Output if Schema Selected --- |
| #110 | if ( |
| #111 | selectedSchema && |
| #112 | typeof (this.llmInstance as any).withStructuredOutput === "function" |
| #113 | ) { |
| #114 | // Apply if a schema was selected (for memory or single tool calls) |
| #115 | if ( |
| #116 | !isToolCallResponse || |
| #117 | (isToolCallResponse && tools && tools.length === 1) |
| #118 | ) { |
| #119 | try { |
| #120 | runnable = (this.llmInstance as any).withStructuredOutput( |
| #121 | selectedSchema, |
| #122 | { name: tools?.[0]?.function.name }, |
| #123 | ); |
| #124 | isStructuredOutput = true; |
| #125 | } catch (e) { |
| #126 | isStructuredOutput = false; // Ensure flag is false on error |
| #127 | // No fallback to response_format here unless explicitly passed |
| #128 | if (response_format?.type === "json_object") { |
| #129 | invokeOptions.response_format = { type: "json_object" }; |
| #130 | } |
| #131 | } |
| #132 | } else if (isToolCallResponse) { |
| #133 | // If multiple tools, don't apply structured output, handle via tool binding below |
| #134 | } |
| #135 | } else if (selectedSchema && response_format?.type === "json_object") { |
| #136 | // Schema selected, but no .withStructuredOutput. Try basic response_format only if explicitly requested. |
| #137 | if ( |
| #138 | (this.llmInstance as any)._identifyingParams?.response_format || |
| #139 | (this.llmInstance as any).response_format |
| #140 | ) { |
| #141 | invokeOptions.response_format = { type: "json_object" }; |
| #142 | } |
| #143 | } else if (!selectedSchema && response_format?.type === "json_object") { |
| #144 | // Explicit JSON request, but no schema inferred. Try basic response_format. |
| #145 | if ( |
| #146 | (this.llmInstance as any)._identifyingParams?.response_format || |
| #147 | (this.llmInstance as any).response_format |
| #148 | ) { |
| #149 | invokeOptions.response_format = { type: "json_object" }; |
| #150 | } |
| #151 | } |
| #152 | |
| #153 | // --- Handle tool binding --- |
| #154 | if (tools && tools.length > 0) { |
| #155 | if (typeof (runnable as any).bindTools === "function") { |
| #156 | try { |
| #157 | runnable = (runnable as any).bindTools(tools); |
| #158 | } catch (e) {} |
| #159 | } else { |
| #160 | } |
| #161 | } |
| #162 | |
| #163 | // --- Invoke and Process Response --- |
| #164 | try { |
| #165 | const response = await runnable.invoke(langchainMessages, invokeOptions); |
| #166 | |
| #167 | if (isStructuredOutput && !isToolCallResponse) { |
| #168 | // Memory prompt with structured output |
| #169 | return JSON.stringify(response); |
| #170 | } else if (isStructuredOutput && isToolCallResponse) { |
| #171 | // Tool call with structured arguments |
| #172 | if (response?.tool_calls && Array.isArray(response.tool_calls)) { |
| #173 | const mappedToolCalls = response.tool_calls.map((call: any) => ({ |
| #174 | name: call.name || tools?.[0]?.function.name || "unknown_tool", |
| #175 | arguments: |
| #176 | typeof call.args === "string" |
| #177 | ? call.args |
| #178 | : JSON.stringify(call.args), |
| #179 | })); |
| #180 | return { |
| #181 | content: response.content || "", |
| #182 | role: "assistant", |
| #183 | toolCalls: mappedToolCalls, |
| #184 | }; |
| #185 | } else { |
| #186 | // Direct object response for tool args |
| #187 | return { |
| #188 | content: "", |
| #189 | role: "assistant", |
| #190 | toolCalls: [ |
| #191 | { |
| #192 | name: tools?.[0]?.function.name || "unknown_tool", |
| #193 | arguments: JSON.stringify(response), |
| #194 | }, |
| #195 | ], |
| #196 | }; |
| #197 | } |
| #198 | } else if ( |
| #199 | response && |
| #200 | response.tool_calls && |
| #201 | Array.isArray(response.tool_calls) |
| #202 | ) { |
| #203 | // Standard tool call response (no structured output used/failed) |
| #204 | const mappedToolCalls = response.tool_calls.map((call: any) => ({ |
| #205 | name: call.name || "unknown_tool", |
| #206 | arguments: |
| #207 | typeof call.args === "string" |
| #208 | ? call.args |
| #209 | : JSON.stringify(call.args), |
| #210 | })); |
| #211 | return { |
| #212 | content: response.content || "", |
| #213 | role: "assistant", |
| #214 | toolCalls: mappedToolCalls, |
| #215 | }; |
| #216 | } else if (response && typeof response.content === "string") { |
| #217 | // Standard text response |
| #218 | return response.content; |
| #219 | } else { |
| #220 | // Fallback for unexpected formats |
| #221 | return JSON.stringify(response); |
| #222 | } |
| #223 | } catch (error) { |
| #224 | throw error; |
| #225 | } |
| #226 | } |
| #227 | |
| #228 | async generateChat(messages: Message[]): Promise<LLMResponse> { |
| #229 | const langchainMessages = convertToLangchainMessages(messages); |
| #230 | try { |
| #231 | const response = await this.llmInstance.invoke(langchainMessages); |
| #232 | if (response && typeof response.content === "string") { |
| #233 | return { |
| #234 | content: response.content, |
| #235 | role: (response as BaseMessage).lc_id ? "assistant" : "assistant", |
| #236 | }; |
| #237 | } else { |
| #238 | console.warn( |
| #239 | `Unexpected response format from Langchain instance (${this.modelName}) for generateChat:`, |
| #240 | response, |
| #241 | ); |
| #242 | return { |
| #243 | content: JSON.stringify(response), |
| #244 | role: "assistant", |
| #245 | }; |
| #246 | } |
| #247 | } catch (error) { |
| #248 | console.error( |
| #249 | `Error invoking Langchain instance (${this.modelName}) for generateChat:`, |
| #250 | error, |
| #251 | ); |
| #252 | throw error; |
| #253 | } |
| #254 | } |
| #255 | } |
| #256 |