Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 1 addition & 9 deletions bun.lock
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,7 @@
},
"packages/fluent-ai": {
"name": "fluent-ai",
"version": "0.4.4",
"dependencies": {
"eventsource-parser": "^3.0.6",
"partial-json": "^0.1.7",
},
"version": "0.4.7",
"devDependencies": {
"@types/bun": "1.3.0",
"bun-plugin-dts": "^0.3.0",
Expand Down Expand Up @@ -504,8 +500,6 @@

"etag": ["[email protected]", "", {}, "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg=="],

"eventsource-parser": ["[email protected]", "", {}, "sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg=="],

"exit-hook": ["[email protected]", "", {}, "sha512-eNTPlAD67BmP31LDINZ3U7HSF8l57TxOY2PmBJ1shpCvpnxBF93mWCE8YHBnXs8qiUZJc9WDcWIeC3a2HIAMfw=="],

"express": ["[email protected]", "", { "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", "body-parser": "1.20.3", "content-disposition": "0.5.4", "content-type": "~1.0.4", "cookie": "0.7.1", "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "2.0.0", "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", "finalhandler": "1.3.1", "fresh": "0.5.2", "http-errors": "2.0.0", "merge-descriptors": "1.0.3", "methods": "~1.1.2", "on-finished": "2.4.1", "parseurl": "~1.3.3", "path-to-regexp": "0.1.12", "proxy-addr": "~2.0.7", "qs": "6.13.0", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", "send": "0.19.0", "serve-static": "1.16.2", "setprototypeof": "1.2.0", "statuses": "2.0.1", "type-is": "~1.6.18", "utils-merge": "1.0.1", "vary": "~1.1.2" } }, "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA=="],
Expand Down Expand Up @@ -664,8 +658,6 @@

"parseurl": ["[email protected]", "", {}, "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ=="],

"partial-json": ["[email protected]", "", {}, "sha512-Njv/59hHaokb/hRUjce3Hdv12wd60MtM9Z5Olmn+nehe0QDAsRtRbJPvJ0Z91TusF0SuZRIvnM+S4l6EIP8leA=="],

"path-key": ["[email protected]", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="],

"path-scurry": ["[email protected]", "", { "dependencies": { "lru-cache": "^10.2.0", "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" } }, "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA=="],
Expand Down
1 change: 1 addition & 0 deletions packages/fluent-ai/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/dist
21 changes: 19 additions & 2 deletions packages/fluent-ai/examples/ollama-chat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,30 @@ const models = await ollama().models().run();
console.log(models);

const response = await ollama()
.chat(models[0].name)
.chat(models[0].id)
.messages([
{
role: "user",
content: "What is the capital of France?",
text: "What is the capital of France?",
},
])
.run();

console.log(response);

const streamResponse = await ollama()
.chat(models[0].id)
.messages([
{
role: "user",
text: "What is the capital of Spain?",
},
])
.stream()
.run();

for await (const chunk of streamResponse) {
if (chunk.message?.text) {
process.stdout.write(chunk.message.text);
}
}
4 changes: 2 additions & 2 deletions packages/fluent-ai/examples/openrouter-chat.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ const job: Job = {
input: {
model: "google/gemini-2.5-flash",
messages: [
{ role: "system", content: "You are a helpful assistant." },
{ role: "user", content: "Hi" },
{ role: "system", text: "You are a helpful assistant." },
{ role: "user", text: "Hi" },
],
},
};
Expand Down
4 changes: 0 additions & 4 deletions packages/fluent-ai/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,6 @@
"build": "bun run build.ts",
"prepublishOnly": "rm -rf dist && bun run build"
},
"dependencies": {
"eventsource-parser": "^3.0.6",
"partial-json": "^0.1.7"
},
"keywords": [
"ai",
"openai",
Expand Down
136 changes: 75 additions & 61 deletions packages/fluent-ai/src/agent/agent.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
import { z } from "zod";
import { convertMessagesForChatCompletion } from "~/src/agent/message";
import {
agentToolSchema,
type AgentToolBuilder,
type AgentTool,
} from "~/src/agent/tool";
import type { Message } from "~/src/job/schema";
import type {
Message,
ToolMessage,
MessageChunk,
AssistantMessage,
} from "~/src/job/schema";
import type { ChatBuilder } from "~/src/builder/chat";

export const agentSchema = z.object({
Expand All @@ -14,7 +18,30 @@ export const agentSchema = z.object({
tools: z.array(agentToolSchema),
});

interface GenerateOptions {
interface ChunkEvent {
type: "chunk";
chunk: {
text?: string;
reasoning?: string;
};
}

interface ToolEvent {
type: "tool";
tool: {
name: string;
args: any;
result?: any;
error?: any;
};
}

interface MessageEvent {
type: "message";
message: Message;
}

export interface AgentGenerateOptions {
maxSteps: number;
}

Expand Down Expand Up @@ -46,7 +73,7 @@ export class Agent<TContext = any> {
generate = async function* (
this: Agent<TContext>,
initialMessages: Message[],
options: GenerateOptions,
options: AgentGenerateOptions,
context?: TContext,
) {
const body = agentSchema.parse(this.body);
Expand All @@ -62,12 +89,11 @@ export class Agent<TContext = any> {
typeof body.instructions === "function"
? body.instructions()
: body.instructions;
const allMessages = initialMessages.concat(newMessages);
const convertedMessages = convertMessagesForChatCompletion(allMessages);
const messages = [{ role: "system", content: instructions }].concat(
convertedMessages as any,
const systemMessage = { role: "system", text: instructions };
const messages = ([systemMessage] as Message[]).concat(
initialMessages,
newMessages,
);
// TODO: agent tool vs chat tool
const tools = body.tools.map((tool) => ({
name: tool.name,
description: tool.description,
Expand All @@ -79,79 +105,67 @@ export class Agent<TContext = any> {
.run();

let totalText = "";
for await (const chunk of result) {
const delta = chunk.raw.choices[0].delta;

// TODO: tool calls with content??
if (delta.tool_calls) {
// TODO: tool call with content
// TODO: tool call with input streaming
// TODO: support multiple tool calls
const toolCall = delta.tool_calls[0];
const toolName = toolCall.function.name;
const input = JSON.parse(toolCall.function.arguments); // TODO: parsing error handling

const agentTool = body.tools.find((t) => t.name === toolName);
for await (const chunk of result as AsyncIterable<MessageChunk>) {
if (chunk.toolCalls) {
const toolCall = chunk.toolCalls[0];
const { name, arguments: args } = toolCall.function;
const agentTool = body.tools.find((t) => t.name === name);
if (!agentTool) {
throw new Error(`Unknown tool: ${toolName}`);
throw new Error(`Unknown tool: ${name}`);
}

const toolPart = {
type: "tool-" + toolName,
toolCallId: toolCall.id,
input: input,
};

yield { type: "tool-call-input", data: toolPart };

let output = null;
let outputError = null;
yield { type: "tool", tool: { name, args } };

let result = null;
let error = null;
try {
output = await agentTool.execute(input, context!);
result = await agentTool.execute(args, context!);
} catch (err) {
outputError = (err as Error).message;
error = (err as Error).message;
}

if (outputError) {
yield {
type: "tool-call-output",
data: { ...toolPart, outputError },
};
} else {
yield { type: "tool-call-output", data: { ...toolPart, output } };
}
yield {
type: "tool",
tool: { name, args, result, error },
} as ToolEvent;

const newMessage: Message = {
const newMessage: ToolMessage = {
role: "tool",
parts: [
{
type: `tool-${toolName}`,
toolCallId: toolCall.id,
input: input,
output: output,
outputError: outputError,
},
],
text: "",
content: {
callId: toolCall.id,
name: name,
args: args,
result: result,
error: error,
},
};

yield { type: "message-created", data: newMessage };
newMessages.push(newMessage);
} else if (delta.content) {
const text = delta.content as string;
yield { type: "text-delta", data: { text } };
totalText += text;
shouldBreak = false;
} else if (chunk.text || chunk.reasoning) {
yield {
type: "chunk",
chunk: {
text: chunk.text,
reasoning: chunk.reasoning,
},
} as ChunkEvent;

if (chunk.text) {
totalText += chunk.text;
}
shouldBreak = true;
}
}

if (totalText.trim()) {
const newMessage: Message = {
const newMessage: AssistantMessage = {
role: "assistant",
parts: [{ type: "text", text: totalText.trim() }],
text: totalText,
};

yield { type: "message-created", data: newMessage };
yield { type: "message", message: newMessage } as MessageEvent;
newMessages.push(newMessage);
shouldBreak = true;
}
Expand Down
46 changes: 0 additions & 46 deletions packages/fluent-ai/src/agent/message.ts

This file was deleted.

Loading