@stacksjs/ai 0.75.151 → 0.75.153
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/buddy.js +2 -2
- package/dist/drivers/anthropic/index.d.ts +16 -0
- package/dist/drivers/anthropic/index.js +1 -1
- package/dist/drivers/index.d.ts +1 -1
- package/dist/drivers/index.js +1 -1
- package/dist/image.js +1 -1
- package/dist/personalization.js +2 -2
- package/dist/search.js +2 -2
- package/package.json +4 -4
package/dist/buddy.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{existsSync,mkdirSync,writeFileSync}from"node:fs";import{homedir}from"node:os";import{dirname,join}from"node:path";import{claudeAgent}from"./agents";import{createAnthropicDriver,createClaudeAgentSDKDriver,createOllamaDriver,createOpenAIDriver}from"./drivers";import{buildProjectContext}from"./context";import{DEFAULT_ANTHROPIC_MODEL}from"./models";export const CONFIG={workDir:join(homedir(),"Code",".buddy-repos"),commitMessage:"chore: wip",ollamaHost:process.env.OLLAMA_HOST||"http://localhost:11434",ollamaModel:process.env.OLLAMA_MODEL||"llama3.2"},apiKeys={anthropic:process.env.ANTHROPIC_API_KEY,openai:process.env.OPENAI_API_KEY,claudeCliHost:process.env.BUDDY_EC2_HOST};const state={repo:null,conversationHistory:[],currentDriver:"claude-cli-local",github:null};if(!existsSync(CONFIG.workDir))mkdirSync(CONFIG.workDir,{recursive:!0});export const buddyState={getState:()=>state,setRepo:(repo)=>{state.repo=repo},setCurrentDriver:(driver)=>{state.currentDriver=driver},setGitHub:(github)=>{state.github=github},addToHistory:(message)=>{state.conversationHistory.push(message)},clearHistory:()=>{state.conversationHistory=[]}};export function buildSystemPrompt(context){return`You are Buddy, an AI code assistant that helps users modify codebases through voice commands.
|
|
1
|
+
import{existsSync,mkdirSync,writeFileSync}from"node:fs";import{homedir}from"node:os";import{dirname,join}from"node:path";import{claudeAgent}from"./agents";import{anthropicApiVersion,createAnthropicDriver,createClaudeAgentSDKDriver,createOllamaDriver,createOpenAIDriver}from"./drivers";import{buildProjectContext}from"./context";import{DEFAULT_ANTHROPIC_MODEL}from"./models";export const CONFIG={workDir:join(homedir(),"Code",".buddy-repos"),commitMessage:"chore: wip",ollamaHost:process.env.OLLAMA_HOST||"http://localhost:11434",ollamaModel:process.env.OLLAMA_MODEL||"llama3.2"},apiKeys={anthropic:process.env.ANTHROPIC_API_KEY,openai:process.env.OPENAI_API_KEY,claudeCliHost:process.env.BUDDY_EC2_HOST};const state={repo:null,conversationHistory:[],currentDriver:"claude-cli-local",github:null};if(!existsSync(CONFIG.workDir))mkdirSync(CONFIG.workDir,{recursive:!0});export const buddyState={getState:()=>state,setRepo:(repo)=>{state.repo=repo},setCurrentDriver:(driver)=>{state.currentDriver=driver},setGitHub:(github)=>{state.github=github},addToHistory:(message)=>{state.conversationHistory.push(message)},clearHistory:()=>{state.conversationHistory=[]}};export function buildSystemPrompt(context){return`You are Buddy, an AI code assistant that helps users modify codebases through voice commands.
|
|
2
2
|
|
|
3
3
|
${state.repo?`You are working on the repository: ${state.repo.name}
|
|
4
4
|
Branch: ${state.repo.branch}
|
|
@@ -79,5 +79,5 @@ Here is our conversation so far:
|
|
|
79
79
|
## Current Request
|
|
80
80
|
`;contextualCommand=conversationContext+command}const result=await claudeAgent.processStreaming(contextualCommand,currentState.repo.path);result.fullResponse.then((response)=>{buddyState.addToHistory({role:"user",content:command});buddyState.addToHistory({role:"assistant",content:response})});return result}export async function buddyStreamSimple(command,history){if(!apiKeys.anthropic)throw Error("Anthropic API key not set. Configure your API key in settings.");const messages=[];if(history&&history.length>0)for(const msg of history)messages.push({role:msg.role,content:msg.content});messages.push({role:"user",content:command});const systemPrompt=`You are a helpful AI assistant. Answer questions naturally and conversationally.
|
|
81
81
|
You can discuss any topic - technology, science, philosophy, everyday questions, or anything else the user asks about.
|
|
82
|
-
Be concise but thorough. If the user asks about coding or their project specifically, help with that too.`,encoder=new TextEncoder;let fullResponse="",resolveFullResponse;const fullResponsePromise=new Promise((resolve)=>{resolveFullResponse=resolve});return{stream:new ReadableStream({async start(controller){try{const response=await fetch("https://api.anthropic.com/v1/messages",{method:"POST",headers:{"Content-Type":"application/json","x-api-key":apiKeys.anthropic,"anthropic-version":
|
|
82
|
+
Be concise but thorough. If the user asks about coding or their project specifically, help with that too.`,encoder=new TextEncoder;let fullResponse="",resolveFullResponse;const fullResponsePromise=new Promise((resolve)=>{resolveFullResponse=resolve});return{stream:new ReadableStream({async start(controller){try{const response=await fetch("https://api.anthropic.com/v1/messages",{method:"POST",headers:{"Content-Type":"application/json","x-api-key":apiKeys.anthropic,"anthropic-version":anthropicApiVersion()},body:JSON.stringify({model:DEFAULT_ANTHROPIC_MODEL,max_tokens:4096,system:systemPrompt,stream:!0,messages})});if(!response.ok){const error=await response.text();throw Error(`Claude API error: ${error}`)}const reader=response.body?.getReader();if(!reader)throw Error("No response body");const decoder=new TextDecoder;let buffer="";while(!0){const{done,value}=await reader.read();if(done)break;buffer+=decoder.decode(value,{stream:!0});const lines=buffer.split(`
|
|
83
83
|
`);buffer=lines.pop()||"";for(const line of lines)if(line.startsWith("data: ")){const data=line.slice(6);if(data==="[DONE]")continue;try{const event=JSON.parse(data);if(event.type==="content_block_delta"&&event.delta?.text){const text=event.delta.text;fullResponse+=text;controller.enqueue(encoder.encode(text))}}catch{}}}buddyState.addToHistory({role:"user",content:command});buddyState.addToHistory({role:"assistant",content:fullResponse});resolveFullResponse(fullResponse);controller.close()}catch(error){resolveFullResponse(fullResponse);controller.error(error)}}}),fullResponse:fullResponsePromise}}
|
|
@@ -1,4 +1,20 @@
|
|
|
1
1
|
import type { AIDriver, AIDriverConfig, AIMessage, AIResult, AIStreamEvent, ChatCompletionOptions } from '../../types';
|
|
2
|
+
/**
|
|
3
|
+
* The `anthropic-version` header a request to Anthropic sends.
|
|
4
|
+
*
|
|
5
|
+
* Every Anthropic request in this package has to agree on this, and five of
|
|
6
|
+
* them did not: `search.ts`, `image.ts` (twice), `buddy.ts` and
|
|
7
|
+
* `personalization.ts` each wrote the date inline and none of them had heard of
|
|
8
|
+
* `anthropicVersion`. So an application that configured a version got it on
|
|
9
|
+
* chat and streaming, through this driver, and was silently ignored on RAG,
|
|
10
|
+
* vision, buddy and personalization. One API answering two versions, with one
|
|
11
|
+
* of them unconfigurable, is the same shape as the Facebook and Instagram
|
|
12
|
+
* drivers disagreeing by three years (stacksjs/stacks#2859).
|
|
13
|
+
*
|
|
14
|
+
* `override` is for a caller that has its own config in hand; with none, an
|
|
15
|
+
* application's `configure({ anthropicVersion })` wins, and the default is last.
|
|
16
|
+
*/
|
|
17
|
+
export declare function anthropicApiVersion(override?: string): string;
|
|
2
18
|
/**
|
|
3
19
|
* Configure Anthropic globally
|
|
4
20
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{fetchWithRetry}from"../../utils/retry";import{sseJson,streamText}from"../../utils/stream";import{parseToolArguments}from"../../utils/tools";import{recordUsage}from"../../utils/usage";import{normalizeMessagesForProvider}from"../../utils/vision";import{DEFAULT_ANTHROPIC_MODEL}from"../../models";const DEFAULT_MODEL=DEFAULT_ANTHROPIC_MODEL,DEFAULT_MAX_TOKENS=4096,DEFAULT_VERSION="2023-06-01",BASE_URL="https://api.anthropic.com/v1";let globalConfig=null;export function configure(config){globalConfig=config}function getConfig(config){const merged={...globalConfig,...config};if(!merged.apiKey)merged.apiKey=process.env.ANTHROPIC_API_KEY||"";return merged}export function createAnthropicDriver(config){const{apiKey,model=DEFAULT_MODEL,maxTokens=DEFAULT_MAX_TOKENS,anthropicVersion=DEFAULT_VERSION}=config;return{name:"Claude API",async process(command,systemPrompt,history){if(!apiKey)throw Error("Anthropic API key not set. Configure your API key in settings.");const response=await fetchWithRetry(`${BASE_URL}/messages`,{method:"POST",headers:{"Content-Type":"application/json","x-api-key":apiKey,"anthropic-version":anthropicVersion},body:JSON.stringify({model,max_tokens:maxTokens,system:systemPrompt,messages:[...history,{role:"user",content:command}]})});if(!response.ok){const error=await response.text();throw Error(`Claude API error: ${error}`)}const data=await response.json();if(!data.content||data.content.length===0)throw Error("Claude API returned empty content");return data.content[0].text},async*stream(command,systemPrompt,history){if(!apiKey)throw Error("Anthropic API key not set. Configure your API key in settings.");const response=await fetchWithRetry(`${BASE_URL}/messages`,{method:"POST",headers:{"Content-Type":"application/json","x-api-key":apiKey,"anthropic-version":anthropicVersion},body:JSON.stringify({model,max_tokens:maxTokens,system:systemPrompt,stream:!0,messages:[...history,{role:"user",content:command}]})});if(!response.ok){const error=await response.text();throw Error(`Claude API error: ${error}`)}if(!response.body)throw Error("No response body");for await(const event of sseJson(response.body,"Claude API")){if(event.type==="error"){const msg=event.error?.message??JSON.stringify(event.error??event);throw Error(`[anthropic/stream] mid-stream error: ${msg}`)}if(event.type==="content_block_delta"&&event.delta?.text)yield event.delta.text}}}}function messagesRequest(messages,options){const{model=DEFAULT_MODEL,maxTokens=DEFAULT_MAX_TOKENS,temperature,topP,stop,system,tools,toolChoice,responseFormat}=options,body={model,max_tokens:maxTokens,temperature,top_p:topP,stop_sequences:stop?Array.isArray(stop)?stop:[stop]:void 0,system,messages:normalizeMessagesForProvider(messages,"anthropic")};if(tools&&tools.length>0){body.tools=tools.map((t)=>({name:t.name,description:t.description,input_schema:t.parameters??{type:"object",properties:{}}}));if(toolChoice!==void 0)body.tool_choice=mapAnthropicToolChoice(toolChoice)}if(responseFormat&&responseFormat.type!=="text"){const outputTool=buildAnthropicJsonTool(responseFormat),existing=Array.isArray(body.tools)?body.tools:[];body.tools=[...existing,outputTool];if(toolChoice===void 0)body.tool_choice={type:"tool",name:outputTool.name};return{body,outputTool:outputTool.name}}return{body}}function postMessages(body){const config=getConfig();return fetchWithRetry(`${BASE_URL}/messages`,{method:"POST",headers:{"Content-Type":"application/json","x-api-key":config.apiKey,"anthropic-version":config.anthropicVersion
|
|
1
|
+
import{fetchWithRetry}from"../../utils/retry";import{sseJson,streamText}from"../../utils/stream";import{parseToolArguments}from"../../utils/tools";import{recordUsage}from"../../utils/usage";import{normalizeMessagesForProvider}from"../../utils/vision";import{DEFAULT_ANTHROPIC_MODEL}from"../../models";const DEFAULT_MODEL=DEFAULT_ANTHROPIC_MODEL,DEFAULT_MAX_TOKENS=4096,DEFAULT_VERSION="2023-06-01",BASE_URL="https://api.anthropic.com/v1";let globalConfig=null;export function anthropicApiVersion(override){return override||globalConfig?.anthropicVersion||DEFAULT_VERSION}export function configure(config){globalConfig=config}function getConfig(config){const merged={...globalConfig,...config};if(!merged.apiKey)merged.apiKey=process.env.ANTHROPIC_API_KEY||"";return merged}export function createAnthropicDriver(config){const{apiKey,model=DEFAULT_MODEL,maxTokens=DEFAULT_MAX_TOKENS,anthropicVersion=DEFAULT_VERSION}=config;return{name:"Claude API",async process(command,systemPrompt,history){if(!apiKey)throw Error("Anthropic API key not set. Configure your API key in settings.");const response=await fetchWithRetry(`${BASE_URL}/messages`,{method:"POST",headers:{"Content-Type":"application/json","x-api-key":apiKey,"anthropic-version":anthropicVersion},body:JSON.stringify({model,max_tokens:maxTokens,system:systemPrompt,messages:[...history,{role:"user",content:command}]})});if(!response.ok){const error=await response.text();throw Error(`Claude API error: ${error}`)}const data=await response.json();if(!data.content||data.content.length===0)throw Error("Claude API returned empty content");return data.content[0].text},async*stream(command,systemPrompt,history){if(!apiKey)throw Error("Anthropic API key not set. Configure your API key in settings.");const response=await fetchWithRetry(`${BASE_URL}/messages`,{method:"POST",headers:{"Content-Type":"application/json","x-api-key":apiKey,"anthropic-version":anthropicVersion},body:JSON.stringify({model,max_tokens:maxTokens,system:systemPrompt,stream:!0,messages:[...history,{role:"user",content:command}]})});if(!response.ok){const error=await response.text();throw Error(`Claude API error: ${error}`)}if(!response.body)throw Error("No response body");for await(const event of sseJson(response.body,"Claude API")){if(event.type==="error"){const msg=event.error?.message??JSON.stringify(event.error??event);throw Error(`[anthropic/stream] mid-stream error: ${msg}`)}if(event.type==="content_block_delta"&&event.delta?.text)yield event.delta.text}}}}function messagesRequest(messages,options){const{model=DEFAULT_MODEL,maxTokens=DEFAULT_MAX_TOKENS,temperature,topP,stop,system,tools,toolChoice,responseFormat}=options,body={model,max_tokens:maxTokens,temperature,top_p:topP,stop_sequences:stop?Array.isArray(stop)?stop:[stop]:void 0,system,messages:normalizeMessagesForProvider(messages,"anthropic")};if(tools&&tools.length>0){body.tools=tools.map((t)=>({name:t.name,description:t.description,input_schema:t.parameters??{type:"object",properties:{}}}));if(toolChoice!==void 0)body.tool_choice=mapAnthropicToolChoice(toolChoice)}if(responseFormat&&responseFormat.type!=="text"){const outputTool=buildAnthropicJsonTool(responseFormat),existing=Array.isArray(body.tools)?body.tools:[];body.tools=[...existing,outputTool];if(toolChoice===void 0)body.tool_choice={type:"tool",name:outputTool.name};return{body,outputTool:outputTool.name}}return{body}}function postMessages(body){const config=getConfig();return fetchWithRetry(`${BASE_URL}/messages`,{method:"POST",headers:{"Content-Type":"application/json","x-api-key":config.apiKey,"anthropic-version":anthropicApiVersion(config.anthropicVersion)},body:JSON.stringify(body)})}export async function chat(messages,options={}){const startedAt=Date.now(),{body,outputTool}=messagesRequest(messages,options),response=await postMessages(body);if(!response.ok){const error=await response.text();throw Error(`Claude API error: ${error}`)}const data=await response.json();if(!data.content||data.content.length===0)throw Error("Claude API returned empty content");const blocks=data.content,structured=outputTool?blocks.find((b)=>b.type==="tool_use"&&b.name===outputTool):void 0,toolCalls=blocks.filter((b)=>b.type==="tool_use"&&b!==structured).map((b)=>({id:b.id??"",name:b.name??"",arguments:b.input??{}})),result={content:structured?JSON.stringify(structured.input):blocks.filter((b)=>b.type==="text").map((b)=>b.text??"").join(""),...toolCalls.length>0?{toolCalls}:{},model:data.model,usage:{promptTokens:data.usage?.input_tokens||0,completionTokens:data.usage?.output_tokens||0,totalTokens:(data.usage?.input_tokens||0)+(data.usage?.output_tokens||0)},finishReason:data.stop_reason};recordUsage({provider:"anthropic",model:data.model,promptTokens:result.usage.promptTokens,completionTokens:result.usage.completionTokens,totalTokens:result.usage.totalTokens,durationMs:Date.now()-startedAt,timestamp:Date.now()});return result}function mapAnthropicToolChoice(choice){if(choice==="auto")return{type:"auto"};if(choice==="required")return{type:"any"};if(choice==="none")return{type:"none"};return{type:"tool",name:choice.name}}function buildAnthropicJsonTool(format){if(format.type==="json_schema")return{name:format.json_schema.name,description:`Returns the result as JSON matching the '${format.json_schema.name}' schema.`,input_schema:format.json_schema.schema};return{name:"structured_output",description:"Returns the result as a JSON object.",input_schema:{type:"object",additionalProperties:!0}}}export async function*streamChatEvents(messages,options={}){const startedAt=Date.now(),{body,outputTool}=messagesRequest(messages,options),response=await postMessages({...body,stream:!0});if(!response.ok){const error=await response.text();throw Error(`Claude API error: ${error}`)}if(!response.body)throw Error("No response body");const toolBlocks=new Map,toolCalls=[];let content="",model=String(body.model),promptTokens=0,completionTokens=0,finishReason;for await(const event of sseJson(response.body,"Claude API"))switch(event.type){case"message_start":model=event.message.model||model;promptTokens=event.message.usage?.input_tokens??0;completionTokens=event.message.usage?.output_tokens??0;break;case"content_block_start":if(event.content_block.type==="tool_use"){const name=event.content_block.name??"";toolBlocks.set(event.index,{id:event.content_block.id??"",name,json:"",structured:name===outputTool})}break;case"content_block_delta":{const delta=event.delta;if("text"in delta&&delta.type==="text_delta"){content+=delta.text;yield{type:"text",text:delta.text}}else if("partial_json"in delta&&delta.type==="input_json_delta"){const block=toolBlocks.get(event.index);if(!block)break;block.json+=delta.partial_json;if(block.structured&&delta.partial_json){content+=delta.partial_json;yield{type:"text",text:delta.partial_json}}}break}case"content_block_stop":{const block=toolBlocks.get(event.index);if(block&&!block.structured){const call={id:block.id,name:block.name,arguments:parseToolArguments(block.json,block.name)};toolCalls.push(call);yield{type:"tool_call",call}}break}case"message_delta":finishReason=event.delta.stop_reason??finishReason;completionTokens=event.usage?.output_tokens??completionTokens;break;case"error":throw Error(`Claude API error: ${event.error.type}: ${event.error.message}`)}const result={content,...toolCalls.length>0?{toolCalls}:{},model,usage:{promptTokens,completionTokens,totalTokens:promptTokens+completionTokens},...finishReason?{finishReason}:{}};recordUsage({provider:"anthropic",model,promptTokens,completionTokens,totalTokens:promptTokens+completionTokens,durationMs:Date.now()-startedAt,timestamp:Date.now()});yield{type:"done",result}}export function streamChat(messages,options={}){return streamText(streamChatEvents(messages,options))}export async function prompt(text,options={}){return(await chat([{role:"user",content:text}],options)).content}export function estimateTokens(text){return Math.ceil(text.length/4)}export const anthropicDriver={create:createAnthropicDriver},anthropic={configure,chat,streamChat,streamChatEvents,prompt,estimateTokens,createDriver:createAnthropicDriver};export default anthropic;
|
package/dist/drivers/index.d.ts
CHANGED
|
@@ -8,7 +8,7 @@ export type { ClaudeAgentSDKConfig } from './claude-agent-sdk/index';
|
|
|
8
8
|
*
|
|
9
9
|
* Export all available AI drivers.
|
|
10
10
|
*/
|
|
11
|
-
export { createAnthropicDriver, anthropicDriver, anthropic, estimateTokens } from './anthropic/index';
|
|
11
|
+
export { createAnthropicDriver, anthropicDriver, anthropic, anthropicApiVersion, estimateTokens } from './anthropic/index';
|
|
12
12
|
export { createOpenAIDriver, openaiDriver, openai } from './openai/index';
|
|
13
13
|
export { createOllamaDriver, ollamaDriver, ollama } from './ollama/index';
|
|
14
14
|
export { bedrock, DEFAULT_BEDROCK_MODEL, toConverseMessages } from './bedrock/index';
|
package/dist/drivers/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export{createAnthropicDriver,anthropicDriver,anthropic,estimateTokens}from"./anthropic";export{createOpenAIDriver,openaiDriver,openai}from"./openai";export{createOllamaDriver,ollamaDriver,ollama}from"./ollama";export{bedrock,DEFAULT_BEDROCK_MODEL,toConverseMessages}from"./bedrock";export{createClaudeAgentSDKDriver,claudeAgentSDK,getLastSessionId,clearSession}from"./claude-agent-sdk";
|
|
1
|
+
export{createAnthropicDriver,anthropicDriver,anthropic,anthropicApiVersion,estimateTokens}from"./anthropic";export{createOpenAIDriver,openaiDriver,openai}from"./openai";export{createOllamaDriver,ollamaDriver,ollama}from"./ollama";export{bedrock,DEFAULT_BEDROCK_MODEL,toConverseMessages}from"./bedrock";export{createClaudeAgentSDKDriver,claudeAgentSDK,getLastSessionId,clearSession}from"./claude-agent-sdk";
|
package/dist/image.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{DEFAULT_ANTHROPIC_MODEL}from"./models";export async function generateImage(prompt,options={}){const{provider="openai",model="dall-e-3",size="1024x1024",quality="standard",n=1,responseFormat="url",style="vivid"}=options;if(provider==="openai")return generateImageOpenAI(prompt,{model,size,quality,n,responseFormat,style});throw Error(`Image generation not supported for provider: ${provider}. Use 'openai'.`)}async function generateImageOpenAI(prompt,options){const apiKey=process.env.OPENAI_API_KEY;if(!apiKey)throw Error("OPENAI_API_KEY environment variable is required for image generation.");const response=await fetch("https://api.openai.com/v1/images/generations",{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${apiKey}`},body:JSON.stringify({model:options.model,prompt,size:options.size,quality:options.quality,n:options.n,response_format:options.responseFormat,style:options.style})});if(!response.ok){const error=await response.text();throw Error(`OpenAI Image API error: ${error}`)}return{images:(await response.json()).data.map((img)=>({url:img.url,b64_json:img.b64_json,revisedPrompt:img.revised_prompt})),provider:"openai",model:options.model}}export async function editImage(image,prompt,options={}){const apiKey=process.env.OPENAI_API_KEY;if(!apiKey)throw Error("OPENAI_API_KEY environment variable is required for image editing.");const formData=new FormData;formData.append("image",image);formData.append("prompt",prompt);formData.append("model",options.model||"dall-e-2");if(options.mask)formData.append("mask",options.mask);if(options.n)formData.append("n",String(options.n));if(options.size)formData.append("size",options.size);if(options.responseFormat)formData.append("response_format",options.responseFormat);const response=await fetch("https://api.openai.com/v1/images/edits",{method:"POST",headers:{Authorization:`Bearer ${apiKey}`},body:formData});if(!response.ok){const error=await response.text();throw Error(`OpenAI Image Edit API error: ${error}`)}return{images:(await response.json()).data.map((img)=>({url:img.url,b64_json:img.b64_json})),provider:"openai",model:options.model||"dall-e-2"}}export async function createImageVariation(image,options={}){const apiKey=process.env.OPENAI_API_KEY;if(!apiKey)throw Error("OPENAI_API_KEY environment variable is required for image variations.");const formData=new FormData;formData.append("image",image);formData.append("model",options.model||"dall-e-2");if(options.n)formData.append("n",String(options.n));if(options.size)formData.append("size",options.size);if(options.responseFormat)formData.append("response_format",options.responseFormat);const response=await fetch("https://api.openai.com/v1/images/variations",{method:"POST",headers:{Authorization:`Bearer ${apiKey}`},body:formData});if(!response.ok){const error=await response.text();throw Error(`OpenAI Image Variations API error: ${error}`)}return{images:(await response.json()).data.map((img)=>({url:img.url,b64_json:img.b64_json})),provider:"openai",model:options.model||"dall-e-2"}}export async function analyzeImage(imageInput,prompt,options={}){const{provider="anthropic"}=options;switch(provider){case"anthropic":return analyzeImageAnthropic(imageInput,prompt,options);case"openai":return analyzeImageOpenAI(imageInput,prompt,options);case"ollama":return analyzeImageOllama(imageInput,prompt,options);default:throw Error(`Vision not supported for provider: ${provider}`)}}async function resolveImageToBase64(input){if(input.type==="base64")return{data:input.data,mediaType:input.mediaType};if(input.type==="url"){const response=await fetch(input.url);if(!response.ok)throw Error(`Failed to fetch image from URL: ${input.url}`);const buffer=await response.arrayBuffer(),base64=Buffer.from(buffer).toString("base64"),contentType=response.headers.get("content-type")||"image/png";return{data:base64,mediaType:contentType}}if(input.type==="file"){const{readFile}=await import("node:fs/promises"),base64=(await readFile(input.path)).toString("base64"),ext=input.path.split(".").pop()?.toLowerCase()||"png";return{data:base64,mediaType:{png:"image/png",jpg:"image/jpeg",jpeg:"image/jpeg",gif:"image/gif",webp:"image/webp"}[ext]||"image/png"}}throw Error("Invalid image input type")}async function analyzeImageAnthropic(imageInput,prompt,options){const apiKey=process.env.ANTHROPIC_API_KEY;if(!apiKey)throw Error("ANTHROPIC_API_KEY environment variable is required for Claude vision.");const{data,mediaType}=await resolveImageToBase64(imageInput),model=options.model||DEFAULT_ANTHROPIC_MODEL,maxTokens=options.maxTokens||4096,messages=[{role:"user",content:[{type:"image",source:{type:"base64",media_type:mediaType,data}},{type:"text",text:prompt}]}],response=await fetch("https://api.anthropic.com/v1/messages",{method:"POST",headers:{"Content-Type":"application/json","x-api-key":apiKey,"anthropic-version":"2023-06-01"},body:JSON.stringify({model,max_tokens:maxTokens,temperature:options.temperature,messages})});if(!response.ok){const error=await response.text();throw Error(`Claude Vision API error: ${error}`)}const responseData=await response.json();return{content:responseData.content[0].text,model:responseData.model,provider:"anthropic",usage:{promptTokens:responseData.usage?.input_tokens||0,completionTokens:responseData.usage?.output_tokens||0,totalTokens:(responseData.usage?.input_tokens||0)+(responseData.usage?.output_tokens||0)},finishReason:responseData.stop_reason}}async function analyzeImageOpenAI(imageInput,prompt,options){const apiKey=process.env.OPENAI_API_KEY;if(!apiKey)throw Error("OPENAI_API_KEY environment variable is required for GPT-4 vision.");const model=options.model||"gpt-4o",maxTokens=options.maxTokens||4096,detail=options.detail||"auto";let imageContent;if(imageInput.type==="url")imageContent={type:"image_url",image_url:{url:imageInput.url,detail}};else{const{data,mediaType}=await resolveImageToBase64(imageInput);imageContent={type:"image_url",image_url:{url:`data:${mediaType};base64,${data}`,detail}}}const response=await fetch("https://api.openai.com/v1/chat/completions",{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${apiKey}`},body:JSON.stringify({model,max_tokens:maxTokens,temperature:options.temperature,messages:[{role:"user",content:[imageContent,{type:"text",text:prompt}]}]})});if(!response.ok){const error=await response.text();throw Error(`OpenAI Vision API error: ${error}`)}const responseData=await response.json();return{content:responseData.choices[0].message.content,model:responseData.model,provider:"openai",usage:{promptTokens:responseData.usage?.prompt_tokens||0,completionTokens:responseData.usage?.completion_tokens||0,totalTokens:responseData.usage?.total_tokens||0},finishReason:responseData.choices[0].finish_reason}}async function analyzeImageOllama(imageInput,prompt,options){const host=process.env.OLLAMA_HOST||"http://localhost:11434",model=options.model||"llava",{data}=await resolveImageToBase64(imageInput),response=await fetch(`${host}/api/generate`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({model,prompt,images:[data],stream:!1})});if(!response.ok){const error=await response.text();throw Error(`Ollama Vision API error: ${error}`)}const responseData=await response.json();return{content:responseData.response,model:responseData.model,provider:"ollama",usage:{promptTokens:responseData.prompt_eval_count||0,completionTokens:responseData.eval_count||0,totalTokens:(responseData.prompt_eval_count||0)+(responseData.eval_count||0)}}}export async function analyzeImages(images,prompt,options={}){const{provider="anthropic"}=options;if(provider==="anthropic"){const apiKey=process.env.ANTHROPIC_API_KEY;if(!apiKey)throw Error("ANTHROPIC_API_KEY required for multi-image analysis.");const model=options.model||DEFAULT_ANTHROPIC_MODEL,contentBlocks=[];for(const img of images){const{data,mediaType}=await resolveImageToBase64(img);contentBlocks.push({type:"image",source:{type:"base64",media_type:mediaType,data}})}contentBlocks.push({type:"text",text:prompt});const response=await fetch("https://api.anthropic.com/v1/messages",{method:"POST",headers:{"Content-Type":"application/json","x-api-key":apiKey,"anthropic-version":"2023-06-01"},body:JSON.stringify({model,max_tokens:options.maxTokens||4096,temperature:options.temperature,messages:[{role:"user",content:contentBlocks}]})});if(!response.ok){const error=await response.text();throw Error(`Claude Vision API error: ${error}`)}const responseData=await response.json();return{content:responseData.content[0].text,model:responseData.model,provider:"anthropic",usage:{promptTokens:responseData.usage?.input_tokens||0,completionTokens:responseData.usage?.output_tokens||0,totalTokens:(responseData.usage?.input_tokens||0)+(responseData.usage?.output_tokens||0)},finishReason:responseData.stop_reason}}if(provider==="openai"){const apiKey=process.env.OPENAI_API_KEY;if(!apiKey)throw Error("OPENAI_API_KEY required for multi-image analysis.");const model=options.model||"gpt-4o",detail=options.detail||"auto",contentBlocks=[];for(const img of images)if(img.type==="url")contentBlocks.push({type:"image_url",image_url:{url:img.url,detail}});else{const{data,mediaType}=await resolveImageToBase64(img);contentBlocks.push({type:"image_url",image_url:{url:`data:${mediaType};base64,${data}`,detail}})}contentBlocks.push({type:"text",text:prompt});const response=await fetch("https://api.openai.com/v1/chat/completions",{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${apiKey}`},body:JSON.stringify({model,max_tokens:options.maxTokens||4096,temperature:options.temperature,messages:[{role:"user",content:contentBlocks}]})});if(!response.ok){const error=await response.text();throw Error(`OpenAI Vision API error: ${error}`)}const responseData=await response.json();return{content:responseData.choices[0].message.content,model:responseData.model,provider:"openai",usage:{promptTokens:responseData.usage?.prompt_tokens||0,completionTokens:responseData.usage?.completion_tokens||0,totalTokens:responseData.usage?.total_tokens||0},finishReason:responseData.choices[0].finish_reason}}throw Error(`Multi-image analysis not supported for provider: ${provider}`)}export const image={generate:generateImage,edit:editImage,variation:createImageVariation,analyze:analyzeImage,analyzeMultiple:analyzeImages};
|
|
1
|
+
import{anthropicApiVersion}from"./drivers/anthropic";import{DEFAULT_ANTHROPIC_MODEL}from"./models";export async function generateImage(prompt,options={}){const{provider="openai",model="dall-e-3",size="1024x1024",quality="standard",n=1,responseFormat="url",style="vivid"}=options;if(provider==="openai")return generateImageOpenAI(prompt,{model,size,quality,n,responseFormat,style});throw Error(`Image generation not supported for provider: ${provider}. Use 'openai'.`)}async function generateImageOpenAI(prompt,options){const apiKey=process.env.OPENAI_API_KEY;if(!apiKey)throw Error("OPENAI_API_KEY environment variable is required for image generation.");const response=await fetch("https://api.openai.com/v1/images/generations",{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${apiKey}`},body:JSON.stringify({model:options.model,prompt,size:options.size,quality:options.quality,n:options.n,response_format:options.responseFormat,style:options.style})});if(!response.ok){const error=await response.text();throw Error(`OpenAI Image API error: ${error}`)}return{images:(await response.json()).data.map((img)=>({url:img.url,b64_json:img.b64_json,revisedPrompt:img.revised_prompt})),provider:"openai",model:options.model}}export async function editImage(image,prompt,options={}){const apiKey=process.env.OPENAI_API_KEY;if(!apiKey)throw Error("OPENAI_API_KEY environment variable is required for image editing.");const formData=new FormData;formData.append("image",image);formData.append("prompt",prompt);formData.append("model",options.model||"dall-e-2");if(options.mask)formData.append("mask",options.mask);if(options.n)formData.append("n",String(options.n));if(options.size)formData.append("size",options.size);if(options.responseFormat)formData.append("response_format",options.responseFormat);const response=await fetch("https://api.openai.com/v1/images/edits",{method:"POST",headers:{Authorization:`Bearer ${apiKey}`},body:formData});if(!response.ok){const error=await response.text();throw Error(`OpenAI Image Edit API error: ${error}`)}return{images:(await response.json()).data.map((img)=>({url:img.url,b64_json:img.b64_json})),provider:"openai",model:options.model||"dall-e-2"}}export async function createImageVariation(image,options={}){const apiKey=process.env.OPENAI_API_KEY;if(!apiKey)throw Error("OPENAI_API_KEY environment variable is required for image variations.");const formData=new FormData;formData.append("image",image);formData.append("model",options.model||"dall-e-2");if(options.n)formData.append("n",String(options.n));if(options.size)formData.append("size",options.size);if(options.responseFormat)formData.append("response_format",options.responseFormat);const response=await fetch("https://api.openai.com/v1/images/variations",{method:"POST",headers:{Authorization:`Bearer ${apiKey}`},body:formData});if(!response.ok){const error=await response.text();throw Error(`OpenAI Image Variations API error: ${error}`)}return{images:(await response.json()).data.map((img)=>({url:img.url,b64_json:img.b64_json})),provider:"openai",model:options.model||"dall-e-2"}}export async function analyzeImage(imageInput,prompt,options={}){const{provider="anthropic"}=options;switch(provider){case"anthropic":return analyzeImageAnthropic(imageInput,prompt,options);case"openai":return analyzeImageOpenAI(imageInput,prompt,options);case"ollama":return analyzeImageOllama(imageInput,prompt,options);default:throw Error(`Vision not supported for provider: ${provider}`)}}async function resolveImageToBase64(input){if(input.type==="base64")return{data:input.data,mediaType:input.mediaType};if(input.type==="url"){const response=await fetch(input.url);if(!response.ok)throw Error(`Failed to fetch image from URL: ${input.url}`);const buffer=await response.arrayBuffer(),base64=Buffer.from(buffer).toString("base64"),contentType=response.headers.get("content-type")||"image/png";return{data:base64,mediaType:contentType}}if(input.type==="file"){const{readFile}=await import("node:fs/promises"),base64=(await readFile(input.path)).toString("base64"),ext=input.path.split(".").pop()?.toLowerCase()||"png";return{data:base64,mediaType:{png:"image/png",jpg:"image/jpeg",jpeg:"image/jpeg",gif:"image/gif",webp:"image/webp"}[ext]||"image/png"}}throw Error("Invalid image input type")}async function analyzeImageAnthropic(imageInput,prompt,options){const apiKey=process.env.ANTHROPIC_API_KEY;if(!apiKey)throw Error("ANTHROPIC_API_KEY environment variable is required for Claude vision.");const{data,mediaType}=await resolveImageToBase64(imageInput),model=options.model||DEFAULT_ANTHROPIC_MODEL,maxTokens=options.maxTokens||4096,messages=[{role:"user",content:[{type:"image",source:{type:"base64",media_type:mediaType,data}},{type:"text",text:prompt}]}],response=await fetch("https://api.anthropic.com/v1/messages",{method:"POST",headers:{"Content-Type":"application/json","x-api-key":apiKey,"anthropic-version":anthropicApiVersion()},body:JSON.stringify({model,max_tokens:maxTokens,temperature:options.temperature,messages})});if(!response.ok){const error=await response.text();throw Error(`Claude Vision API error: ${error}`)}const responseData=await response.json();return{content:responseData.content[0].text,model:responseData.model,provider:"anthropic",usage:{promptTokens:responseData.usage?.input_tokens||0,completionTokens:responseData.usage?.output_tokens||0,totalTokens:(responseData.usage?.input_tokens||0)+(responseData.usage?.output_tokens||0)},finishReason:responseData.stop_reason}}async function analyzeImageOpenAI(imageInput,prompt,options){const apiKey=process.env.OPENAI_API_KEY;if(!apiKey)throw Error("OPENAI_API_KEY environment variable is required for GPT-4 vision.");const model=options.model||"gpt-4o",maxTokens=options.maxTokens||4096,detail=options.detail||"auto";let imageContent;if(imageInput.type==="url")imageContent={type:"image_url",image_url:{url:imageInput.url,detail}};else{const{data,mediaType}=await resolveImageToBase64(imageInput);imageContent={type:"image_url",image_url:{url:`data:${mediaType};base64,${data}`,detail}}}const response=await fetch("https://api.openai.com/v1/chat/completions",{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${apiKey}`},body:JSON.stringify({model,max_tokens:maxTokens,temperature:options.temperature,messages:[{role:"user",content:[imageContent,{type:"text",text:prompt}]}]})});if(!response.ok){const error=await response.text();throw Error(`OpenAI Vision API error: ${error}`)}const responseData=await response.json();return{content:responseData.choices[0].message.content,model:responseData.model,provider:"openai",usage:{promptTokens:responseData.usage?.prompt_tokens||0,completionTokens:responseData.usage?.completion_tokens||0,totalTokens:responseData.usage?.total_tokens||0},finishReason:responseData.choices[0].finish_reason}}async function analyzeImageOllama(imageInput,prompt,options){const host=process.env.OLLAMA_HOST||"http://localhost:11434",model=options.model||"llava",{data}=await resolveImageToBase64(imageInput),response=await fetch(`${host}/api/generate`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({model,prompt,images:[data],stream:!1})});if(!response.ok){const error=await response.text();throw Error(`Ollama Vision API error: ${error}`)}const responseData=await response.json();return{content:responseData.response,model:responseData.model,provider:"ollama",usage:{promptTokens:responseData.prompt_eval_count||0,completionTokens:responseData.eval_count||0,totalTokens:(responseData.prompt_eval_count||0)+(responseData.eval_count||0)}}}export async function analyzeImages(images,prompt,options={}){const{provider="anthropic"}=options;if(provider==="anthropic"){const apiKey=process.env.ANTHROPIC_API_KEY;if(!apiKey)throw Error("ANTHROPIC_API_KEY required for multi-image analysis.");const model=options.model||DEFAULT_ANTHROPIC_MODEL,contentBlocks=[];for(const img of images){const{data,mediaType}=await resolveImageToBase64(img);contentBlocks.push({type:"image",source:{type:"base64",media_type:mediaType,data}})}contentBlocks.push({type:"text",text:prompt});const response=await fetch("https://api.anthropic.com/v1/messages",{method:"POST",headers:{"Content-Type":"application/json","x-api-key":apiKey,"anthropic-version":anthropicApiVersion()},body:JSON.stringify({model,max_tokens:options.maxTokens||4096,temperature:options.temperature,messages:[{role:"user",content:contentBlocks}]})});if(!response.ok){const error=await response.text();throw Error(`Claude Vision API error: ${error}`)}const responseData=await response.json();return{content:responseData.content[0].text,model:responseData.model,provider:"anthropic",usage:{promptTokens:responseData.usage?.input_tokens||0,completionTokens:responseData.usage?.output_tokens||0,totalTokens:(responseData.usage?.input_tokens||0)+(responseData.usage?.output_tokens||0)},finishReason:responseData.stop_reason}}if(provider==="openai"){const apiKey=process.env.OPENAI_API_KEY;if(!apiKey)throw Error("OPENAI_API_KEY required for multi-image analysis.");const model=options.model||"gpt-4o",detail=options.detail||"auto",contentBlocks=[];for(const img of images)if(img.type==="url")contentBlocks.push({type:"image_url",image_url:{url:img.url,detail}});else{const{data,mediaType}=await resolveImageToBase64(img);contentBlocks.push({type:"image_url",image_url:{url:`data:${mediaType};base64,${data}`,detail}})}contentBlocks.push({type:"text",text:prompt});const response=await fetch("https://api.openai.com/v1/chat/completions",{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${apiKey}`},body:JSON.stringify({model,max_tokens:options.maxTokens||4096,temperature:options.temperature,messages:[{role:"user",content:contentBlocks}]})});if(!response.ok){const error=await response.text();throw Error(`OpenAI Vision API error: ${error}`)}const responseData=await response.json();return{content:responseData.choices[0].message.content,model:responseData.model,provider:"openai",usage:{promptTokens:responseData.usage?.prompt_tokens||0,completionTokens:responseData.usage?.completion_tokens||0,totalTokens:responseData.usage?.total_tokens||0},finishReason:responseData.choices[0].finish_reason}}throw Error(`Multi-image analysis not supported for provider: ${provider}`)}export const image={generate:generateImage,edit:editImage,variation:createImageVariation,analyze:analyzeImage,analyzeMultiple:analyzeImages};
|
package/dist/personalization.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{DEFAULT_ANTHROPIC_MODEL}from"./models";export async function analyzeSentiment(text,options={}){const{provider="anthropic",aspects}=options,aspectInstruction=aspects?`
|
|
1
|
+
import{anthropicApiVersion}from"./drivers/anthropic";import{DEFAULT_ANTHROPIC_MODEL}from"./models";export async function analyzeSentiment(text,options={}){const{provider="anthropic",aspects}=options,aspectInstruction=aspects?`
|
|
2
2
|
Also analyze sentiment for these specific aspects: ${aspects.join(", ")}`:"",systemPrompt=`You are a sentiment analysis expert. Analyze the sentiment of the given text and respond with ONLY valid JSON in this exact format:
|
|
3
3
|
{
|
|
4
4
|
"sentiment": "positive" | "negative" | "neutral" | "mixed",
|
|
@@ -32,4 +32,4 @@ ${itemsList}`,result=await callProvider(provider,systemPrompt,prompt,options.mod
|
|
|
32
32
|
Top Preferences: ${topPreferences||"none"}
|
|
33
33
|
Recent Interactions: ${recentInteractions||"none"}`}export function createProfile(id,segments=[]){return{id,preferences:{},interactions:[],segments}}export function recordInteraction(profile,interaction){profile.interactions.push(interaction);const weight={view:0.1,click:0.3,like:0.5,share:0.6,bookmark:0.7,purchase:1,dislike:-0.5,custom:interaction.weight||0.3}[interaction.type]||0.1,key=interaction.itemId;profile.preferences[key]=(profile.preferences[key]||0)+weight;return profile}export async function extractUserInterests(profile,items,options={}){const{provider="anthropic"}=options,interactedItemIds=new Set(profile.interactions.map((i)=>i.itemId)),interactedItems=items.filter((item)=>interactedItemIds.has(item.id));if(interactedItems.length===0)return profile.segments;const contentSample=interactedItems.slice(0,20).map((item)=>item.content.slice(0,200)).join(`
|
|
34
34
|
---
|
|
35
|
-
`),result=await callProvider(provider,`Extract the main interests/topics from the user's interaction history. Return ONLY a JSON array of strings, e.g. ["technology", "cooking", "travel"]. Maximum 10 interests.`,contentSample,options.model);try{return JSON.parse(result.content)}catch{return profile.segments}}async function callProvider(provider,systemPrompt,userMessage,model){if(provider==="anthropic"){const apiKey=process.env.ANTHROPIC_API_KEY;if(!apiKey)throw Error("ANTHROPIC_API_KEY required.");const response=await fetch("https://api.anthropic.com/v1/messages",{method:"POST",headers:{"Content-Type":"application/json","x-api-key":apiKey,"anthropic-version":
|
|
35
|
+
`),result=await callProvider(provider,`Extract the main interests/topics from the user's interaction history. Return ONLY a JSON array of strings, e.g. ["technology", "cooking", "travel"]. Maximum 10 interests.`,contentSample,options.model);try{return JSON.parse(result.content)}catch{return profile.segments}}async function callProvider(provider,systemPrompt,userMessage,model){if(provider==="anthropic"){const apiKey=process.env.ANTHROPIC_API_KEY;if(!apiKey)throw Error("ANTHROPIC_API_KEY required.");const response=await fetch("https://api.anthropic.com/v1/messages",{method:"POST",headers:{"Content-Type":"application/json","x-api-key":apiKey,"anthropic-version":anthropicApiVersion()},body:JSON.stringify({model:model||DEFAULT_ANTHROPIC_MODEL,max_tokens:4096,system:systemPrompt,messages:[{role:"user",content:userMessage}]})});if(!response.ok){const error=await response.text();throw Error(`Claude API error: ${error}`)}const data=await response.json();return{content:data.content[0].text,model:data.model,usage:{promptTokens:data.usage?.input_tokens||0,completionTokens:data.usage?.output_tokens||0,totalTokens:(data.usage?.input_tokens||0)+(data.usage?.output_tokens||0)},finishReason:data.stop_reason}}if(provider==="openai"){const apiKey=process.env.OPENAI_API_KEY;if(!apiKey)throw Error("OPENAI_API_KEY required.");const response=await fetch("https://api.openai.com/v1/chat/completions",{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${apiKey}`},body:JSON.stringify({model:model||"gpt-4o",max_tokens:4096,messages:[{role:"system",content:systemPrompt},{role:"user",content:userMessage}]})});if(!response.ok){const error=await response.text();throw Error(`OpenAI API error: ${error}`)}const data=await response.json();return{content:data.choices[0].message.content,model:data.model,usage:{promptTokens:data.usage?.prompt_tokens||0,completionTokens:data.usage?.completion_tokens||0,totalTokens:data.usage?.total_tokens||0},finishReason:data.choices[0].finish_reason}}if(provider==="ollama"){const host=process.env.OLLAMA_HOST||"http://localhost:11434",response=await fetch(`${host}/api/chat`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({model:model||"llama3.2",messages:[{role:"system",content:systemPrompt},{role:"user",content:userMessage}],stream:!1})});if(!response.ok){const error=await response.text();throw Error(`Ollama API error: ${error}`)}const data=await response.json();return{content:data.message.content,model:data.model,usage:{promptTokens:data.prompt_eval_count||0,completionTokens:data.eval_count||0,totalTokens:(data.prompt_eval_count||0)+(data.eval_count||0)},finishReason:data.done_reason||"stop"}}throw Error(`Provider not supported: ${provider}`)}export const personalization={analyzeSentiment,classifyText,summarize,recommend,createProfile,recordInteraction,extractUserInterests};
|
package/dist/search.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{DEFAULT_ANTHROPIC_MODEL}from"./models";export async function createEmbedding(input,options={}){const{provider="openai"}=options;if(provider==="openai")return createEmbeddingOpenAI(input,options.model||"text-embedding-3-small");if(provider==="ollama")return createEmbeddingOllama(input,options.model||"nomic-embed-text");throw Error(`Embedding provider not supported: ${provider}`)}async function createEmbeddingOpenAI(input,model){const apiKey=process.env.OPENAI_API_KEY;if(!apiKey)throw Error("OPENAI_API_KEY environment variable is required for embeddings.");const response=await fetch("https://api.openai.com/v1/embeddings",{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${apiKey}`},body:JSON.stringify({model,input})});if(!response.ok){const error=await response.text();throw Error(`OpenAI Embeddings API error: ${error}`)}const data=await response.json();if(Array.isArray(input))return data.data.map((d)=>d.embedding);const first=data.data[0];if(!first)throw Error("OpenAI Embeddings API returned no embedding");return first.embedding}async function createEmbeddingOllama(input,model){const host=process.env.OLLAMA_HOST||"http://localhost:11434",inputs=Array.isArray(input)?input:[input],embeddings=[];for(const text of inputs){const response=await fetch(`${host}/api/embeddings`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({model,prompt:text})});if(!response.ok){const error=await response.text();throw Error(`Ollama Embeddings API error: ${error}`)}const data=await response.json();embeddings.push(data.embedding)}if(Array.isArray(input))return embeddings;const first=embeddings[0];if(!first)throw Error("Ollama Embeddings API returned no embedding");return first}export function cosineSimilarity(a,b){if(a.length!==b.length)throw Error(`Vector dimensions must match: ${a.length} vs ${b.length}`);let dotProduct=0,normA=0,normB=0;for(let i=0;i<a.length;i++){const av=a[i],bv=b[i];dotProduct+=av*bv;normA+=av*av;normB+=bv*bv}const denominator=Math.sqrt(normA)*Math.sqrt(normB);if(denominator===0)return 0;return dotProduct/denominator}export function dotProduct(a,b){if(a.length!==b.length)throw Error(`Vector dimensions must match: ${a.length} vs ${b.length}`);let result=0;for(let i=0;i<a.length;i++)result+=a[i]*b[i];return result}export function euclideanDistance(a,b){if(a.length!==b.length)throw Error(`Vector dimensions must match: ${a.length} vs ${b.length}`);let sum=0;for(let i=0;i<a.length;i++){const diff=a[i]-b[i];sum+=diff*diff}return Math.sqrt(sum)}export class VectorIndex{documents=[];embeddingOptions;constructor(options={}){this.embeddingOptions=options}async add(documents){if(documents.length===0)return;const contents=documents.map((d)=>d.content),embeddings=await createEmbedding(contents,this.embeddingOptions);for(let i=0;i<documents.length;i++){const document=documents[i],embedding=embeddings[i];if(!document||!embedding)continue;this.documents.push({...document,embedding})}}addWithEmbedding(document,embedding){this.documents.push({...document,embedding})}async search(query,topK=5){if(this.documents.length===0)return[];const queryEmbedding=await createEmbedding(query,this.embeddingOptions);return this.searchByVector(queryEmbedding,topK)}searchByVector(queryEmbedding,topK=5){if(this.documents.length===0)return[];const scored=this.documents.map((doc)=>({document:{id:doc.id,content:doc.content,metadata:doc.metadata},score:cosineSimilarity(queryEmbedding,doc.embedding)}));scored.sort((a,b)=>b.score-a.score);return scored.slice(0,topK).map((result,index)=>({...result,rank:index+1}))}remove(id){const initialLength=this.documents.length;this.documents=this.documents.filter((d)=>d.id!==id);return this.documents.length<initialLength}clear(){this.documents=[]}get size(){return this.documents.length}get ids(){return this.documents.map((d)=>d.id)}}export async function rag(query,index,options={}){const{provider="anthropic",topK=5,maxTokens=4096,temperature,systemPrompt}=options,searchResults=await index.search(query,topK),contextText=searchResults.map((r,i)=>`[Source ${i+1}] (score: ${r.score.toFixed(3)})
|
|
1
|
+
import{anthropicApiVersion}from"./drivers/anthropic";import{DEFAULT_ANTHROPIC_MODEL}from"./models";export async function createEmbedding(input,options={}){const{provider="openai"}=options;if(provider==="openai")return createEmbeddingOpenAI(input,options.model||"text-embedding-3-small");if(provider==="ollama")return createEmbeddingOllama(input,options.model||"nomic-embed-text");throw Error(`Embedding provider not supported: ${provider}`)}async function createEmbeddingOpenAI(input,model){const apiKey=process.env.OPENAI_API_KEY;if(!apiKey)throw Error("OPENAI_API_KEY environment variable is required for embeddings.");const response=await fetch("https://api.openai.com/v1/embeddings",{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${apiKey}`},body:JSON.stringify({model,input})});if(!response.ok){const error=await response.text();throw Error(`OpenAI Embeddings API error: ${error}`)}const data=await response.json();if(Array.isArray(input))return data.data.map((d)=>d.embedding);const first=data.data[0];if(!first)throw Error("OpenAI Embeddings API returned no embedding");return first.embedding}async function createEmbeddingOllama(input,model){const host=process.env.OLLAMA_HOST||"http://localhost:11434",inputs=Array.isArray(input)?input:[input],embeddings=[];for(const text of inputs){const response=await fetch(`${host}/api/embeddings`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({model,prompt:text})});if(!response.ok){const error=await response.text();throw Error(`Ollama Embeddings API error: ${error}`)}const data=await response.json();embeddings.push(data.embedding)}if(Array.isArray(input))return embeddings;const first=embeddings[0];if(!first)throw Error("Ollama Embeddings API returned no embedding");return first}export function cosineSimilarity(a,b){if(a.length!==b.length)throw Error(`Vector dimensions must match: ${a.length} vs ${b.length}`);let dotProduct=0,normA=0,normB=0;for(let i=0;i<a.length;i++){const av=a[i],bv=b[i];dotProduct+=av*bv;normA+=av*av;normB+=bv*bv}const denominator=Math.sqrt(normA)*Math.sqrt(normB);if(denominator===0)return 0;return dotProduct/denominator}export function dotProduct(a,b){if(a.length!==b.length)throw Error(`Vector dimensions must match: ${a.length} vs ${b.length}`);let result=0;for(let i=0;i<a.length;i++)result+=a[i]*b[i];return result}export function euclideanDistance(a,b){if(a.length!==b.length)throw Error(`Vector dimensions must match: ${a.length} vs ${b.length}`);let sum=0;for(let i=0;i<a.length;i++){const diff=a[i]-b[i];sum+=diff*diff}return Math.sqrt(sum)}export class VectorIndex{documents=[];embeddingOptions;constructor(options={}){this.embeddingOptions=options}async add(documents){if(documents.length===0)return;const contents=documents.map((d)=>d.content),embeddings=await createEmbedding(contents,this.embeddingOptions);for(let i=0;i<documents.length;i++){const document=documents[i],embedding=embeddings[i];if(!document||!embedding)continue;this.documents.push({...document,embedding})}}addWithEmbedding(document,embedding){this.documents.push({...document,embedding})}async search(query,topK=5){if(this.documents.length===0)return[];const queryEmbedding=await createEmbedding(query,this.embeddingOptions);return this.searchByVector(queryEmbedding,topK)}searchByVector(queryEmbedding,topK=5){if(this.documents.length===0)return[];const scored=this.documents.map((doc)=>({document:{id:doc.id,content:doc.content,metadata:doc.metadata},score:cosineSimilarity(queryEmbedding,doc.embedding)}));scored.sort((a,b)=>b.score-a.score);return scored.slice(0,topK).map((result,index)=>({...result,rank:index+1}))}remove(id){const initialLength=this.documents.length;this.documents=this.documents.filter((d)=>d.id!==id);return this.documents.length<initialLength}clear(){this.documents=[]}get size(){return this.documents.length}get ids(){return this.documents.map((d)=>d.id)}}export async function rag(query,index,options={}){const{provider="anthropic",topK=5,maxTokens=4096,temperature,systemPrompt}=options,searchResults=await index.search(query,topK),contextText=searchResults.map((r,i)=>`[Source ${i+1}] (score: ${r.score.toFixed(3)})
|
|
2
2
|
${r.document.content}`).join(`
|
|
3
3
|
|
|
4
4
|
---
|
|
@@ -6,5 +6,5 @@ ${r.document.content}`).join(`
|
|
|
6
6
|
`),ragSystemPrompt=systemPrompt||"You are a helpful assistant. Answer the user's question based on the provided context. If the context doesn't contain relevant information, say so. Always cite your sources by referencing [Source N].",fullPrompt=`Context:
|
|
7
7
|
${contextText}
|
|
8
8
|
|
|
9
|
-
Question: ${query}`;if(provider==="anthropic")return ragWithAnthropic(fullPrompt,ragSystemPrompt,searchResults,{maxTokens,temperature,model:options.model});if(provider==="openai")return ragWithOpenAI(fullPrompt,ragSystemPrompt,searchResults,{maxTokens,temperature,model:options.model});if(provider==="ollama")return ragWithOllama(fullPrompt,ragSystemPrompt,searchResults,{maxTokens,temperature,model:options.model});throw Error(`RAG provider not supported: ${provider}`)}async function ragWithAnthropic(prompt,systemPrompt,sources,options){const apiKey=process.env.ANTHROPIC_API_KEY;if(!apiKey)throw Error("ANTHROPIC_API_KEY required for RAG with Anthropic.");const model=options.model||DEFAULT_ANTHROPIC_MODEL,response=await fetch("https://api.anthropic.com/v1/messages",{method:"POST",headers:{"Content-Type":"application/json","x-api-key":apiKey,"anthropic-version":
|
|
9
|
+
Question: ${query}`;if(provider==="anthropic")return ragWithAnthropic(fullPrompt,ragSystemPrompt,searchResults,{maxTokens,temperature,model:options.model});if(provider==="openai")return ragWithOpenAI(fullPrompt,ragSystemPrompt,searchResults,{maxTokens,temperature,model:options.model});if(provider==="ollama")return ragWithOllama(fullPrompt,ragSystemPrompt,searchResults,{maxTokens,temperature,model:options.model});throw Error(`RAG provider not supported: ${provider}`)}async function ragWithAnthropic(prompt,systemPrompt,sources,options){const apiKey=process.env.ANTHROPIC_API_KEY;if(!apiKey)throw Error("ANTHROPIC_API_KEY required for RAG with Anthropic.");const model=options.model||DEFAULT_ANTHROPIC_MODEL,response=await fetch("https://api.anthropic.com/v1/messages",{method:"POST",headers:{"Content-Type":"application/json","x-api-key":apiKey,"anthropic-version":anthropicApiVersion()},body:JSON.stringify({model,max_tokens:options.maxTokens||4096,temperature:options.temperature,system:systemPrompt,messages:[{role:"user",content:prompt}]})});if(!response.ok){const error=await response.text();throw Error(`Claude API error: ${error}`)}const data=await response.json();return{content:data.content[0].text,model:data.model,usage:{promptTokens:data.usage?.input_tokens||0,completionTokens:data.usage?.output_tokens||0,totalTokens:(data.usage?.input_tokens||0)+(data.usage?.output_tokens||0)},finishReason:data.stop_reason,sources}}async function ragWithOpenAI(prompt,systemPrompt,sources,options){const apiKey=process.env.OPENAI_API_KEY;if(!apiKey)throw Error("OPENAI_API_KEY required for RAG with OpenAI.");const model=options.model||"gpt-4o",response=await fetch("https://api.openai.com/v1/chat/completions",{method:"POST",headers:{"Content-Type":"application/json",Authorization:`Bearer ${apiKey}`},body:JSON.stringify({model,max_tokens:options.maxTokens||4096,temperature:options.temperature,messages:[{role:"system",content:systemPrompt},{role:"user",content:prompt}]})});if(!response.ok){const error=await response.text();throw Error(`OpenAI API error: ${error}`)}const data=await response.json();return{content:data.choices[0].message.content,model:data.model,usage:{promptTokens:data.usage?.prompt_tokens||0,completionTokens:data.usage?.completion_tokens||0,totalTokens:data.usage?.total_tokens||0},finishReason:data.choices[0].finish_reason,sources}}async function ragWithOllama(prompt,systemPrompt,sources,options){const host=process.env.OLLAMA_HOST||"http://localhost:11434",model=options.model||"llama3.2",response=await fetch(`${host}/api/chat`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({model,messages:[{role:"system",content:systemPrompt},{role:"user",content:prompt}],stream:!1,options:{temperature:options.temperature}})});if(!response.ok){const error=await response.text();throw Error(`Ollama API error: ${error}`)}const data=await response.json();return{content:data.message.content,model:data.model,usage:{promptTokens:data.prompt_eval_count||0,completionTokens:data.eval_count||0,totalTokens:(data.prompt_eval_count||0)+(data.eval_count||0)},finishReason:data.done_reason||"stop",sources}}export function chunkText(text,options={}){const{chunkSize=1000,chunkOverlap=200,separator=`
|
|
10
10
|
`}=options,segments=text.split(separator),chunks=[];let currentChunk="";for(const segment of segments)if(currentChunk.length+segment.length+1>chunkSize&¤tChunk.length>0){chunks.push(currentChunk.trim());if(chunkOverlap>0){const overlapStart=Math.max(0,currentChunk.length-chunkOverlap);currentChunk=currentChunk.slice(overlapStart)+separator+segment}else currentChunk=segment}else currentChunk+=(currentChunk?separator:"")+segment;if(currentChunk.trim())chunks.push(currentChunk.trim());return chunks}export async function indexText(text,options={}){const chunks=chunkText(text,options),index=new VectorIndex(options),documents=chunks.map((chunk,i)=>({id:`${options.idPrefix||"chunk"}-${i}`,content:chunk,metadata:{chunkIndex:i,totalChunks:chunks.length}}));await index.add(documents);return index}export const search={createEmbedding,cosineSimilarity,dotProduct,euclideanDistance,VectorIndex,rag,chunkText,indexText};
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@stacksjs/ai",
|
|
3
3
|
"type": "module",
|
|
4
4
|
"sideEffects": false,
|
|
5
|
-
"version": "0.75.
|
|
5
|
+
"version": "0.75.153",
|
|
6
6
|
"description": "Stacks Artificial Intelligence.",
|
|
7
7
|
"author": "Chris Breuer",
|
|
8
8
|
"contributors": [
|
|
@@ -65,9 +65,9 @@
|
|
|
65
65
|
"prepublishOnly": "bun run build"
|
|
66
66
|
},
|
|
67
67
|
"dependencies": {
|
|
68
|
-
"@stacksjs/cli": "0.75.
|
|
69
|
-
"@stacksjs/config": "0.75.
|
|
70
|
-
"@stacksjs/storage": "0.75.
|
|
68
|
+
"@stacksjs/cli": "0.75.153",
|
|
69
|
+
"@stacksjs/config": "0.75.153",
|
|
70
|
+
"@stacksjs/storage": "0.75.153",
|
|
71
71
|
"@stacksjs/ts-cloud": "^0.16.52"
|
|
72
72
|
},
|
|
73
73
|
"peerDependencies": {
|