@scoopika/scoopika
Advanced tools
Comparing version 1.0.4 to 1.0.5
import OpenAI from 'openai'; | ||
import { GoogleGenerativeAI } from '@google/generative-ai'; | ||
import { JSONSchema } from 'json-schema-to-ts'; | ||
export { FromSchema, JSONSchema } from 'json-schema-to-ts'; | ||
declare class StateStore { | ||
state: Record<string, 0 | 1>; | ||
queue: Record<string, string[]>; | ||
constructor(); | ||
setState(id: string, state: 0 | 1): Promise<void>; | ||
getState(id: string): 0 | 1; | ||
checkState(id: string): boolean; | ||
queueRun(id: string, run_id: string, timeout?: number): Promise<void>; | ||
} | ||
interface ParameterBase { | ||
@@ -61,3 +53,3 @@ description?: string; | ||
} | ||
type ImageSize = "265x265" | "512x512" | "1024x1024" | "1792x1024" | "1792x1024" | null | undefined; | ||
type ImageSize = "265x265" | "512x512" | "1024x1024" | "1792x1024" | "1792x1024" | null | undefined | string; | ||
interface ImagePrompt { | ||
@@ -81,34 +73,20 @@ id: string; | ||
interface ToolParameters { | ||
type: "object"; | ||
properties: Record<string, Parameter>; | ||
required?: Array<string>; | ||
type Input = string | number | boolean | Array<string | number | boolean> | Object; | ||
type PlugFunc = (context: string) => string | Promise<string>; | ||
interface Plug { | ||
rag?: string | PlugFunc; | ||
images?: string[]; | ||
data?: { | ||
description: string; | ||
data: string; | ||
}[]; | ||
} | ||
interface ToolFunction { | ||
name: string; | ||
description: string; | ||
parameters: ToolParameters; | ||
} | ||
interface Tool { | ||
type: "function"; | ||
function: ToolFunction; | ||
} | ||
interface FunctionToolSchema { | ||
type: "function"; | ||
executor: (inputs: Record<string, any>) => any | Promise<any>; | ||
tool: Tool; | ||
} | ||
interface ApiToolSchema { | ||
type: "api"; | ||
url: string; | ||
method: "get" | "post" | "delete" | "post" | "patch" | "put"; | ||
headers: Record<string, string>; | ||
tool: Tool; | ||
} | ||
interface SubpromptToolSchema { | ||
type: "subprompt"; | ||
prompt: Prompt; | ||
tool: Tool; | ||
} | ||
type ToolSchema = FunctionToolSchema | ApiToolSchema | SubpromptToolSchema; | ||
type Inputs = Record<string, Input> & { | ||
tools?: ToolSchema[]; | ||
message?: string; | ||
session_id?: string; | ||
run_id?: string; | ||
save_history?: boolean; | ||
plug?: Plug; | ||
}; | ||
@@ -121,4 +99,20 @@ interface StoreSession { | ||
} | ||
interface UserTextContent { | ||
type: "text"; | ||
text: string; | ||
} | ||
interface UserImageContent { | ||
type: "image_url"; | ||
image_url: { | ||
url: string; | ||
}; | ||
} | ||
interface UserContentHistory { | ||
role: "user"; | ||
name?: string; | ||
follow_up?: boolean; | ||
content: string | Array<UserTextContent | UserImageContent>; | ||
} | ||
interface ContentHistory { | ||
role: "system" | "user" | "assistant" | "model" | "prompt"; | ||
role: "system" | "assistant" | "model" | "prompt"; | ||
follow_up?: boolean; | ||
@@ -135,5 +129,31 @@ name?: string; | ||
} | ||
type LLMHistory = ContentHistory | ToolHistory; | ||
type LLMHistory = UserContentHistory | ContentHistory | ToolHistory; | ||
interface UserRunHistory { | ||
at: number; | ||
role: "user"; | ||
user_id?: string; | ||
run_id: string; | ||
session_id: string; | ||
request: Inputs; | ||
} | ||
interface AgentRunHistory { | ||
at: number; | ||
role: "agent"; | ||
run_id: string; | ||
session_id: string; | ||
agent_id: string; | ||
agent_name: string; | ||
response: LLMResponse; | ||
tools: { | ||
call: LLMToolCall; | ||
result: any; | ||
}[]; | ||
} | ||
type RunHistory = UserRunHistory | AgentRunHistory; | ||
interface Store { | ||
newSession: (id: string, user_name?: string) => void; | ||
newSession: (data: { | ||
id?: string; | ||
user_id?: string; | ||
user_name?: string; | ||
}) => void; | ||
getSession: (id: string) => Promise<StoreSession | undefined>; | ||
@@ -144,5 +164,6 @@ updateSession: (id: string, new_data: { | ||
}) => void; | ||
getHistory: (session: StoreSession) => Promise<LLMHistory[]>; | ||
pushHistory: (session: StoreSession, history: LLMHistory) => Promise<void>; | ||
getHistory: (session: StoreSession | string) => Promise<LLMHistory[]>; | ||
pushHistory: (session: StoreSession | string, history: LLMHistory) => Promise<void>; | ||
batchPushHistory: (session: StoreSession, history: LLMHistory[]) => Promise<void>; | ||
getRuns: (session: StoreSession) => Promise<RunHistory[]>; | ||
} | ||
@@ -161,2 +182,6 @@ interface LLMToolCall { | ||
tool_calls?: LLMToolCall[]; | ||
tools_history: { | ||
call: LLMToolCall; | ||
result: any; | ||
}[]; | ||
follow_up_history?: any[]; | ||
@@ -187,12 +212,40 @@ } | ||
type Input = string | number | boolean | Array<string | number | boolean> | Object; | ||
type PlugFunc = (context: string) => (string | Promise<string>); | ||
interface Plug { | ||
rag?: string | PlugFunc; | ||
images?: string[]; | ||
interface ToolParameters { | ||
type: "object"; | ||
properties: Record<string, Parameter>; | ||
required?: Array<string>; | ||
} | ||
type Inputs = Record<string, Input> & { | ||
message?: string; | ||
plug?: Plug; | ||
}; | ||
interface ToolFunction { | ||
name: string; | ||
description: string; | ||
parameters: ToolParameters; | ||
} | ||
interface Tool { | ||
type: "function"; | ||
function: ToolFunction; | ||
} | ||
interface FunctionToolSchema { | ||
type: "function"; | ||
executor: (inputs: Record<string, any> | any) => any | Promise<any>; | ||
tool: Tool; | ||
} | ||
interface ApiToolSchema { | ||
type: "api"; | ||
url: string; | ||
method: "get" | "post" | "delete" | "post" | "patch" | "put"; | ||
headers: Record<string, string>; | ||
tool: Tool; | ||
} | ||
interface ClientSideToolSchema { | ||
type: "client-side"; | ||
executor: (data: any) => any; | ||
tool: Tool; | ||
} | ||
interface AgentToolSchema { | ||
type: "agent"; | ||
agent_id: string; | ||
executor: (session_id: string, run_id: string, instructions: string) => Promise<string>; | ||
tool: Tool; | ||
} | ||
type ToolSchema = FunctionToolSchema | ApiToolSchema | ClientSideToolSchema | AgentToolSchema; | ||
@@ -210,43 +263,15 @@ interface AgentData { | ||
} | ||
interface AgentInnerRunResult { | ||
responses: Record<string, LLMResponse>; | ||
updated_history: LLMHistory[]; | ||
} | ||
interface AgentRunInputs { | ||
run_id: string; | ||
session: StoreSession; | ||
agent: AgentData; | ||
inputs: Inputs; | ||
} | ||
interface AgentResponse { | ||
run_id: string; | ||
session_id: string; | ||
responses: Record<string, LLMResponse>; | ||
response: LLMTextResponse; | ||
} | ||
interface StreamMessage { | ||
final?: boolean; | ||
type: "text" | "image"; | ||
run_id: string; | ||
content: string; | ||
prompt_name: string; | ||
} | ||
interface ToolCalledMessage { | ||
name: string; | ||
result: string; | ||
} | ||
type StreamFunc = (stream: StreamMessage) => (undefined | void | unknown); | ||
type StatusUpdateFunc = (status: string) => (undefined | void | unknown); | ||
type ToolCalledFunc = (data: ToolCalledMessage) => (undefined | void | unknown); | ||
interface StreamListener { | ||
type: "stream"; | ||
func: StreamFunc; | ||
} | ||
interface StatusUpdateListener { | ||
type: "status"; | ||
func: StatusUpdateFunc; | ||
} | ||
interface ToolCalledListener { | ||
type: "tool_call"; | ||
func: ToolCalledFunc; | ||
} | ||
type OnListener = StreamListener | StatusUpdateListener | ToolCalledListener; | ||
type StreamFunc = (stream: StreamMessage) => undefined | void | unknown; | ||
type StatusUpdateFunc = (status: string) => undefined | void | unknown; | ||
@@ -261,16 +286,215 @@ interface BoxData { | ||
interface BoxStream { | ||
type: "text" | "image"; | ||
agent_name: string; | ||
prompt_name: string; | ||
run_id: string; | ||
content: string; | ||
} | ||
type BoxStreamFunc = (stream: BoxStream) => (undefined | void | unknown); | ||
type BoxStreamFunc = (stream: BoxStream) => undefined | void | unknown; | ||
interface ServerBaseStream { | ||
type: "stream"; | ||
data: StreamMessage; | ||
} | ||
interface ServerStartStream { | ||
type: "start"; | ||
data: { | ||
run_id: string; | ||
session_id: string; | ||
}; | ||
} | ||
interface ServerTokenStream { | ||
type: "token"; | ||
data: string; | ||
} | ||
interface ServerResponseStream { | ||
type: "response"; | ||
data: AgentData; | ||
} | ||
interface ServerToolCallStream { | ||
type: "tool_call"; | ||
data: LLMToolCall; | ||
} | ||
interface ServerToolResStream { | ||
type: "tool_result"; | ||
data: { | ||
call: LLMToolCall; | ||
result: any; | ||
}; | ||
} | ||
interface ServerAgentStream { | ||
type: "select_agent"; | ||
data: { | ||
name: string; | ||
response: AgentResponse; | ||
}; | ||
} | ||
interface ServerAgentResponseStream { | ||
type: "agent_response"; | ||
data: { | ||
name: string; | ||
response: AgentResponse; | ||
}; | ||
} | ||
interface ServerBoxResponseStream { | ||
type: "box_response"; | ||
data: { | ||
name: string; | ||
run: AgentResponse; | ||
}[]; | ||
} | ||
interface ServerClientActionStream { | ||
type: "client_action"; | ||
data: { | ||
id: string; | ||
tool_name: string; | ||
arguments: Record<string, any>; | ||
}; | ||
} | ||
interface Hooks { | ||
onStream?: (stream: StreamMessage) => any; | ||
onToken?: (token: string) => any; | ||
onFinish?: (response: AgentResponse) => any; | ||
onStart?: (info: { | ||
run_id: string; | ||
session_id: string; | ||
}) => any; | ||
onToolCall?: (call: LLMToolCall) => any; | ||
onToolResult?: (tool: { | ||
call: LLMToolCall; | ||
result: any; | ||
}) => any; | ||
onClientSideAction?: (action: ServerClientActionStream["data"]) => any; | ||
onError?: (data: { | ||
healed?: boolean; | ||
error: string; | ||
}) => any; | ||
onAgentResponse?: (res: { | ||
name: string; | ||
response: AgentResponse; | ||
}) => any; | ||
} | ||
interface BoxHooks extends Hooks { | ||
onSelectAgent?: (agent: AgentData) => any; | ||
onBoxFinish?: (res: { | ||
name: string; | ||
run: AgentResponse; | ||
}[]) => any; | ||
} | ||
interface LoadAgentRequest { | ||
type: "load_agent"; | ||
payload: { | ||
id: string; | ||
}; | ||
} | ||
interface LoadBoxRequest { | ||
type: "load_box"; | ||
payload: { | ||
id: string; | ||
}; | ||
} | ||
interface RunAgentRequest { | ||
type: "run_agent"; | ||
payload: { | ||
id: string; | ||
inputs: Inputs; | ||
hooks: Array<keyof Hooks>; | ||
}; | ||
} | ||
interface RunBoxRequest { | ||
type: "run_box"; | ||
payload: { | ||
id: string; | ||
inputs: Inputs; | ||
hooks: Array<keyof BoxHooks>; | ||
}; | ||
} | ||
interface GetSessionRequest { | ||
type: "get_session"; | ||
payload: { | ||
id: string; | ||
allow_new?: boolean; | ||
}; | ||
} | ||
interface NewSessionRequest { | ||
type: "new_session"; | ||
payload: { | ||
id?: string; | ||
user_name?: string; | ||
user_id?: string; | ||
}; | ||
} | ||
interface DeleteSessionRequest { | ||
type: "delete_session"; | ||
payload: { | ||
id: string; | ||
}; | ||
} | ||
interface GetSessionRunsRequest { | ||
type: "get_session_runs"; | ||
payload: { | ||
id: string; | ||
}; | ||
} | ||
interface ListUserSessionsRequest { | ||
type: "list_user_sessions"; | ||
payload: { | ||
id: string; | ||
}; | ||
} | ||
type ServerRequest = LoadAgentRequest | LoadBoxRequest | RunAgentRequest | RunBoxRequest | GetSessionRequest | NewSessionRequest | DeleteSessionRequest | GetSessionRunsRequest | ListUserSessionsRequest; | ||
declare class RemoteStore implements Store { | ||
private url; | ||
private token; | ||
history: Record<string, LLMHistory[]>; | ||
sessions: Record<string, StoreSession>; | ||
users_sessions: Record<string, string[]>; | ||
constructor(token: string, url: string); | ||
request<Response>(path: string, method: string, body?: Record<string, any>): Promise<Response>; | ||
newSession({ id, user_id, user_name, }: { | ||
id?: string; | ||
user_id?: string; | ||
user_name?: string; | ||
}): Promise<void>; | ||
getSession(id: string): Promise<StoreSession | undefined>; | ||
deleteSession(id: string): Promise<void>; | ||
getUserSessions(user_id: string): Promise<string[]>; | ||
updateSession(id: string, new_data: { | ||
user_name?: string; | ||
saved_prompts?: Record<string, string>; | ||
}): Promise<void>; | ||
getHistory(session: string | StoreSession): Promise<LLMHistory[]>; | ||
pushHistory(session: string | StoreSession, new_history: LLMHistory | LLMHistory[]): Promise<void>; | ||
batchPushHistory(session: string | StoreSession, new_history: LLMHistory[]): Promise<void>; | ||
getRuns(session: StoreSession | string): Promise<RunHistory[]>; | ||
pushRun(session: StoreSession | string, run: RunHistory): Promise<void>; | ||
batchPushRuns(session: StoreSession | string, runs: RunHistory[]): Promise<void>; | ||
} | ||
declare class StateStore { | ||
state: Record<string, 0 | 1>; | ||
queue: Record<string, string[]>; | ||
constructor(); | ||
setState(id: string, state: 0 | 1): Promise<void>; | ||
getState(id: string): 0 | 1; | ||
checkState(id: string): boolean; | ||
queueRun(id: string, run_id: string, timeout?: number): Promise<void>; | ||
} | ||
declare class InMemoryStore implements Store { | ||
history: Record<string, LLMHistory[]>; | ||
sessions: Record<string, StoreSession>; | ||
users_sessions: Record<string, string[]>; | ||
runs: Record<string, RunHistory[]>; | ||
constructor(); | ||
checkSession(session: StoreSession): undefined; | ||
newSession(id: string, user_name?: string): Promise<void>; | ||
newSession({ id, user_id, user_name, }: { | ||
id?: string; | ||
user_id?: string; | ||
user_name?: string; | ||
}): Promise<void>; | ||
getSession(id: string): Promise<StoreSession | undefined>; | ||
deleteSession(id: string): Promise<void>; | ||
getUserSessions(user_id: string): Promise<string[]>; | ||
updateSession(id: string, new_data: { | ||
@@ -280,5 +504,8 @@ user_name?: string; | ||
}): Promise<void>; | ||
getHistory(session: StoreSession): Promise<LLMHistory[]>; | ||
pushHistory(session: StoreSession, new_history: LLMHistory): Promise<void>; | ||
getHistory(session: StoreSession | string): Promise<LLMHistory[]>; | ||
pushHistory(session: StoreSession | string, new_history: LLMHistory): Promise<void>; | ||
batchPushHistory(session: StoreSession, new_history: LLMHistory[]): Promise<void>; | ||
pushRun(session: StoreSession | string, run: RunHistory): Promise<void>; | ||
batchPushRuns(session: StoreSession | string, runs: RunHistory[]): Promise<void>; | ||
getRuns(session: StoreSession | string): Promise<RunHistory[]>; | ||
} | ||
@@ -289,17 +516,24 @@ | ||
private token; | ||
store: InMemoryStore; | ||
store: InMemoryStore | RemoteStore; | ||
memoryStore: InMemoryStore; | ||
engines: RawEngines | undefined; | ||
private loadedSessions; | ||
stateStore: StateStore; | ||
loaded_agents: Record<string, AgentData>; | ||
loaded_boxes: Record<string, BoxData>; | ||
constructor({ token, store, engines, }: { | ||
token: string; | ||
store?: "memory" | string | InMemoryStore; | ||
store?: "memory" | string | InMemoryStore | RemoteStore; | ||
engines?: RawEngines; | ||
}); | ||
getSession(id: string, allow_new?: boolean): Promise<StoreSession>; | ||
newSession({ id, user_name, }: { | ||
id: string; | ||
newSession({ id, user_name, user_id, }: { | ||
id?: string; | ||
user_name?: string; | ||
user_id?: string; | ||
}): Promise<StoreSession>; | ||
deleteSession(id: string): Promise<this>; | ||
pushRuns(session: StoreSession | string, runs: RunHistory[]): Promise<void>; | ||
listUserSessions(user_id: string): Promise<string[]>; | ||
getSessionRuns(session: StoreSession | string): Promise<RunHistory[]>; | ||
getSessionHistory(session: StoreSession | string): Promise<LLMHistory[]>; | ||
loadAgent(id: string): Promise<AgentData>; | ||
@@ -312,6 +546,4 @@ loadBox(id: string): Promise<BoxData>; | ||
agent: AgentData | null; | ||
private id; | ||
id: string; | ||
private client; | ||
private stateStore; | ||
private saved_prompts; | ||
tools: ToolSchema[]; | ||
@@ -321,4 +553,7 @@ streamFunc: StreamFunc | undefined; | ||
status_listeners: StatusUpdateFunc[]; | ||
tool_calls_listeners: ToolCalledFunc[]; | ||
prompt_listeners: ((response: LLMResponse) => any)[]; | ||
tool_calls_listeners: ((call: LLMToolCall) => any)[]; | ||
tool_results_listeners: ((tool: { | ||
call: LLMToolCall; | ||
result: any; | ||
}) => any)[]; | ||
finish_listeners: ((response: AgentResponse) => any)[]; | ||
@@ -328,3 +563,2 @@ constructor(id: string, client: Scoopika, options?: { | ||
engines?: RawEngines; | ||
stateStore?: StateStore; | ||
streamFunc?: StreamFunc; | ||
@@ -334,20 +568,22 @@ }); | ||
load(): Promise<Agent>; | ||
run(inputs: Inputs): Promise<AgentResponse>; | ||
chainRun({ run_id, session, agent, inputs, }: AgentRunInputs): Promise<{ | ||
run: AgentInnerRunResult; | ||
saved_prompts: Record<string, string>; | ||
}>; | ||
updateSavedPrompts(session: StoreSession, new_prompts: Record<string, string>): Promise<void>; | ||
setupHistory(session: StoreSession, inputs: Inputs, history: LLMHistory[]): LLMHistory[]; | ||
getStreamFunc(): StreamFunc; | ||
getPromptStreamFunc(): (response: LLMResponse) => any; | ||
private updateStatus; | ||
private toolCalled; | ||
on({ type, func }: OnListener): undefined; | ||
run({ inputs, hooks, }: { | ||
inputs: Inputs; | ||
hooks?: Hooks; | ||
}): Promise<AgentResponse>; | ||
structuredOutput<Data = Record<string, any>>({ inputs, schema, system_prompt, }: { | ||
inputs: Inputs; | ||
schema: ToolParameters | JSONSchema; | ||
system_prompt?: string; | ||
}): Promise<Data>; | ||
private getStreamFunc; | ||
private getToolCallStreamFunc; | ||
private getToolResStreamFunc; | ||
onToolCall(call: LLMToolCall): undefined; | ||
onStream(func: StreamFunc): void; | ||
onToken(func: StreamFunc): void; | ||
onPromptResponse(func: (response: LLMResponse) => any): void; | ||
onImage(func: (img: LLMImageResponse) => any): void; | ||
onFinish(func: (response: AgentResponse) => any): void; | ||
get<K extends keyof AgentData>(key: K): Promise<AgentData[K]>; | ||
info<K extends keyof AgentData>(key: K): Promise<AgentData[K]>; | ||
addTool<Data = any>(func: (args: Data) => any, tool: ToolFunction): this; | ||
addAgentAsTool(agent: Agent): Promise<this>; | ||
private selectTools; | ||
asTool(): Promise<AgentToolSchema>; | ||
} | ||
@@ -365,3 +601,2 @@ | ||
stream_listeners: BoxStreamFunc[]; | ||
prompt_listeners: ((response: LLMResponse) => any)[]; | ||
agent_selection_listeners: ((agent: AgentData) => any)[]; | ||
@@ -380,3 +615,6 @@ finish_listeners: ((response: { | ||
load(): Promise<Box>; | ||
run(inputs: Inputs): Promise<{ | ||
run({ inputs, hooks, }: { | ||
inputs: Inputs; | ||
hooks?: BoxHooks; | ||
}): Promise<{ | ||
name: string; | ||
@@ -395,8 +633,4 @@ run: AgentResponse; | ||
buildTools(): ToolSchema[]; | ||
getStreamFunc(): BoxStreamFunc; | ||
getPromptStreamFunc(): (response: LLMResponse) => any; | ||
getStreamFunc(run_listeners?: BoxStreamFunc[]): BoxStreamFunc; | ||
onStream(func: BoxStreamFunc): this; | ||
onPromptResponse(func: (response: LLMResponse) => any): void; | ||
onToken(func: (message: StreamMessage) => any): void; | ||
onImage(func: (img: LLMImageResponse) => any): void; | ||
onSelectAgent(func: (agent: AgentData) => any): void; | ||
@@ -407,7 +641,94 @@ onFinish(func: (response: { | ||
}[]) => any): void; | ||
addGlobalTool(func: (args: Record<string, any>) => any, tool: ToolFunction): void; | ||
addTool(agent_name: string, func: (args: Record<string, any>) => any, tool: ToolFunction): void; | ||
addGlobalTool<Data = any>(func: (args: Data) => any, tool: ToolFunction): void; | ||
addTool<Data = any>(agent_name: string, func: (args: Data) => any, tool: ToolFunction): void; | ||
addAgentAsTool(agent: Agent): Promise<this>; | ||
system_prompt: string; | ||
} | ||
export { Agent, Box, InMemoryStore, Scoopika }; | ||
declare class StreamObject<Data> { | ||
data: Data; | ||
constructor(data: Data); | ||
string(): string; | ||
object(): Data; | ||
} | ||
declare const serverStream: { | ||
baseStream: (data: ServerBaseStream["data"]) => StreamObject<ServerBaseStream>; | ||
startStream: (data: ServerStartStream["data"]) => StreamObject<ServerStartStream>; | ||
tokenStream: (data: ServerTokenStream["data"]) => StreamObject<ServerTokenStream>; | ||
responseStream: (data: ServerResponseStream["data"]) => StreamObject<ServerResponseStream>; | ||
toolCallStream: (data: ServerToolCallStream["data"]) => StreamObject<ServerToolCallStream>; | ||
toolResultStream: (data: ServerToolResStream["data"]) => StreamObject<ServerToolResStream>; | ||
agentSelectedStream: (data: ServerAgentStream["data"]) => StreamObject<ServerAgentStream>; | ||
agentResponseStream: (data: ServerAgentResponseStream["data"]) => StreamObject<ServerAgentResponseStream>; | ||
boxResponseStream: (data: ServerBoxResponseStream["data"]) => StreamObject<ServerBoxResponseStream>; | ||
}; | ||
declare function serverHooks(used_hooks: Array<keyof BoxHooks>, callBack: (stream: string) => any): BoxHooks; | ||
declare function serverRequestBody(body: Record<string, any>): { | ||
inputs: Inputs; | ||
hooks: Array<keyof BoxHooks>; | ||
}; | ||
declare function setupAgents(arg: { | ||
agents: string[]; | ||
scoopika: Scoopika; | ||
} | (() => Promise<Agent[]>)): () => Promise<Agent[]>; | ||
declare function setupBoxes(arg: { | ||
boxes: string[]; | ||
scoopika: Scoopika; | ||
} | (() => Promise<Box[]>)): () => Promise<Box[]>; | ||
type Stream = (s: string) => any; | ||
type Mappings = { | ||
[K in ServerRequest["type"]]: (s: Stream, payload: Extract<ServerRequest, { | ||
type: K; | ||
}>["payload"]) => any; | ||
}; | ||
declare class Container { | ||
scoopika: Scoopika; | ||
setupAgents?: () => Promise<Agent[]>; | ||
setupBoxes?: () => Promise<Box[]>; | ||
onRequest?: (req: ServerRequest) => any; | ||
caching: boolean; | ||
latest_setup: number; | ||
agents: Agent[]; | ||
boxes: Box[]; | ||
caching_limit: number; | ||
constructor({ scoopika, setupAgents, setupBoxes, onRequest, caching, caching_limit, }: { | ||
scoopika: Scoopika; | ||
setupAgents?: () => Promise<Agent[]>; | ||
setupBoxes?: () => Promise<Box[]>; | ||
onRequest?: (req: ServerRequest) => any; | ||
caching?: boolean; | ||
caching_limit?: number; | ||
}); | ||
private getAgent; | ||
private getBox; | ||
handleRequest(full_request: { | ||
request: Record<string, any> | any; | ||
stream: (s: string) => any; | ||
end?: () => any; | ||
}): Promise<void>; | ||
private setup; | ||
private getSession; | ||
private handleAgentRun; | ||
private handleBoxRun; | ||
private loadAgent; | ||
private loadBox; | ||
private newSession; | ||
private deleteSession; | ||
private listUserSessions; | ||
private getSessionRuns; | ||
private streamMessage; | ||
handlers: Mappings; | ||
} | ||
declare function createToolSchema(tool: { | ||
name: string; | ||
description: string; | ||
parameters: JSONSchema; | ||
}): ToolFunction; | ||
export { Agent, Box, Container, InMemoryStore, Scoopika, createToolSchema, serverHooks, serverRequestBody, serverStream, setupAgents, setupBoxes }; |
{ | ||
"name": "@scoopika/scoopika", | ||
"version": "1.0.4", | ||
"version": "1.0.5", | ||
"description": "Create magical AI agents and boxes with multiple agents in seconds", | ||
@@ -21,3 +21,3 @@ "main": "dist/index.js", | ||
"devDependencies": { | ||
"@scoopika/types": "^1.2.2", | ||
"@scoopika/types": "^1.8.5", | ||
"@types/node": "^20.12.7", | ||
@@ -32,5 +32,10 @@ "@vitest/coverage-v8": "^1.5.0", | ||
"@google/generative-ai": "^0.7.0", | ||
"@types/turndown": "^5.0.4", | ||
"ajv": "^8.12.0", | ||
"openai": "^4.33.0" | ||
"axios": "^1.6.8", | ||
"cheerio": "^1.0.0-rc.12", | ||
"json-schema-to-ts": "^3.1.0", | ||
"openai": "^4.33.0", | ||
"turndown": "^7.1.3" | ||
} | ||
} |
487
src/agent.ts
@@ -1,8 +0,10 @@ | ||
import PromptChain from "./prompt_chain"; | ||
import StateStore from "./state"; | ||
import buildClients from "./lib/build_clients"; | ||
import resolveRAG from "./lib/resolve_rag"; | ||
import resolveInputs from "./lib/resolve_inputs"; | ||
import crypto from "node:crypto"; | ||
import * as types from "@scoopika/types"; | ||
import Scoopika from "./scoopika"; | ||
import Run from "./run"; | ||
import mixRuns from "./lib/mix_runs"; | ||
import { FromSchema, JSONSchema } from "json-schema-to-ts"; | ||
@@ -12,6 +14,4 @@ class Agent { | ||
public agent: types.AgentData | null = null; | ||
private id: string; | ||
public id: string; | ||
private client: Scoopika; | ||
private stateStore: StateStore; | ||
private saved_prompts: Record<string, Record<string, string>> = {}; | ||
public tools: types.ToolSchema[] = []; | ||
@@ -22,4 +22,7 @@ | ||
status_listeners: types.StatusUpdateFunc[] = []; | ||
tool_calls_listeners: types.ToolCalledFunc[] = []; | ||
prompt_listeners: ((response: types.LLMResponse) => any)[] = []; | ||
tool_calls_listeners: ((call: types.LLMToolCall) => any)[] = []; | ||
tool_results_listeners: ((tool: { | ||
call: types.LLMToolCall; | ||
result: any; | ||
}) => any)[] = []; | ||
finish_listeners: ((response: types.AgentResponse) => any)[] = []; | ||
@@ -33,3 +36,2 @@ | ||
engines?: types.RawEngines; | ||
stateStore?: StateStore; | ||
streamFunc?: types.StreamFunc; | ||
@@ -41,15 +43,16 @@ }, | ||
if (client.engines) { | ||
this.llm_clients = buildClients(client.engines); | ||
} | ||
if (client.loaded_agents[id]) { | ||
this.agent = client.loaded_agents[id]; | ||
} | ||
if (!options) { | ||
this.stateStore = new StateStore(); | ||
return; | ||
} | ||
let { agent, stateStore, engines, streamFunc } = options; | ||
const { agent, engines, streamFunc } = options; | ||
if (stateStore) { | ||
this.stateStore = new StateStore(); | ||
} else { | ||
this.stateStore = new StateStore(); | ||
} | ||
if (agent) { | ||
@@ -61,4 +64,2 @@ this.agent = agent; | ||
this.llm_clients = buildClients(engines); | ||
} else if (client.engines) { | ||
this.llm_clients = buildClients(client.engines); | ||
} | ||
@@ -88,3 +89,9 @@ | ||
public async run(inputs: types.Inputs): Promise<types.AgentResponse> { | ||
public async run({ | ||
inputs, | ||
hooks, | ||
}: { | ||
inputs: types.Inputs; | ||
hooks?: types.Hooks; | ||
}): Promise<types.AgentResponse> { | ||
if (!this.agent) { | ||
@@ -95,41 +102,102 @@ await this.loadAgent(); | ||
const session_id: string = | ||
typeof inputs.session_id === "string" | ||
? inputs.session_id | ||
: "session_" + crypto.randomUUID(); | ||
inputs.session_id || "session_" + crypto.randomUUID(); | ||
const run_id = inputs.run_id || "run_" + crypto.randomUUID(); | ||
const original_inputs: types.Inputs = JSON.parse(JSON.stringify(inputs)); | ||
const new_inputs: types.Inputs = { | ||
...(await resolveInputs(inputs)), | ||
session_id, | ||
run_id, | ||
}; | ||
const start = Date.now(); | ||
const agent = this.agent as types.AgentData; | ||
const session = await this.client.getSession(session_id); | ||
const run_id = | ||
typeof inputs.run_id === "string" | ||
? inputs.run_id | ||
: "run_" + crypto.randomUUID(); | ||
const { run, saved_prompts } = await this.chainRun({ | ||
run_id, | ||
if (inputs.save_history !== false) { | ||
this.client.pushRuns(session, [ | ||
{ | ||
at: start, | ||
role: "user", | ||
session_id, | ||
run_id, | ||
user_id: session.user_id, | ||
request: original_inputs, | ||
}, | ||
]); | ||
} | ||
if (hooks && hooks.onStart) { | ||
hooks.onStart({ run_id, session_id }); | ||
} | ||
const run_listeners: ((s: types.StreamMessage) => any)[] = []; | ||
if (hooks && hooks.onStream) { | ||
run_listeners.push(hooks.onStream); | ||
} | ||
if (hooks && hooks.onToken) { | ||
run_listeners.push(async (s: types.StreamMessage) => { | ||
if (hooks.onToken) { | ||
hooks.onToken(s.content); | ||
} | ||
}); | ||
} | ||
const history: types.LLMHistory[] = await mixRuns( | ||
agent.id, | ||
session, | ||
await this.client.getSessionRuns(session), | ||
); | ||
const modelRun = new Run({ | ||
session, | ||
clients: this.llm_clients, | ||
agent, | ||
inputs, | ||
tools: [], | ||
stream: this.getStreamFunc(run_listeners), | ||
toolCallStream: this.getToolCallStreamFunc( | ||
hooks?.onToolCall && [hooks.onToolCall], | ||
), | ||
toolResStream: this.getToolResStreamFunc( | ||
hooks?.onToolResult && [hooks.onToolResult], | ||
), | ||
clientActionStream: hooks?.onClientSideAction, | ||
}); | ||
await this.client.store.batchPushHistory(session, run.updated_history); | ||
this.updateSavedPrompts(session, saved_prompts); | ||
const wanted_tools = await this.selectTools(modelRun, history, new_inputs); | ||
modelRun.tools = wanted_tools; | ||
await this.stateStore.setState(session.id, 0); | ||
const run = await modelRun.run({ run_id, inputs: new_inputs, history }); | ||
let res: types.AgentResponse; | ||
if (modelRun.built_prompt) { | ||
await this.client.store.updateSession(session_id, { | ||
...session, | ||
saved_prompts: { | ||
...session.saved_prompts, | ||
[agent.id]: modelRun.built_prompt, | ||
}, | ||
}); | ||
} | ||
if (!this.agent?.chained) { | ||
res = { | ||
run_id, | ||
session_id, | ||
responses: { | ||
main: run.responses[Object.keys(run.responses)[0]], | ||
const res: types.AgentResponse = { | ||
run_id, | ||
session_id, | ||
response: run.response, | ||
}; | ||
if (inputs.save_history !== false) { | ||
await this.client.pushRuns(session, [ | ||
{ | ||
at: Date.now(), | ||
role: "agent", | ||
run_id, | ||
session_id, | ||
agent_id: agent.id, | ||
agent_name: agent.name, | ||
response: run.response, | ||
tools: run.tools_history, | ||
}, | ||
}; | ||
} else { | ||
res = { | ||
run_id, | ||
session_id: session.id, | ||
responses: run.responses, | ||
}; | ||
]); | ||
} | ||
@@ -139,169 +207,252 @@ | ||
if (hooks?.onFinish) { | ||
hooks.onFinish(res); | ||
} | ||
if (hooks?.onAgentResponse) { | ||
hooks.onAgentResponse({ | ||
name: agent.name, | ||
response: res, | ||
}); | ||
} | ||
return res; | ||
} | ||
async chainRun({ | ||
run_id, | ||
session, | ||
agent, | ||
public async structuredOutput<Data = Record<string, any>>({ | ||
inputs, | ||
}: types.AgentRunInputs): Promise<{ | ||
run: types.AgentInnerRunResult; | ||
saved_prompts: Record<string, string>; | ||
}> { | ||
const prompt_chain = new PromptChain({ | ||
session, | ||
agent, | ||
clients: this.llm_clients, | ||
stream: this.getStreamFunc(), | ||
statusUpdate: this.updateStatus, | ||
tools: [...agent.tools, ...this.tools], | ||
prompts: agent.prompts, | ||
saved_prompts: this.saved_prompts[session.id] || {}, | ||
}); | ||
schema, | ||
system_prompt, | ||
}: { | ||
inputs: types.Inputs; | ||
schema: types.ToolParameters | JSONSchema; | ||
system_prompt?: string; | ||
}): Promise<Data> { | ||
if (!this.agent) { | ||
await this.loadAgent(); | ||
} | ||
const newInputs: types.Inputs = await resolveRAG(inputs); | ||
const session_id: string = | ||
typeof inputs.session_id === "string" | ||
? inputs.session_id | ||
: "session_" + crypto.randomUUID(); | ||
const history = this.setupHistory( | ||
const agent = this.agent as types.AgentData; | ||
const session = await this.client.getSession(session_id); | ||
const new_inputs: types.Inputs = await resolveInputs(inputs); | ||
const history: types.LLMHistory[] = await mixRuns( | ||
"STRUCTURED", | ||
session, | ||
newInputs, | ||
await this.client.store.getHistory(session), | ||
await this.client.getSessionRuns(session), | ||
); | ||
const run = await prompt_chain.run({ | ||
run_id, | ||
inputs: newInputs, | ||
messages: history, | ||
wanted_responses: agent.wanted_responses, | ||
timeout: agent.timeout, | ||
onPromptResponse: this.getPromptStreamFunc(), | ||
const modelRun = new Run({ | ||
session, | ||
clients: this.llm_clients, | ||
agent, | ||
tools: [...this.tools, ...(agent.tools || []), ...(inputs.tools || [])], | ||
stream: () => {}, | ||
toolCallStream: () => {}, | ||
toolResStream: () => {}, | ||
}); | ||
const output = await modelRun.jsonRun<Data>({ | ||
inputs: new_inputs, | ||
schema: schema as types.ToolParameters, | ||
history, | ||
system_prompt, | ||
}); | ||
const updated_history: types.LLMHistory[] = !inputs.message | ||
? run.updated_history | ||
: [ | ||
{ | ||
role: "user", | ||
name: session.user_name, | ||
content: inputs.message, | ||
}, | ||
...run.updated_history, | ||
]; | ||
return output; | ||
} | ||
return { | ||
run: { ...run, updated_history }, | ||
saved_prompts: prompt_chain.saved_prompts, | ||
private getStreamFunc( | ||
run_listeners?: ((stream: types.StreamMessage) => void)[], | ||
): types.StreamFunc { | ||
const listeners = [...this.stream_listeners, ...(run_listeners || [])]; | ||
return (message: types.StreamMessage) => { | ||
for (const l of listeners) { | ||
l(message); | ||
} | ||
}; | ||
} | ||
async updateSavedPrompts( | ||
session: types.StoreSession, | ||
new_prompts: Record<string, string>, | ||
) { | ||
this.saved_prompts[session.id] = new_prompts; | ||
await this.client.store.updateSession(session.id, { | ||
saved_prompts: new_prompts, | ||
}); | ||
private getToolCallStreamFunc( | ||
run_listeners?: ((call: types.LLMToolCall) => any)[], | ||
): (call: types.LLMToolCall) => any { | ||
const listeners = [...this.tool_calls_listeners, ...(run_listeners || [])]; | ||
return (call: types.LLMToolCall) => { | ||
listeners.map((l) => l(call)); | ||
}; | ||
} | ||
setupHistory( | ||
session: types.StoreSession, | ||
inputs: types.Inputs, | ||
history: types.LLMHistory[], | ||
): types.LLMHistory[] { | ||
const newHistory: types.LLMHistory[] = JSON.parse(JSON.stringify(history)); | ||
private getToolResStreamFunc( | ||
run_listeners?: ((tool: { call: types.LLMToolCall; result: any }) => any)[], | ||
): (tool: { call: types.LLMToolCall; result: any }) => any { | ||
const listeners = [ | ||
...this.tool_results_listeners, | ||
...(run_listeners || []), | ||
]; | ||
return (tool: { call: types.LLMToolCall; result: any }) => { | ||
listeners.map((l) => l(tool)); | ||
}; | ||
} | ||
if (typeof inputs.message === "string") { | ||
newHistory.push({ | ||
role: "user", | ||
name: session.user_name || "User", | ||
content: inputs.message, | ||
}); | ||
} | ||
public onToolCall(call: types.LLMToolCall): undefined { | ||
this.tool_calls_listeners.map((listener) => listener(call)); | ||
} | ||
return newHistory; | ||
public onStream(func: types.StreamFunc): void { | ||
this.stream_listeners.push(func); | ||
} | ||
getStreamFunc(): types.StreamFunc { | ||
if (this.streamFunc) { | ||
return this.streamFunc; | ||
} | ||
const listeners = this.stream_listeners; | ||
return (message: types.StreamMessage) => { | ||
listeners.map((listener) => { | ||
listener(message); | ||
}); | ||
}; | ||
public onToken(func: types.StreamFunc): void { | ||
this.stream_listeners.push(func); | ||
} | ||
getPromptStreamFunc(): (response: types.LLMResponse) => any { | ||
const listeners = this.prompt_listeners; | ||
return (response: types.LLMResponse) => { | ||
listeners.map((listener) => { | ||
listener(response); | ||
}); | ||
}; | ||
public async info<K extends keyof types.AgentData>( | ||
key: K, | ||
): Promise<types.AgentData[K]> { | ||
if (!this.agent) { | ||
await this.loadAgent(); | ||
} | ||
if (!this.agent) { | ||
throw new Error("Agent not loaded"); | ||
} | ||
return this.agent[key]; | ||
} | ||
private updateStatus(status: string): undefined { | ||
this.status_listeners.map((listener) => { | ||
listener(status); | ||
public addTool<Data = any>( | ||
func: (args: Data) => any, | ||
tool: types.ToolFunction, | ||
) { | ||
this.tools.push({ | ||
type: "function", | ||
executor: func, | ||
tool: { | ||
type: "function", | ||
function: tool, | ||
}, | ||
}); | ||
return this; | ||
} | ||
private toolCalled(data: types.ToolCalledMessage): undefined { | ||
this.tool_calls_listeners.map((listener) => listener(data)); | ||
public async addAgentAsTool(agent: Agent) { | ||
const agent_tool = await agent.asTool(); | ||
this.tools.push(agent_tool); | ||
return this; | ||
} | ||
public on({ type, func }: types.OnListener): undefined { | ||
if (type === "stream") { | ||
this.stream_listeners.push(func); | ||
return; | ||
} | ||
private async selectTools( | ||
run: Run, | ||
history: types.LLMHistory[], | ||
inputs: types.Inputs, | ||
) { | ||
const tools: types.ToolSchema[] = [ | ||
...this.tools, | ||
...(inputs.tools || []), | ||
...(this.agent?.tools || []), | ||
]; | ||
if (type === "status") { | ||
this.status_listeners.push(func); | ||
return; | ||
const max = Number(inputs.max_tools || 5); | ||
if (tools.length <= max) { | ||
return tools; | ||
} | ||
this.tool_calls_listeners.push(func); | ||
} | ||
const prompt = | ||
"Your role is to select at most 7 tools that might be helpful to achieve the user request from a list of available tools, never make up new tools, and don't include tools that are not relevant to the user request. output a JSON object with the names of the tools that might be useful based on the context of previous conversations and current user request"; | ||
public onStream(func: types.StreamFunc): void { | ||
this.stream_listeners.push(func); | ||
} | ||
const string_tools: string[] = tools.map( | ||
(t) => `${t.tool.function.name}: ${t.tool.function.description}`, | ||
); | ||
const message = | ||
(inputs.message || "") + | ||
`\n\nAvailable tools:\n${string_tools.join(".\n")}`; | ||
public onToken(func: types.StreamFunc): void { | ||
this.stream_listeners.push(func); | ||
} | ||
const schema = { | ||
type: "object", | ||
properties: { | ||
tools: { | ||
description: | ||
"The selected tools names that are relevant to the context", | ||
type: "array", | ||
items: { | ||
type: "string", | ||
}, | ||
}, | ||
}, | ||
required: ["tools"], | ||
} as const satisfies JSONSchema; | ||
public onPromptResponse(func: (response: types.LLMResponse) => any) { | ||
this.prompt_listeners.push(func); | ||
} | ||
type Output = FromSchema<typeof schema>; | ||
onImage(func: (img: types.LLMImageResponse) => any) { | ||
this.prompt_listeners.push((response) => { | ||
if (response.type !== "image") { | ||
return; | ||
} | ||
func(response); | ||
const output = await run.jsonRun<Output>({ | ||
inputs: { ...inputs, message }, | ||
system_prompt: prompt, | ||
history, | ||
schema: schema as any as types.ToolParameters, | ||
}); | ||
} | ||
onFinish(func: (response: types.AgentResponse) => any) { | ||
this.finish_listeners.push(func); | ||
const selected_names = output.tools.slice(0, 4); | ||
const wanted = tools.filter( | ||
(t) => selected_names.indexOf(t.tool.function.name) !== -1, | ||
); | ||
return wanted; | ||
} | ||
public async get<K extends keyof types.AgentData>( | ||
key: K, | ||
): Promise<types.AgentData[K]> { | ||
public async asTool(): Promise<types.AgentToolSchema> { | ||
if (!this.agent) { | ||
await this.loadAgent(); | ||
} | ||
if (!this.agent) { | ||
throw new Error("Agent not loaded"); | ||
} | ||
return this.agent[key]; | ||
const agent = this.agent as types.AgentData; | ||
const runFunc = this.run; | ||
const executor: types.AgentToolSchema["executor"] = async ( | ||
session_id: string, | ||
run_id: string, | ||
instructions: string, | ||
) => { | ||
const res = await runFunc({ | ||
inputs: { | ||
session_id, | ||
run_id, | ||
message: instructions, | ||
save_history: false, | ||
}, | ||
}); | ||
return res.response.content; | ||
}; | ||
const agent_tool: types.AgentToolSchema = { | ||
type: "agent", | ||
agent_id: this.id, | ||
executor, | ||
tool: { | ||
type: "function", | ||
function: { | ||
name: agent.name, | ||
description: `an AI agent called ${agent.name}. its task is: ${agent.description}`, | ||
parameters: { | ||
type: "object", | ||
properties: { | ||
instructions: { | ||
type: "string", | ||
description: | ||
"The instruction or task to give the agent. include all instructions to guide this agent", | ||
}, | ||
}, | ||
required: ["instructions"], | ||
}, | ||
}, | ||
}, | ||
}; | ||
return agent_tool; | ||
} | ||
@@ -308,0 +459,0 @@ } |
136
src/box.ts
@@ -21,3 +21,2 @@ import Scoopika from "./scoopika"; | ||
stream_listeners: types.BoxStreamFunc[] = []; | ||
prompt_listeners: ((response: types.LLMResponse) => any)[] = []; | ||
agent_selection_listeners: ((agent: types.AgentData) => any)[] = []; | ||
@@ -42,2 +41,6 @@ finish_listeners: (( | ||
if (client.loaded_boxes[id]) { | ||
this.box = client.loaded_boxes[id]; | ||
} | ||
if (!options) { | ||
@@ -79,5 +82,9 @@ return; | ||
async run( | ||
inputs: types.Inputs, | ||
): Promise<{ name: string; run: types.AgentResponse }[]> { | ||
async run({ | ||
inputs, | ||
hooks, | ||
}: { | ||
inputs: types.Inputs; | ||
hooks?: types.BoxHooks; | ||
}): Promise<{ name: string; run: types.AgentResponse }[]> { | ||
if (!this.box) { | ||
@@ -88,12 +95,23 @@ await this.load(); | ||
const session_id: string = | ||
typeof inputs.session_id === "string" | ||
? inputs.session_id | ||
: "session_" + crypto.randomUUID(); | ||
inputs.session_id || "session_" + crypto.randomUUID(); | ||
const run_id = inputs.run_id || "run_" + crypto.randomUUID(); | ||
const box = this.box as types.BoxData; | ||
const session = await this.client.getSession(session_id); | ||
const run_id = "run_" + crypto.randomUUID(); | ||
const streamFunc = this.getStreamFunc(); | ||
const promptStreamFunc = this.getPromptStreamFunc(); | ||
const run_listeners: ((s: types.StreamMessage) => any)[] = []; | ||
if (hooks && hooks.onStream) { | ||
run_listeners.push(hooks.onStream); | ||
} | ||
if (hooks && hooks.onToken) { | ||
run_listeners.push((s: types.StreamMessage) => { | ||
if (hooks.onToken) { | ||
hooks.onToken(s.content); | ||
} | ||
}); | ||
} | ||
const streamFunc = this.getStreamFunc(run_listeners); | ||
if (typeof inputs.run_id !== "string") { | ||
@@ -125,2 +143,7 @@ inputs.run_id = run_id; | ||
this.agent_selection_listeners.forEach((listener) => listener(agentData)); | ||
if (hooks?.onSelectAgent) { | ||
hooks.onSelectAgent(agentData); | ||
} | ||
const agent = new Agent(agentData.id, this.client, { | ||
@@ -133,2 +156,3 @@ agent: { | ||
...(this.agents_tools[agentData.name.toLowerCase()] || []), | ||
...(this.agents_tools[agentData.id] || []), | ||
], | ||
@@ -139,20 +163,23 @@ }, | ||
agent.onStream((stream: types.StreamMessage) => { | ||
if (stream.type !== "text") return; | ||
streamFunc({ | ||
prompt_name: stream.prompt_name, | ||
run_id: stream.run_id, | ||
type: stream.type, | ||
agent_name: this.running_agent, | ||
content: stream.content, | ||
agent_name: this.running_agent, | ||
}); | ||
}); | ||
agent.onPromptResponse((response) => { | ||
promptStreamFunc(response); | ||
}); | ||
this.running_agent = agentData.name; | ||
const run = await agent.run({ | ||
...inputs, | ||
message: selected.instructions, | ||
inputs: { | ||
...inputs, | ||
message: selected.instructions, | ||
}, | ||
}); | ||
responses.push({ name: agentData.name, run }); | ||
if (hooks?.onAgentResponse) { | ||
hooks.onAgentResponse({ name: agentData.name, response: run }); | ||
} | ||
} | ||
@@ -162,2 +189,6 @@ | ||
if (hooks?.onBoxFinish) { | ||
hooks.onBoxFinish(responses); | ||
} | ||
return responses; | ||
@@ -210,3 +241,3 @@ } | ||
const tools = this.buildTools(); | ||
const modelRunner = new Model(client, undefined, tools); | ||
const modelRunner = new Model(client, tools); | ||
const LLM_inputs: types.LLMFunctionBaseInputs = { | ||
@@ -222,11 +253,12 @@ tools: tools.map((tool) => tool.tool), | ||
const run = await modelRunner.baseRun( | ||
"BOX", | ||
() => {}, | ||
() => {}, | ||
LLM_inputs, | ||
false, | ||
undefined, | ||
false, | ||
); | ||
const run = await modelRunner.baseRun({ | ||
run_id: "BOX", | ||
session_id: "DUMMY_SESSION_" + crypto.randomUUID(), | ||
stream: () => {}, | ||
onToolCall: () => {}, | ||
onToolRes: () => {}, | ||
updateHistory: () => {}, | ||
inputs: LLM_inputs, | ||
execute_tools: false, | ||
}); | ||
@@ -309,3 +341,3 @@ if (!run.tool_calls || run.tool_calls.length < 1) { | ||
name: agent.name, | ||
description: `${agent.name} is an agent. task: ${agent.description}`, | ||
description: `${agent.name} is an AI agent. task: ${agent.description}`, | ||
parameters: { | ||
@@ -329,4 +361,5 @@ type: "object", | ||
getStreamFunc(): types.BoxStreamFunc { | ||
const listeners = this.stream_listeners; | ||
getStreamFunc(run_listeners?: types.BoxStreamFunc[]): types.BoxStreamFunc { | ||
const listeners = [...this.stream_listeners, ...(run_listeners || [])]; | ||
return (message: types.BoxStream) => { | ||
@@ -337,11 +370,2 @@ listeners.map((listener) => listener(message)); | ||
getPromptStreamFunc(): (response: types.LLMResponse) => any { | ||
const listeners = this.prompt_listeners; | ||
return (response: types.LLMResponse) => { | ||
listeners.map((listener) => { | ||
listener(response); | ||
}); | ||
}; | ||
} | ||
onStream(func: types.BoxStreamFunc) { | ||
@@ -352,20 +376,2 @@ this.stream_listeners.push(func); | ||
onPromptResponse(func: (response: types.LLMResponse) => any) { | ||
this.prompt_listeners.push(func); | ||
} | ||
onToken(func: (message: types.StreamMessage) => any) { | ||
this.stream_listeners.push(func); | ||
} | ||
onImage(func: (img: types.LLMImageResponse) => any) { | ||
this.prompt_listeners.push((response) => { | ||
if (response.type !== "image") { | ||
return; | ||
} | ||
func(response); | ||
}); | ||
} | ||
onSelectAgent(func: (agent: types.AgentData) => any) { | ||
@@ -381,4 +387,4 @@ this.agent_selection_listeners.push(func); | ||
addGlobalTool( | ||
func: (args: Record<string, any>) => any, | ||
addGlobalTool<Data = any>( | ||
func: (args: Data) => any, | ||
tool: types.ToolFunction, | ||
@@ -396,5 +402,5 @@ ) { | ||
addTool( | ||
addTool<Data = any>( | ||
agent_name: string, | ||
func: (args: Record<string, any>) => any, | ||
func: (args: Data) => any, | ||
tool: types.ToolFunction, | ||
@@ -416,2 +422,8 @@ ) { | ||
public async addAgentAsTool(agent: Agent) { | ||
const agent_tool = await agent.asTool(); | ||
this.tools.push(agent_tool); | ||
return this; | ||
} | ||
system_prompt = `You are a manager that chooses from a number of AI agents to execute a specific task. choose the most suitable agent for the task.`; | ||
@@ -418,0 +430,0 @@ } |
@@ -5,3 +5,25 @@ import Agent from "./agent"; | ||
import Box from "./box"; | ||
import serverStream from "./server_stream"; | ||
import serverHooks from "./server_hooks"; | ||
import serverRequestBody from "./server_request"; | ||
import setupAgents from "./setup_agents"; | ||
import setupBoxes from "./setup_boxes"; | ||
import Container from "./container"; | ||
import { JSONSchema, FromSchema } from "json-schema-to-ts"; | ||
import { createToolSchema } from "./create_tool"; | ||
export { Scoopika, Agent, Box, InMemoryStore }; | ||
export { | ||
Scoopika, | ||
Agent, | ||
Box, | ||
InMemoryStore, | ||
serverStream, | ||
serverHooks, | ||
serverRequestBody, | ||
setupAgents, | ||
setupBoxes, | ||
Container, | ||
JSONSchema, | ||
FromSchema, | ||
createToolSchema, | ||
}; |
@@ -35,4 +35,2 @@ import * as types from "@scoopika/types"; | ||
// console.log(content); | ||
return { | ||
@@ -53,7 +51,7 @@ content: content, | ||
if (!value && input.required) { | ||
if (value === undefined && input.required) { | ||
return { success: false, errors: ["missing"] }; | ||
} | ||
if (!value) { | ||
if (typeof value !== "boolean" && !value) { | ||
return undefined; | ||
@@ -60,0 +58,0 @@ } |
import { LLMHistory } from "@scoopika/types"; | ||
function mixHistory(history: LLMHistory[]): string { | ||
const stringHistory = history.map((item) => { | ||
if (item.role === "user") { | ||
return `${item.name} (User): ${item.content}`; | ||
} | ||
function mixHistory(history: LLMHistory[]): LLMHistory[] { | ||
const system_prompts = history.filter((i) => i.role === "system"); | ||
if (item.role === "assistant" || item.role === "model") { | ||
return `${item.name} (AI assistant): ${item.content}`; | ||
} | ||
const string_history = history | ||
.filter((i) => i.role !== "system") | ||
.map((item) => { | ||
if (item.role === "user") { | ||
return `${item.name} (User): ${item.content}`; | ||
} | ||
if (item.role === "tool") { | ||
return `Executed tool (${item.name}) with results: ${item.content}`; | ||
} | ||
if (item.role === "assistant" || item.role === "model") { | ||
return `${item.name} (AI assistant): ${item.content}`; | ||
} | ||
if (item.role === "prompt") { | ||
return `${item.name || "result"}: ${item.content}`; | ||
} | ||
if (item.role === "tool") { | ||
return `Executed tool (${item.name}) with results: ${item.content}`; | ||
} | ||
return `${item.name}: ${item.content}`; | ||
}); | ||
return stringHistory.join(".\n"); | ||
if (item.role === "prompt") { | ||
return `${item.name || "result"}: ${item.content}`; | ||
} | ||
return `${item.name}: ${item.content}`; | ||
}); | ||
const mixed_history: LLMHistory[] = [ | ||
...system_prompts, | ||
{ | ||
role: "user", | ||
content: `Context history:\n${string_history.join(".\n")}`, | ||
}, | ||
]; | ||
return mixed_history; | ||
} | ||
export default mixHistory; |
@@ -1,5 +0,72 @@ | ||
import { ToolSchema } from "@scoopika/types"; | ||
import { Parameter, ToolSchema } from "@scoopika/types"; | ||
import Ajv from "ajv"; | ||
import new_error from "./error"; | ||
export function validateObject( | ||
schema: Record<string, Parameter>, | ||
required: string[], | ||
data: any, | ||
): | ||
| { success: false; error: string } | ||
| { success: true; data: Record<string, any> } { | ||
if (typeof data !== "object") { | ||
return { | ||
success: false, | ||
error: `Invalid data type received from LLM: ${typeof data}`, | ||
}; | ||
} | ||
let validated_data: Record<string, any> = JSON.parse(JSON.stringify(data)); | ||
for (const key of Object.keys(schema)) { | ||
const param = schema[key]; | ||
if (param.type === "object") { | ||
const nested = validateObject( | ||
param.properties, | ||
param.required || [], | ||
validated_data[key] || {}, | ||
); | ||
if (!nested.success) { | ||
return nested; | ||
} | ||
validated_data = { ...validated_data, [key]: nested.data }; | ||
continue; | ||
} | ||
if ( | ||
param.default !== undefined && | ||
(validated_data[key] === undefined || validated_data[key] === null) | ||
) { | ||
validated_data[key] = param.default; | ||
} | ||
const is_required = param.required || required.indexOf(key) !== -1; | ||
if ( | ||
is_required && | ||
(validated_data[key] === undefined || validated_data[key] === null) | ||
) { | ||
return { | ||
success: false, | ||
error: `Missing required data: ${key}: ${param.description || "Unknown decsription"}`, | ||
}; | ||
} | ||
if (!param.enum || param.enum.length < 1) { | ||
continue; | ||
} | ||
if (param.enum.indexOf(data[key]) === -1) { | ||
const joined = param.enum.join(", "); | ||
return { | ||
success: false, | ||
error: `Invalid data for ${key}: ${param.description}. expected one of (${joined}), but instead got ${data[key]}`, | ||
}; | ||
} | ||
} | ||
return { success: true, data: validated_data }; | ||
} | ||
export default function validate( | ||
@@ -6,0 +73,0 @@ schema: ToolSchema["tool"]["function"]["parameters"], |
133
src/model.ts
import new_error from "./lib/error"; | ||
import { ToolRun } from "./tool"; | ||
import hosts from "./models/hosts"; | ||
import sleep from "./lib/sleep"; | ||
import * as types from "@scoopika/types"; | ||
@@ -9,3 +8,2 @@ | ||
public client: types.LLMClient; | ||
public prompt: types.Prompt; | ||
private host: types.LLMHost<any>; | ||
@@ -15,10 +13,11 @@ private tools: types.ToolSchema[] = []; | ||
private follow_up_history: any[] = []; | ||
private calls: types.LLMToolCall[] = []; | ||
public tools_history: { | ||
call: types.LLMToolCall; | ||
result: any; | ||
}[] = []; | ||
constructor( | ||
client: types.LLMClient, | ||
prompt?: types.Prompt, | ||
tools?: types.ToolSchema[], | ||
) { | ||
constructor(client: types.LLMClient, tools?: types.ToolSchema[]) { | ||
this.client = client; | ||
this.prompt = prompt || ({} as types.Prompt); | ||
const wanted_host = hosts[client.host]; | ||
@@ -34,2 +33,3 @@ if (!wanted_host) { | ||
} | ||
this.host = wanted_host; | ||
@@ -42,11 +42,23 @@ | ||
async baseRun( | ||
run_id: string, | ||
stream: types.StreamFunc, | ||
updateHistory: (history: types.LLMHistory) => undefined, | ||
inputs: types.LLMFunctionBaseInputs, | ||
chained: boolean, | ||
timeout?: number, | ||
execute_tools?: boolean, | ||
): Promise<types.LLMTextResponse> { | ||
async baseRun({ | ||
run_id, | ||
session_id, | ||
stream, | ||
onToolCall, | ||
onToolRes, | ||
updateHistory, | ||
inputs, | ||
execute_tools, | ||
onClientAction, | ||
}: { | ||
run_id: string; | ||
session_id: string; | ||
stream: types.StreamFunc; | ||
onToolCall: (call: types.LLMToolCall) => any; | ||
onToolRes: (tool: { call: types.LLMToolCall; result: any }) => any; | ||
updateHistory: (history: types.LLMHistory) => undefined; | ||
inputs: types.LLMFunctionBaseInputs; | ||
execute_tools?: boolean; | ||
onClientAction?: (action: types.ServerClientActionStream["data"]) => any; | ||
}): Promise<types.LLMTextResponse> { | ||
const messages = [ | ||
@@ -58,12 +70,6 @@ ...inputs.messages, | ||
const output = await this.host.text( | ||
chained ? this.prompt.variable_name : "main", | ||
run_id, | ||
this.client.client, | ||
stream, | ||
{ | ||
...inputs, | ||
messages, | ||
}, | ||
); | ||
const output = await this.host.text(run_id, this.client.client, stream, { | ||
...inputs, | ||
messages, | ||
}); | ||
@@ -83,2 +89,5 @@ if (output.type !== "text") { | ||
const calls_results: types.ToolHistory[] = []; | ||
if (output.tool_calls) { | ||
this.calls = [...this.calls, ...output.tool_calls]; | ||
} | ||
@@ -103,9 +112,37 @@ for (const tool_call of output.tool_calls) { | ||
try { | ||
JSON.parse(call_function.arguments); | ||
} catch { | ||
throw new Error( | ||
new_error( | ||
"invalid_json", | ||
"The LLM returned an invalid json object for the tool arguments", | ||
"LLM Run", | ||
), | ||
); | ||
} | ||
onToolCall(tool_call); | ||
const wanted_tool_schema = wanted_tools_schema[0]; | ||
const tool_run = new ToolRun( | ||
wanted_tool_schema, | ||
JSON.parse(call_function.arguments), | ||
); | ||
const tool_run = new ToolRun({ | ||
id: tool_call.id, | ||
run_id, | ||
session_id, | ||
tool: wanted_tool_schema, | ||
args: JSON.parse(call_function.arguments), | ||
clientSideHook: onClientAction, | ||
}); | ||
const execution = await tool_run.execute(); | ||
onToolRes({ | ||
call: tool_call, | ||
result: execution, | ||
}); | ||
this.tools_history.push({ | ||
call: tool_call, | ||
result: execution, | ||
}); | ||
calls_results.push({ | ||
@@ -115,3 +152,3 @@ role: "tool", | ||
name: call_function.name, | ||
content: execution.result, | ||
content: execution, | ||
}); | ||
@@ -131,2 +168,4 @@ } | ||
// Only used with Google Gemini, and running Google LLMs is not recommended anyways | ||
// So almost useless, just keep it for later | ||
if (output.follow_up_history) { | ||
@@ -137,17 +176,20 @@ this.follow_up_history = output.follow_up_history; | ||
if (calls_results.length > 0) { | ||
if (timeout) { | ||
await sleep(timeout); | ||
} | ||
return await this.baseRun( | ||
return await this.baseRun({ | ||
run_id, | ||
session_id, | ||
stream, | ||
onToolCall, | ||
onToolRes, | ||
updateHistory, | ||
inputs, | ||
chained, | ||
timeout, | ||
execute_tools, | ||
); | ||
onClientAction, | ||
}); | ||
} | ||
return output; | ||
return { | ||
...output, | ||
tool_calls: this.calls, | ||
tools_history: this.tools_history, | ||
}; | ||
} | ||
@@ -158,4 +200,5 @@ | ||
schema: types.ToolParameters, | ||
stream: types.StreamFunc, | ||
): Promise<types.LLMJsonResponse> { | ||
const response = this.host.json(this.client.client, inputs, schema); | ||
const response = this.host.json(this.client.client, inputs, schema, stream); | ||
@@ -165,4 +208,8 @@ return response; | ||
async imageRun(inputs: types.LLMFunctionImageInputs) { | ||
return await this.host.image(this.client.client, inputs); | ||
async imageRun( | ||
run_id: string, | ||
stream: types.StreamFunc, | ||
inputs: types.LLMFunctionImageInputs, | ||
) { | ||
return await this.host.image(run_id, this.client.client, stream, inputs); | ||
} | ||
@@ -169,0 +216,0 @@ } |
@@ -12,3 +12,2 @@ import * as types from "@scoopika/types"; | ||
text: async ( | ||
prompt_name: string, | ||
run_id: string, | ||
@@ -55,3 +54,11 @@ client: GoogleGenerativeAI, | ||
if (inputs.messages[inputs.messages.length - 1].role === "user") { | ||
input = inputs.messages[inputs.messages.length - 1].content; | ||
const latest = inputs.messages[inputs.messages.length - 1].content; | ||
if (typeof latest === "string") { | ||
input = latest; | ||
} else { | ||
const recent_text = latest.filter( | ||
(l) => l.type === "text", | ||
) as types.UserTextContent[]; | ||
input = recent_text.length > 0 ? recent_text[0].text : ""; | ||
} | ||
} else { | ||
@@ -77,3 +84,3 @@ input = [history[history.length - 1].parts[0]]; | ||
response_message += text; | ||
stream({ prompt_name, content: text, run_id }); | ||
stream({ type: "text", content: text, run_id }); | ||
} | ||
@@ -108,2 +115,3 @@ | ||
follow_up_history, | ||
tools_history: [], | ||
}; | ||
@@ -167,3 +175,3 @@ }, | ||
image: async (_client, _inputs: types.LLMFunctionImageInputs) => { | ||
image: async (...args: any) => { | ||
throw new Error( | ||
@@ -170,0 +178,0 @@ new_error( |
@@ -15,3 +15,2 @@ import OpenAI from "openai"; | ||
text: async ( | ||
prompt_name: string, | ||
run_id: string, | ||
@@ -23,3 +22,3 @@ client: OpenAI, | ||
const completion_inputs = setupInputs(inputs); | ||
const options_string = JSON.stringify(completion_inputs.options); | ||
const options = JSON.parse(JSON.stringify(completion_inputs.options)); | ||
delete completion_inputs.options; | ||
@@ -30,3 +29,3 @@ | ||
stream: true, | ||
...JSON.parse(options_string), | ||
...options, | ||
} as ChatCompletionCreateParamsStreaming); | ||
@@ -40,4 +39,4 @@ | ||
response_message += chunk.choices[0].delta.content; | ||
stream({ | ||
prompt_name, | ||
await stream({ | ||
type: "text", | ||
run_id, | ||
@@ -54,3 +53,3 @@ content: chunk.choices[0].delta.content, | ||
calls.map((call) => { | ||
for await (const call of calls) { | ||
const saved_call = tool_calls[call.index]; | ||
@@ -66,3 +65,3 @@ if (!saved_call) { | ||
}; | ||
return; | ||
continue; | ||
} | ||
@@ -75,3 +74,3 @@ | ||
if (!call.function) { | ||
return; | ||
continue; | ||
} | ||
@@ -92,3 +91,3 @@ | ||
} | ||
}); | ||
} | ||
} | ||
@@ -107,3 +106,3 @@ | ||
if (response_message.length === 0 && tool_calls.length === 0) { | ||
return openai.text(prompt_name, run_id, client, stream, { | ||
return openai.text(run_id, client, stream, { | ||
...inputs, | ||
@@ -114,3 +113,8 @@ tools: [], | ||
return { type: "text", content: response_message, tool_calls }; | ||
return { | ||
type: "text", | ||
content: response_message, | ||
tool_calls, | ||
tools_history: [], | ||
}; | ||
}, | ||
@@ -122,13 +126,8 @@ | ||
schema: types.ToolParameters, | ||
stream: types.StreamFunc, | ||
): Promise<types.LLMJsonResponse> => { | ||
const response = await openai.text( | ||
"", | ||
"json_mode", | ||
client, | ||
(_stream) => {}, | ||
{ | ||
...inputs, | ||
response_format: { type: "json_object", schema }, | ||
}, | ||
); | ||
const response = await openai.text("json_mode", client, () => {}, { | ||
...inputs, | ||
response_format: { type: "json_object", schema }, | ||
}); | ||
@@ -160,5 +159,7 @@ if (!response.content || response.content.length < 1) { | ||
image: async ( | ||
run_id: string, | ||
client: OpenAI, | ||
stream: types.StreamFunc, | ||
inputs: types.LLMFunctionImageInputs, | ||
): Promise<types.LLMResponse> => { | ||
): Promise<types.LLMImageResponse> => { | ||
const response = await client.images.generate({ | ||
@@ -173,7 +174,22 @@ model: inputs.model, | ||
const images_url: string[] = []; | ||
images.map((image) => { | ||
const image_url = image.url; | ||
const raw = image.b64_json; | ||
if (!image_url && !raw) { | ||
return; | ||
} | ||
if (typeof image_url === "string") { | ||
images_url.push(image_url); | ||
} else if (typeof raw === "string") { | ||
images_url.push(raw); | ||
} | ||
stream({ | ||
type: "image", | ||
content: image_url || raw || "", | ||
run_id, | ||
}); | ||
}); | ||
@@ -180,0 +196,0 @@ |
@@ -1,335 +0,353 @@ | ||
import buildPrompt from "./lib/build_prompt"; | ||
import Model from "./model"; | ||
import mixHistory from "./lib/mix_history"; | ||
import new_error from "./lib/error"; | ||
import promptToTool from "./lib/prompt_to_tool"; | ||
import { sleep } from "openai/core"; | ||
import * as types from "@scoopika/types"; | ||
class PromptChain { | ||
public saved_prompts: Record<string, string> = {}; | ||
private clients: types.LLMClient[]; | ||
private prompts: types.Prompt[]; | ||
private tools: types.ToolSchema[]; | ||
private session: types.StoreSession; | ||
private stream: types.StreamFunc; | ||
private statusUpdate: types.StatusUpdateFunc; | ||
private agent: types.AgentData; | ||
public running_prompt: types.Prompt | undefined = undefined; | ||
constructor({ | ||
session, | ||
agent, | ||
clients, | ||
stream, | ||
statusUpdate, | ||
prompts, | ||
tools, | ||
saved_prompts, | ||
}: { | ||
session: types.StoreSession; | ||
agent: types.AgentData; | ||
clients: types.LLMClient[]; | ||
stream: types.StreamFunc; | ||
statusUpdate: types.StatusUpdateFunc; | ||
prompts: types.Prompt[]; | ||
tools: types.ToolSchema[]; | ||
saved_prompts?: Record<string, string>; | ||
}) { | ||
this.session = session; | ||
this.agent = agent; | ||
this.clients = clients; | ||
this.stream = stream; | ||
this.statusUpdate = statusUpdate; | ||
this.prompts = prompts; | ||
this.tools = tools; | ||
if (saved_prompts) { | ||
this.saved_prompts = saved_prompts; | ||
} | ||
} | ||
async run({ | ||
run_id, | ||
inputs, | ||
messages, | ||
wanted_responses, | ||
timeout, | ||
onPromptResponse, | ||
}: { | ||
run_id: string; | ||
inputs: types.Inputs; | ||
messages: types.LLMHistory[]; | ||
wanted_responses?: string[]; | ||
timeout?: number; | ||
onPromptResponse?: (response: types.LLMResponse) => any; | ||
}): Promise<types.AgentInnerRunResult> { | ||
const prompts = this.prompts.sort((a, b) => a.index - b.index); | ||
const responses: Record<string, types.LLMResponse> = {}; | ||
const updated_history: types.LLMHistory[] = []; | ||
let running_prompt: string = ""; | ||
const updateHistory = (new_history: types.LLMHistory): undefined => { | ||
if (this.agent.chained) { | ||
updated_history.push({ | ||
...new_history, | ||
name: running_prompt, | ||
role: "prompt", | ||
}); | ||
return; | ||
} | ||
updated_history.push({ ...new_history, name: this.agent.name }); | ||
}; | ||
for await (const prompt of prompts) { | ||
running_prompt = prompt.variable_name; | ||
const { client, validated_prompt } = await this.setupPrompt( | ||
prompt, | ||
inputs, | ||
messages, | ||
); | ||
const model = new Model(client, prompt, this.tools); | ||
const llmOutput = await this.runPrompt( | ||
run_id, | ||
model, | ||
updateHistory, | ||
validated_prompt, | ||
messages, | ||
timeout, | ||
); | ||
inputs[prompt.variable_name] = llmOutput.content as types.Input; | ||
responses[prompt.variable_name] = llmOutput; | ||
if (llmOutput.type === "text") { | ||
updateHistory({ role: "model", content: llmOutput.content }); | ||
} | ||
if (onPromptResponse) { | ||
onPromptResponse(llmOutput); | ||
} | ||
if (timeout) { | ||
await sleep(timeout); | ||
} | ||
} | ||
const mixed_history: types.LLMHistory[] = [ | ||
{ | ||
role: "user", | ||
content: | ||
"Previous conversation context:\n" + mixHistory(updated_history), | ||
}, | ||
]; | ||
if (!wanted_responses || wanted_responses.length < 1) { | ||
return { responses: responses, updated_history: mixed_history }; | ||
} | ||
let results: Record<string, types.LLMResponse> = {}; | ||
wanted_responses.map((wanted_response) => { | ||
const response = responses[wanted_response]; | ||
if (!response) { | ||
throw new Error( | ||
new_error( | ||
"invalid_wanted_response", | ||
`The wanted response ${wanted_response} is not available`, | ||
"prompt chain results", | ||
), | ||
); | ||
} | ||
results[wanted_response] = response; | ||
}); | ||
return { responses: results, updated_history: mixed_history }; | ||
} | ||
async runPrompt( | ||
run_id: string, | ||
model: Model, | ||
updateHistory: (history: types.LLMHistory) => undefined, | ||
validated_prompt: string, | ||
messages: types.LLMHistory[], | ||
timeout?: number, | ||
): Promise<types.LLMResponse> { | ||
const prompt_type = model.prompt.type; | ||
if (prompt_type === "image") { | ||
return model.imageRun({ | ||
prompt: validated_prompt, | ||
n: model.prompt.n, | ||
size: model.prompt.size, | ||
model: model.prompt.model, | ||
}); | ||
} | ||
if (prompt_type === "json") { | ||
const json = this.jsonMode( | ||
model.client, | ||
model.prompt, | ||
model.prompt.inputs, | ||
messages, | ||
); | ||
return { type: "object", content: json }; | ||
} | ||
return model.baseRun( | ||
run_id, | ||
this.stream, | ||
updateHistory, | ||
{ | ||
model: model.prompt.model, | ||
options: model.prompt.options, | ||
messages: [{ role: "system", content: validated_prompt }, ...messages], | ||
tools: this.tools.map((tool) => tool.tool), | ||
tool_choice: model.prompt.tool_choice, | ||
}, | ||
this.agent.chained, | ||
timeout, | ||
); | ||
} | ||
async setupPrompt( | ||
prompt: types.Prompt, | ||
inputs: types.Inputs, | ||
messages: types.LLMHistory[], | ||
): Promise<{ | ||
client: types.LLMClient; | ||
validated_prompt: string; | ||
}> { | ||
const wanted_clients = this.clients.filter( | ||
(client) => client.host === prompt.llm_client, | ||
); | ||
if (wanted_clients.length < 1) { | ||
throw new Error( | ||
new_error( | ||
"no_client", | ||
`Client '${prompt.llm_client}' not found for the prompt '${prompt.variable_name}'`, | ||
"prompt chain run", | ||
), | ||
); | ||
} | ||
const client = wanted_clients[0]; | ||
const built_prompt: types.BuiltPrompt = buildPrompt(prompt, inputs); | ||
let validated_prompt = await this.validatePrompt( | ||
prompt, | ||
built_prompt, | ||
inputs.message, | ||
messages, | ||
); | ||
if (prompt.conversational !== false) { | ||
this.saved_prompts[prompt.variable_name] = validated_prompt; | ||
} | ||
if (!this.agent.chained) { | ||
validated_prompt = `You are ${this.agent.name}, ` + validated_prompt; | ||
} | ||
return { client, validated_prompt }; | ||
} | ||
async validatePrompt( | ||
prompt: types.Prompt, | ||
built: types.BuiltPrompt, | ||
inputText: types.Input | undefined, | ||
messages: types.LLMHistory[], | ||
): Promise<string> { | ||
const json_mode = typeof inputText === "string" ? true : false; | ||
const missing = built.missing; | ||
if (missing.length === 0) { | ||
return built.content; | ||
} | ||
if ( | ||
prompt.conversational !== false && | ||
this.saved_prompts[prompt.variable_name] | ||
) { | ||
const saved_prompt = this.saved_prompts[prompt.variable_name]; | ||
return saved_prompt; | ||
} | ||
if (!json_mode) { | ||
const missingIds: string[] = built.missing.map((mis) => mis.id); | ||
throw new Error( | ||
new_error( | ||
"missing_inputs", | ||
`Missing inputs in prompt '${prompt.variable_name}': ${missingIds.join(",")}`, | ||
"prompt validation", | ||
), | ||
); | ||
} | ||
const wanted_clients = this.clients.filter( | ||
(client) => client.host === prompt.llm_client, | ||
); | ||
if (wanted_clients.length < 1) { | ||
throw new Error( | ||
new_error( | ||
"invalid_llm_client", | ||
`The wanted client ${prompt.llm_client} is not available`, | ||
"Json mode", | ||
), | ||
); | ||
} | ||
const original_missing = JSON.stringify(missing); | ||
const extracted_inputs = await this.jsonMode( | ||
wanted_clients[0], | ||
prompt, | ||
[...missing], | ||
messages, | ||
); | ||
const new_built_prompt = buildPrompt( | ||
{ ...prompt, inputs: JSON.parse(original_missing) }, | ||
extracted_inputs, | ||
); | ||
if (new_built_prompt.missing.length > 0) { | ||
throw new Error( | ||
new_error( | ||
"missing_inputs", | ||
`Can't extract all required inputs for the prompt '${prompt.variable_name}'`, | ||
"prompt validation", | ||
), | ||
); | ||
} | ||
return new_built_prompt.content; | ||
} | ||
async jsonMode( | ||
client: types.LLMClient, | ||
prompt: types.Prompt, | ||
inputs: types.PromptInput[], | ||
messages: types.LLMHistory[], | ||
): Promise<types.Inputs> { | ||
const model = new Model(client, prompt, this.tools); | ||
const response = await model.jsonRun( | ||
{ | ||
model: prompt.model, | ||
tools: [], | ||
options: prompt.options, | ||
messages: [ | ||
{ | ||
role: "system", | ||
content: "Your role is to extract JSON data from the context.", | ||
}, | ||
...messages, | ||
], | ||
}, | ||
promptToTool(prompt, inputs).tool.function.parameters, | ||
); | ||
return response.content as types.Inputs; | ||
} | ||
} | ||
export default PromptChain; | ||
// import buildPrompt from "./lib/build_prompt"; | ||
// import Model from "./model"; | ||
// import mixHistory from "./lib/mix_history"; | ||
// import new_error from "./lib/error"; | ||
// import promptToTool from "./lib/prompt_to_tool"; | ||
// import { sleep } from "openai/core"; | ||
// import * as types from "@scoopika/types"; | ||
// | ||
// class PromptChain { | ||
// public saved_prompts: Record<string, string> = {}; | ||
// private clients: types.LLMClient[]; | ||
// private prompts: types.Prompt[]; | ||
// private tools: types.ToolSchema[]; | ||
// private session: types.StoreSession; | ||
// private stream: types.StreamFunc; | ||
// private toolCallStream: (call: types.LLMToolCall) => any; | ||
// private toolResStream: (tool: { | ||
// call: types.LLMToolCall; | ||
// result: any; | ||
// }) => any; | ||
// private statusUpdate: types.StatusUpdateFunc; | ||
// private agent: types.AgentData; | ||
// public running_prompt: types.Prompt | undefined = undefined; | ||
// public tools_history: { | ||
// call: types.LLMToolCall; | ||
// result: any; | ||
// }[] = []; | ||
// | ||
// constructor({ | ||
// session, | ||
// agent, | ||
// clients, | ||
// stream, | ||
// toolCallStream, | ||
// toolResStream, | ||
// statusUpdate, | ||
// prompts, | ||
// tools, | ||
// saved_prompts, | ||
// }: { | ||
// session: types.StoreSession; | ||
// agent: types.AgentData; | ||
// clients: types.LLMClient[]; | ||
// stream: types.StreamFunc; | ||
// toolCallStream: (call: types.LLMToolCall) => any; | ||
// toolResStream: (tool: { call: types.LLMToolCall; result: any }) => any; | ||
// statusUpdate: types.StatusUpdateFunc; | ||
// prompts: types.Prompt[]; | ||
// tools: types.ToolSchema[]; | ||
// saved_prompts?: Record<string, string>; | ||
// }) { | ||
// this.session = session; | ||
// this.agent = agent; | ||
// this.clients = clients; | ||
// this.stream = stream; | ||
// this.toolCallStream = toolCallStream; | ||
// this.toolResStream = toolResStream; | ||
// this.statusUpdate = statusUpdate; | ||
// this.prompts = prompts; | ||
// this.tools = tools; | ||
// | ||
// if (saved_prompts) { | ||
// this.saved_prompts = saved_prompts; | ||
// } | ||
// } | ||
// | ||
// async run({ | ||
// run_id, | ||
// inputs, | ||
// messages, | ||
// timeout, | ||
// onPromptResponse, | ||
// }: { | ||
// run_id: string; | ||
// inputs: types.Inputs; | ||
// messages: types.LLMHistory[]; | ||
// timeout?: number; | ||
// onPromptResponse?: (response: types.LLMResponse) => any; | ||
// }): Promise<types.AgentInnerRunResult> { | ||
// const prompts = this.prompts.sort((a, b) => a.index - b.index); | ||
// const responses: { | ||
// prompt_name: string; | ||
// response: types.LLMResponse; | ||
// }[] = []; | ||
// const updated_history: types.LLMHistory[] = []; | ||
// const calls: types.ToolHistory[] = []; | ||
// let running_prompt: string = ""; | ||
// | ||
// const updateHistory = (new_history: types.LLMHistory): undefined => { | ||
// if (new_history.role === "tool") { | ||
// calls.push(new_history); | ||
// return; | ||
// } | ||
// | ||
// if (this.agent.chained) { | ||
// updated_history.push({ | ||
// ...new_history, | ||
// name: running_prompt, | ||
// role: "prompt", | ||
// }); | ||
// return; | ||
// } | ||
// | ||
// updated_history.push({ ...new_history, name: this.agent.name }); | ||
// }; | ||
// | ||
// for await (const prompt of prompts) { | ||
// running_prompt = prompt.variable_name; | ||
// let { client, validated_prompt } = await this.setupPrompt( | ||
// prompt, | ||
// inputs, | ||
// messages, | ||
// ); | ||
// | ||
// if (!this.agent.chained) { | ||
// validated_prompt = | ||
// `You are called ${this.agent.name}` + validated_prompt; | ||
// } | ||
// | ||
// const model = new Model(client, prompt, this.tools); | ||
// const llmOutput = await this.runPrompt( | ||
// run_id, | ||
// model, | ||
// updateHistory, | ||
// validated_prompt, | ||
// messages, | ||
// timeout, | ||
// ); | ||
// | ||
// inputs[prompt.variable_name] = llmOutput.content as types.Input; | ||
// responses.push({ | ||
// prompt_name: prompt.variable_name, | ||
// response: llmOutput, | ||
// }); | ||
// | ||
// if (llmOutput.type === "text") { | ||
// this.tools_history = [...this.tools_history, ...model.tools_history]; | ||
// updateHistory({ role: "model", content: llmOutput.content }); | ||
// } | ||
// | ||
// if (onPromptResponse) { | ||
// onPromptResponse(llmOutput); | ||
// } | ||
// | ||
// if (timeout) { | ||
// await sleep(timeout); | ||
// } | ||
// } | ||
// | ||
// const mixed_history: types.LLMHistory[] = [ | ||
// ...calls, | ||
// { | ||
// role: "user", | ||
// content: "Conversation context:\n" + mixHistory(updated_history), | ||
// }, | ||
// ]; | ||
// | ||
// return { | ||
// responses, | ||
// updated_history: mixed_history, | ||
// runs: [], | ||
// tools_history: [], | ||
// }; | ||
// } | ||
// | ||
// async runPrompt( | ||
// run_id: string, | ||
// model: Model, | ||
// updateHistory: (history: types.LLMHistory) => undefined, | ||
// validated_prompt: string, | ||
// messages: types.LLMHistory[], | ||
// timeout?: number, | ||
// ): Promise<types.LLMResponse> { | ||
// const prompt_type = model.prompt.type; | ||
// | ||
// if (prompt_type === "image") { | ||
// return model.imageRun(run_id, this.stream, { | ||
// prompt: validated_prompt, | ||
// n: model.prompt.n, | ||
// size: model.prompt.size, | ||
// model: model.prompt.model, | ||
// }); | ||
// } | ||
// | ||
// if (prompt_type === "json") { | ||
// const json = this.jsonMode( | ||
// model.client, | ||
// model.prompt, | ||
// model.prompt.inputs, | ||
// messages, | ||
// ); | ||
// return { type: "object", content: json }; | ||
// } | ||
// | ||
// return model.baseRun( | ||
// run_id, | ||
// this.stream, | ||
// this.toolCallStream, | ||
// this.toolResStream, | ||
// updateHistory, | ||
// { | ||
// model: model.prompt.model, | ||
// options: model.prompt.options, | ||
// messages: [{ role: "system", content: validated_prompt }, ...messages], | ||
// tools: this.tools.map((tool) => tool.tool), | ||
// tool_choice: model.prompt.tool_choice, | ||
// }, | ||
// this.agent.chained, | ||
// timeout, | ||
// ); | ||
// } | ||
// | ||
// async setupPrompt( | ||
// prompt: types.Prompt, | ||
// inputs: types.Inputs, | ||
// messages: types.LLMHistory[], | ||
// ): Promise<{ | ||
// client: types.LLMClient; | ||
// validated_prompt: string; | ||
// }> { | ||
// const wanted_clients = this.clients.filter( | ||
// (client) => client.host === prompt.llm_client, | ||
// ); | ||
// if (wanted_clients.length < 1) { | ||
// throw new Error( | ||
// new_error( | ||
// "no_client", | ||
// `Client '${prompt.llm_client}' not found for the prompt '${prompt.variable_name}'`, | ||
// "prompt chain run", | ||
// ), | ||
// ); | ||
// } | ||
// | ||
// const client = wanted_clients[0]; | ||
// const built_prompt: types.BuiltPrompt = buildPrompt(prompt, inputs); | ||
// | ||
// let validated_prompt = await this.validatePrompt( | ||
// prompt, | ||
// built_prompt, | ||
// inputs.message, | ||
// messages, | ||
// ); | ||
// | ||
// if (prompt.conversational !== false) { | ||
// this.saved_prompts[prompt.variable_name] = validated_prompt; | ||
// } | ||
// | ||
// if (!this.agent.chained) { | ||
// validated_prompt = `You are ${this.agent.name}, ` + validated_prompt; | ||
// } | ||
// | ||
// return { client, validated_prompt }; | ||
// } | ||
// | ||
// async validatePrompt( | ||
// prompt: types.Prompt, | ||
// built: types.BuiltPrompt, | ||
// inputText: types.Input | undefined, | ||
// messages: types.LLMHistory[], | ||
// ): Promise<string> { | ||
// const json_mode = typeof inputText === "string" ? true : false; | ||
// const missing = built.missing; | ||
// if (missing.length === 0) { | ||
// return built.content; | ||
// } | ||
// | ||
// if ( | ||
// prompt.conversational !== false && | ||
// this.saved_prompts[prompt.variable_name] | ||
// ) { | ||
// const saved_prompt = this.saved_prompts[prompt.variable_name]; | ||
// return saved_prompt; | ||
// } | ||
// | ||
// if (!json_mode) { | ||
// const missingIds: string[] = built.missing.map((mis) => mis.id); | ||
// throw new Error( | ||
// new_error( | ||
// "missing_inputs", | ||
// `Missing inputs in prompt '${prompt.variable_name}': ${missingIds.join(",")}`, | ||
// "prompt validation", | ||
// ), | ||
// ); | ||
// } | ||
// | ||
// const wanted_clients = this.clients.filter( | ||
// (client) => client.host === prompt.llm_client, | ||
// ); | ||
// | ||
// if (wanted_clients.length < 1) { | ||
// throw new Error( | ||
// new_error( | ||
// "invalid_llm_client", | ||
// `The wanted client ${prompt.llm_client} is not available`, | ||
// "Json mode", | ||
// ), | ||
// ); | ||
// } | ||
// | ||
// const original_missing = JSON.stringify(missing); | ||
// const extracted_inputs = await this.jsonMode( | ||
// wanted_clients[0], | ||
// prompt, | ||
// [...missing], | ||
// messages, | ||
// ); | ||
// | ||
// const new_built_prompt = buildPrompt( | ||
// { ...prompt, inputs: JSON.parse(original_missing) }, | ||
// extracted_inputs, | ||
// ); | ||
// | ||
// if (new_built_prompt.missing.length > 0) { | ||
// throw new Error( | ||
// new_error( | ||
// "missing_inputs", | ||
// `Can't extract all required inputs for the prompt '${prompt.variable_name}'`, | ||
// "prompt validation", | ||
// ), | ||
// ); | ||
// } | ||
// | ||
// return new_built_prompt.content; | ||
// } | ||
// | ||
// async jsonMode( | ||
// client: types.LLMClient, | ||
// prompt: types.Prompt, | ||
// inputs: types.PromptInput[], | ||
// messages: types.LLMHistory[], | ||
// ): Promise<types.Inputs> { | ||
// const model = new Model(client, prompt, this.tools); | ||
// const response = await model.jsonRun( | ||
// { | ||
// model: prompt.model, | ||
// tools: [], | ||
// options: prompt.options, | ||
// messages: [ | ||
// { | ||
// role: "system", | ||
// content: "Your role is to extract JSON data from the context.", | ||
// }, | ||
// ...messages, | ||
// ], | ||
// }, | ||
// promptToTool(prompt, inputs).tool.function.parameters, | ||
// ); | ||
// | ||
// return response.content as types.Inputs; | ||
// } | ||
// } | ||
// | ||
// export default PromptChain; |
@@ -0,1 +1,2 @@ | ||
import RemoteStore from "./remote_store"; | ||
import StateStore from "./state"; | ||
@@ -9,8 +10,11 @@ import InMemoryStore from "./store"; | ||
private token: string; | ||
public store: InMemoryStore; | ||
public store: InMemoryStore | RemoteStore; | ||
public memoryStore: InMemoryStore; | ||
public engines: types.RawEngines | undefined = {}; | ||
private loadedSessions: Record<string, types.StoreSession> = {}; | ||
public stateStore: StateStore; | ||
// Will be used soon for caching somehow | ||
public loaded_agents: Record<string, types.AgentData> = {}; | ||
public loaded_boxes: Record<string, types.BoxData> = {}; | ||
constructor({ | ||
@@ -22,3 +26,3 @@ token, | ||
token: string; | ||
store?: "memory" | string | InMemoryStore; | ||
store?: "memory" | string | InMemoryStore | RemoteStore; | ||
engines?: types.RawEngines; | ||
@@ -32,9 +36,17 @@ }) { | ||
if (!store) { | ||
store = new InMemoryStore(); | ||
} | ||
if (store === "memory") { | ||
this.store = new InMemoryStore(); | ||
store = new InMemoryStore(); | ||
} else if (typeof store === "string") { | ||
store = new RemoteStore(token, store); | ||
} | ||
this.store = new InMemoryStore(); | ||
this.store = store; | ||
} | ||
// Sessions | ||
public async getSession( | ||
@@ -44,8 +56,2 @@ id: string, | ||
): Promise<types.StoreSession> { | ||
const loaded = this.loadedSessions[id]; | ||
if (loaded) { | ||
return loaded; | ||
} | ||
let session = await this.store.getSession(id); | ||
@@ -61,4 +67,2 @@ | ||
this.loadedSessions[id] = session; | ||
return session; | ||
@@ -70,21 +74,51 @@ } | ||
user_name, | ||
user_id, | ||
}: { | ||
id: string; | ||
id?: string; | ||
user_name?: string; | ||
user_id?: string; | ||
}): Promise<types.StoreSession> { | ||
const session_id = id || "session_" + crypto.randomUUID(); | ||
await this.store.newSession(session_id, user_name); | ||
this.loadedSessions[session_id] = { | ||
id: session_id, | ||
user_name, | ||
saved_prompts: {}, | ||
}; | ||
await this.store.newSession({ id: session_id, user_id, user_name }); | ||
this.stateStore.setState(session_id, 0); | ||
return { id, user_name, saved_prompts: {} }; | ||
return { id: session_id, user_name, user_id, saved_prompts: {} }; | ||
} | ||
public async deleteSession(id: string) { | ||
await this.store.deleteSession(id); | ||
return this; | ||
} | ||
public async pushRuns( | ||
session: types.StoreSession | string, | ||
runs: types.RunHistory[], | ||
) { | ||
await this.store.batchPushRuns(session, runs); | ||
} | ||
public async listUserSessions(user_id: string): Promise<string[]> { | ||
const sessions = await this.store.getUserSessions(user_id); | ||
return sessions; | ||
} | ||
public async getSessionRuns( | ||
session: types.StoreSession | string, | ||
): Promise<types.RunHistory[]> { | ||
const runs = await this.store.getRuns(session); | ||
return runs; | ||
} | ||
public async getSessionHistory( | ||
session: types.StoreSession | string, | ||
): Promise<types.LLMHistory[]> { | ||
const history = await this.store.getHistory(session); | ||
return history; | ||
} | ||
// Loading | ||
public async loadAgent(id: string): Promise<types.AgentData> { | ||
const res = await fetch(this.url + `/agent/${id}`, { | ||
const res = await fetch(this.url + `/main/agent/${id}`, { | ||
method: "GET", | ||
@@ -97,4 +131,8 @@ headers: { | ||
const status = res.status; | ||
const data = await res.json(); | ||
const data = (await res.json()) as any; | ||
if (!data.success) { | ||
throw new Error(`Remote server error: ${data.error || "Unknown error"}`); | ||
} | ||
if (status !== 200) { | ||
@@ -108,2 +146,3 @@ throw new Error(data.error || `Server error, status: ${status}`); | ||
this.loaded_agents[id] = data.agent as types.AgentData; | ||
return data.agent as types.AgentData; | ||
@@ -113,3 +152,3 @@ } | ||
public async loadBox(id: string): Promise<types.BoxData> { | ||
const res = await fetch(this.url + `/box/${id}`, { | ||
const res = await fetch(this.url + `/main/box/${id}`, { | ||
method: "GET", | ||
@@ -122,3 +161,3 @@ headers: { | ||
const status = res.status; | ||
const data = await res.json(); | ||
const data = (await res.json()) as any; | ||
@@ -133,2 +172,3 @@ if (status !== 200) { | ||
this.loaded_boxes[id] = data.box as types.BoxData; | ||
return data.box as types.BoxData; | ||
@@ -135,0 +175,0 @@ } |
101
src/store.ts
@@ -1,3 +0,3 @@ | ||
import new_error from "./lib/error"; | ||
import { LLMHistory, Store, StoreSession } from "@scoopika/types"; | ||
import { LLMHistory, RunHistory, Store, StoreSession } from "@scoopika/types"; | ||
import crypto from "node:crypto"; | ||
@@ -7,28 +7,57 @@ class InMemoryStore implements Store { | ||
public sessions: Record<string, StoreSession> = {}; | ||
public users_sessions: Record<string, string[]> = {}; | ||
public runs: Record<string, RunHistory[]> = {}; | ||
constructor() {} | ||
checkSession(session: StoreSession): undefined { | ||
const sessions = this.sessions[session.id]; | ||
if (!sessions) { | ||
throw new Error( | ||
new_error( | ||
"session_notfound", | ||
`The session '${session.id}' does not exist`, | ||
"session check", | ||
), | ||
); | ||
async newSession({ | ||
id, | ||
user_id, | ||
user_name, | ||
}: { | ||
id?: string; | ||
user_id?: string; | ||
user_name?: string; | ||
}) { | ||
const session_id = id || "session_" + crypto.randomUUID(); | ||
this.sessions[session_id] = { | ||
id: session_id, | ||
user_id, | ||
user_name, | ||
saved_prompts: {}, | ||
}; | ||
this.history[session_id] = []; | ||
this.runs[session_id] = []; | ||
if (!user_id) { | ||
return; | ||
} | ||
} | ||
async newSession(id: string, user_name?: string) { | ||
this.sessions[id] = { id, user_name, saved_prompts: {} }; | ||
this.history[id] = []; | ||
if (!this.users_sessions[user_id]) { | ||
this.users_sessions[user_id] = []; | ||
} | ||
this.users_sessions[user_id].push(session_id); | ||
} | ||
async getSession(id: string): Promise<StoreSession | undefined> { | ||
const wanted_sessions = this.sessions[id]; | ||
return wanted_sessions; | ||
const wanted_session = this.sessions[id]; | ||
return wanted_session; | ||
} | ||
async deleteSession(id: string) { | ||
if (this.sessions[id]) { | ||
delete this.sessions[id]; | ||
} | ||
if (this.history[id]) { | ||
delete this.history[id]; | ||
} | ||
} | ||
async getUserSessions(user_id: string): Promise<string[]> { | ||
return this.users_sessions[user_id] || []; | ||
} | ||
async updateSession( | ||
@@ -41,3 +70,3 @@ id: string, | ||
) { | ||
const session = await this.getSession(id); | ||
const session = this.sessions[id]; | ||
@@ -52,7 +81,6 @@ if (!session) { | ||
async getHistory(session: StoreSession): Promise<LLMHistory[]> { | ||
this.checkSession(session); | ||
async getHistory(session: StoreSession | string): Promise<LLMHistory[]> { | ||
const id = typeof session === "string" ? session : session.id; | ||
const history = this.history[id]; | ||
const history = this.history[session.id]; | ||
if (!history || history.length < 1) { | ||
@@ -67,9 +95,10 @@ return []; | ||
async pushHistory( | ||
session: StoreSession, | ||
session: StoreSession | string, | ||
new_history: LLMHistory, | ||
): Promise<void> { | ||
if (!this.history[session.id]) { | ||
this.history[session.id] = []; | ||
const id = typeof session === "string" ? session : session.id; | ||
if (!this.history[id]) { | ||
this.history[id] = []; | ||
} | ||
this.history[session.id].push(new_history); | ||
this.history[id].push(new_history); | ||
} | ||
@@ -85,4 +114,22 @@ | ||
} | ||
async pushRun(session: StoreSession | string, run: RunHistory) { | ||
const id = typeof session === "string" ? session : session.id; | ||
if (!this.runs[id]) { | ||
this.runs[id] = []; | ||
} | ||
this.runs[id].push(run); | ||
} | ||
async batchPushRuns(session: StoreSession | string, runs: RunHistory[]) { | ||
runs.forEach((r) => this.pushRun(session, r)); | ||
} | ||
async getRuns(session: StoreSession | string): Promise<RunHistory[]> { | ||
const id = typeof session === "string" ? session : session.id; | ||
return this.runs[id] || []; | ||
} | ||
} | ||
export default InMemoryStore; |
106
src/tool.ts
@@ -1,12 +0,43 @@ | ||
import { ToolSchema, FunctionToolSchema, ApiToolSchema } from "@scoopika/types"; | ||
import validate from "./lib/validate"; | ||
import { | ||
ToolSchema, | ||
FunctionToolSchema, | ||
ApiToolSchema, | ||
ServerClientActionStream, | ||
AgentToolSchema, | ||
} from "@scoopika/types"; | ||
import validate, { validateObject } from "./lib/validate"; | ||
class ToolRun { | ||
id: string; | ||
run_id: string; | ||
session_id: string; | ||
tool: ToolSchema; | ||
args: Record<string, any>; | ||
clientSideHook?: (action: ServerClientActionStream["data"]) => any; | ||
errorHook?: (e: { healed: boolean; error: string }) => any; | ||
constructor(tool: ToolSchema, args: Record<string, any>) { | ||
validate(tool.tool.function.parameters, args); | ||
constructor({ | ||
id, | ||
run_id, | ||
session_id, | ||
tool, | ||
args, | ||
clientSideHook, | ||
errorHook, | ||
}: { | ||
id: string; | ||
run_id: string; | ||
session_id: string; | ||
tool: ToolSchema; | ||
args: Record<string, any>; | ||
clientSideHook?: (action: ServerClientActionStream["data"]) => any; | ||
errorHook?: (e: { healed: boolean; error: string }) => any; | ||
}) { | ||
this.id = id; | ||
this.run_id = run_id; | ||
this.session_id = session_id; | ||
this.tool = tool; | ||
this.args = args; | ||
this.clientSideHook = clientSideHook; | ||
this.errorHook = errorHook; | ||
} | ||
@@ -20,6 +51,20 @@ | ||
return JSON.stringify({ result }); | ||
return `${result}`; | ||
} | ||
async execute(): Promise<{ result: string }> { | ||
async execute(): Promise<any> { | ||
const parameters = this.tool.tool.function.parameters; | ||
const validated_args = validateObject( | ||
parameters.properties, | ||
parameters.required || [], | ||
this.args, | ||
); | ||
if (!validated_args.success) { | ||
return `ERROR: ${validated_args.error}`; | ||
} | ||
validate(parameters, validated_args.data); | ||
this.args = validated_args.data; | ||
if (this.tool.type === "function") { | ||
@@ -33,10 +78,51 @@ return await this.executeFunction(this.tool); | ||
return { result: this.toolResult("Invalid tool execution") }; | ||
if (this.tool.type === "agent") { | ||
return await this.executeAgent(this.tool); | ||
} | ||
if (this.tool.type !== "client-side") { | ||
throw new Error("ERROR: Unknown tool type"); | ||
} | ||
if (!this.clientSideHook) { | ||
throw new Error( | ||
"Needed to execute a tool on the client side but no hooks are found", | ||
); | ||
} | ||
await this.clientSideHook({ | ||
id: this.id, | ||
tool_name: this.tool.tool.function.name, | ||
arguments: validated_args.data, | ||
}); | ||
return `Executed the action ${this.tool.tool.function.name} successfully, keep the conversation going and inform the user that the action was executed`; | ||
} | ||
async executeFunction(tool: FunctionToolSchema): Promise<{ result: string }> { | ||
let result = await tool.executor(this.args); | ||
return { result: this.toolResult(result) }; | ||
async executeAgent(tool: AgentToolSchema): Promise<string> { | ||
if (typeof this.args.instructions !== "string") { | ||
throw new Error( | ||
"Invalid instructions sent to an agent running as a tool", | ||
); | ||
} | ||
const result = await tool.executor( | ||
this.session_id, | ||
this.run_id, | ||
this.args.instructions, | ||
); | ||
return result; | ||
} | ||
async executeFunction(tool: FunctionToolSchema): Promise<string> { | ||
try { | ||
const result = await tool.executor(this.args); | ||
return this.toolResult({ result }); | ||
} catch (err: any) { | ||
const error: string = err.message || "Unexpected error!"; | ||
return `The tool ${tool.tool.function.name} faced an error: ${error}`; | ||
} | ||
} | ||
async executeApi(tool: ApiToolSchema): Promise<{ result: string }> { | ||
@@ -43,0 +129,0 @@ const inputs: { |
159
test_box.ts
@@ -1,8 +0,12 @@ | ||
import { AgentData } from "@scoopika/types"; | ||
import { AgentData, BoxHooks } from "@scoopika/types"; | ||
import Box from "./src/box"; | ||
import Client from "./src/client"; | ||
import Client from "./src/scoopika"; | ||
import crypto from "node:crypto"; | ||
import { createToolSchema } from "./src/create_tool"; | ||
import { FromSchema, JSONSchema } from "json-schema-to-ts"; | ||
import readline from "node:readline"; | ||
const client = new Client({ | ||
token: "token123", | ||
store: "memory", | ||
store: "http://127.0.0.1:8000", // Local data store url | ||
engines: { | ||
@@ -17,3 +21,3 @@ fireworks: process.env["FIREWORKS_API"], | ||
name: "Jack", | ||
description: "Writes 3 main keywords about a research topic", | ||
description: "Helps with writing detailed reports", | ||
chained: false, | ||
@@ -30,41 +34,27 @@ tools: [], | ||
options: {}, | ||
inputs: [ | ||
{ | ||
type: "string", | ||
id: "topic", | ||
description: "The technology topic to research and write about", | ||
required: true, | ||
}, | ||
], | ||
content: "Write 3 main keywords about this research topic: $topic", | ||
inputs: [], | ||
content: "You Help the user with writing detailed reports", | ||
}, | ||
], | ||
}, | ||
{ | ||
id: "agent2", | ||
name: "Mark", | ||
description: "Knows a lot of information about mobile and PC games", | ||
chained: false, | ||
tools: [], | ||
prompts: [ | ||
{ | ||
id: "prompt1", | ||
type: "text", | ||
index: 0, | ||
variable_name: "info", | ||
llm_client: "fireworks", | ||
model: "accounts/fireworks/models/firefunction-v1", | ||
options: {}, | ||
inputs: [ | ||
{ | ||
type: "string", | ||
id: "game", | ||
description: "The game title we need to get information about", | ||
required: true, | ||
}, | ||
], | ||
content: "", | ||
}, | ||
], | ||
}, | ||
// { | ||
// id: "agent2", | ||
// name: "Mark", | ||
// description: "Knows a lot of information about mobile and PC games", | ||
// chained: false, | ||
// tools: [], | ||
// prompts: [ | ||
// { | ||
// id: "prompt1", | ||
// type: "text", | ||
// index: 0, | ||
// variable_name: "info", | ||
// llm_client: "fireworks", | ||
// model: "accounts/fireworks/models/firefunction-v1", | ||
// options: {}, | ||
// inputs: [], | ||
// content: "You know a lot of information about mobile and PC games", | ||
// }, | ||
// ], | ||
// }, | ||
]; | ||
@@ -83,38 +73,67 @@ | ||
box.onSelectAgent((agent) => { | ||
console.log("Selected agent:", agent.name); | ||
}); | ||
const toolParameters = { | ||
type: "object", | ||
properties: { | ||
number: { | ||
type: "number", | ||
description: "the number of history results to get. default to 1", | ||
default: 1, | ||
}, | ||
}, | ||
required: ["number"], | ||
} as const satisfies JSONSchema; | ||
box.onFinish((responses) => { | ||
console.log(responses); | ||
type ToolInputs = FromSchema<typeof toolParameters>; | ||
const toolSchema = createToolSchema({ | ||
name: "get_steam_history", | ||
description: "get the recent search history from Steam", | ||
parameters: toolParameters, | ||
}); | ||
box.addGlobalTool(() => {}, { | ||
name: "save_data", | ||
description: "Save data to the database", | ||
parameters: { | ||
type: "object", | ||
properties: { | ||
data: { | ||
type: "string", | ||
description: "The data to be saved as text", | ||
}, | ||
}, | ||
required: ["data"], | ||
const toolFunction = (data: ToolInputs) => { | ||
console.log("TOOL CALLED", data.number); | ||
return "Classic cars"; | ||
}; | ||
box.addGlobalTool(toolFunction, toolSchema); | ||
const hooks: BoxHooks = { | ||
onSelectAgent: (agent) => { | ||
console.log(`\n${agent.name}:`); | ||
}, | ||
onToken: (token) => { | ||
process.stdout.write(token); | ||
}, | ||
onAgentResponse: () => { | ||
console.log("\n-----\n"); | ||
}, | ||
}; | ||
const session = crypto.randomUUID(); | ||
console.log(`STARTED SESSION: ${session}`); | ||
const input_interface = readline.createInterface({ | ||
input: process.stdin, | ||
output: process.stdout, | ||
}); | ||
box | ||
.run({ session_id: "s123", message: "my research topic is about robotics" }) | ||
.then((res) => { | ||
box | ||
.run({ | ||
session_id: "s123", | ||
message: "Can you give me some games related to that", | ||
}) | ||
.then(() => { | ||
box.run({ | ||
message: "Hello, how are you?", | ||
}); | ||
}); | ||
async function runBox(message: string) { | ||
await box.run({ | ||
inputs: { | ||
message: message, | ||
session_id: session, | ||
}, | ||
hooks, | ||
}); | ||
} | ||
function getUserMessage() { | ||
input_interface.question("Your message: ", async (message) => { | ||
await runBox(message); | ||
getUserMessage(); | ||
}); | ||
} | ||
getUserMessage(); |
@@ -47,3 +47,3 @@ import { test, expect } from "vitest"; | ||
content: | ||
"you respond 3 main tips about how to learn the topic $topic. juts 3 main tips a nothing else", | ||
"you respond with 3 main tips about how to learn the topic $topic. juts 3 main tips a nothing else", | ||
inputs: [ | ||
@@ -76,27 +76,29 @@ { | ||
const run = await agent.run({ | ||
session_id: "session1", | ||
topic: "playing guitar", | ||
message: | ||
"I want to learn how to play the chords of the latest song I searched for", | ||
inputs: { | ||
session_id: "session123", | ||
topic: "playing guitar", | ||
message: | ||
"I want to learn how to play the chords of the latest song I searched for", | ||
}, | ||
}); | ||
const run2 = await agent.run({ | ||
session_id: "session1", | ||
message: "What was the name of the latest song I searched for again ?", | ||
plug: { | ||
rag: "New latest search result:\nEagles - Hotel California", | ||
inputs: { | ||
session_id: "session123", | ||
message: "What was the name of the latest song I searched for again ?", | ||
plug: { | ||
rag: "New latest search result:\nEagles - Hotel California", | ||
}, | ||
}, | ||
}); | ||
expect(typeof run.responses.main.content).toBe("string"); | ||
expect(run.responses.main.type).toBe("text"); | ||
expect(typeof run.response.content).toBe("string"); | ||
expect(run.response.type).toBe("text"); | ||
expect(run.session_id).toBe("session1"); | ||
expect(run2.responses.main.type).toBe("text"); | ||
expect(run2.session_id).toBe("session1"); | ||
expect(run.session_id).toBe("session123"); | ||
expect(run2.response.type).toBe("text"); | ||
expect(run2.session_id).toBe("session123"); | ||
expect( | ||
String(run2.responses.main.content) | ||
.toLowerCase() | ||
.includes("hotel california"), | ||
String(run2.response.content).toLowerCase().includes("hotel california"), | ||
).toBe(true); | ||
}); |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
566091
63
9597
8
8
+ Added@types/turndown@^5.0.4
+ Addedaxios@^1.6.8
+ Addedcheerio@^1.0.0-rc.12
+ Addedjson-schema-to-ts@^3.1.0
+ Addedturndown@^7.1.3
+ Added@babel/runtime@7.26.0(transitive)
+ Added@mixmark-io/domino@2.2.0(transitive)
+ Added@types/turndown@5.0.5(transitive)
+ Addedaxios@1.7.9(transitive)
+ Addedboolbase@1.0.0(transitive)
+ Addedcheerio@1.0.0(transitive)
+ Addedcheerio-select@2.1.0(transitive)
+ Addedcss-select@5.1.0(transitive)
+ Addedcss-what@6.1.0(transitive)
+ Addeddom-serializer@2.0.0(transitive)
+ Addeddomelementtype@2.3.0(transitive)
+ Addeddomhandler@5.0.3(transitive)
+ Addeddomutils@3.2.2(transitive)
+ Addedencoding-sniffer@0.2.0(transitive)
+ Addedentities@4.5.0(transitive)
+ Addedfollow-redirects@1.15.9(transitive)
+ Addedhtmlparser2@9.1.0(transitive)
+ Addediconv-lite@0.6.3(transitive)
+ Addedjson-schema-to-ts@3.1.1(transitive)
+ Addednth-check@2.1.1(transitive)
+ Addedparse5@7.2.1(transitive)
+ Addedparse5-htmlparser2-tree-adapter@7.1.0(transitive)
+ Addedparse5-parser-stream@7.1.2(transitive)
+ Addedproxy-from-env@1.1.0(transitive)
+ Addedregenerator-runtime@0.14.1(transitive)
+ Addedsafer-buffer@2.1.2(transitive)
+ Addedts-algebra@2.0.0(transitive)
+ Addedturndown@7.2.0(transitive)
+ Addedundici@6.21.1(transitive)
+ Addedwhatwg-encoding@3.1.1(transitive)
+ Addedwhatwg-mimetype@4.0.0(transitive)