my-ai-chat-framework
v2.7.0
Published
A lightweight AI chat framework with plugin system, unified message format, and tool calling support.
Maintainers
Readme
🤖 My AI Chat Framework
A lightweight, modular AI chat framework with plugin system, unified message format, and tool calling support.
⚠️ Note: This project is created for learning purposes and is AI‑generated. It is not intended for production use. No backward compatibility is guaranteed. Use at your own risk.
✨ Features
- Lightweight Core – ~300 lines, easy to understand and extend.
- Plugin System – Add features (tool calling, reasoning, custom adapters) without touching the core.
- Unified Message Format – Consistent data structure across all components.
- Multi‑Environment – Builds ES module, UMD, and CommonJS for browser & Node.js.
- No External Dependencies – Uses native
fetch(Node 18+ & modern browsers). - Tool Calling – Built‑in plugin to handle function calls from AI models.
- Streaming – Full support for real‑time responses.
- Flexible Configuration – Supports both flat and nested
modelParamsstructure. - Event‑Driven – Built‑in EventEmitter for
message,sending,error,stream-progressevents. - Custom Error Classes –
APIError,NetworkError,ConfigurationError,ParsingErrorfor fine‑grained error handling.
🧱 Architecture
src/
├── index.js # Public entry point (re-exports)
├── core/
│ ├── ChatService.js # Main service: config, send/stream, plugin hosting
│ ├── MessageStore.js # In-memory message list with CRUD helpers
│ ├── EventEmitter.js # Minimal pub/sub (on/off/emit)
│ └── Errors.js # Custom error classes
├── adapters/
│ └── openai.js # OpenAI‑compatible API adapter (plugin pattern)
├── plugins/
│ └── tool-calling.js # Tool calling plugin (auto‑detect & loop)
└── utils/
├── typeCheck.js # Type checking helpers
└── url.js # URL joining utilityData flow:
User calls chat.send(input)
→ ChatService._request()
→ emits 'sending'
→ MessageStore.add(userMsg)
→ adapter.buildRequest(messages, config) ← formats request body
→ adapter.send(requestBody, config) ← HTTP call (fetch)
→ adapter.parseResponse(response) ← normalize response
→ MessageStore.add(assistantMsg)
→ emits 'message'
→ returns assistantMsgWith toolCallingPlugin, the flow loops: response → detect tool_calls → execute tools → add tool results → sendExisting → repeat (max 5 iterations).
📦 Installation
npm install my-ai-chat-framework🚀 Quick Start
Basic Usage (Flat Configuration)
import { ChatService, openaiAdapter, toolCallingPlugin } from 'my-ai-chat-framework';
const chat = new ChatService({
apiKey: 'your-api-key',
baseUrl: 'https://api.deepseek.com', // optional, defaults to OpenAI
model: 'deepseek-chat',
temperature: 0.7,
maxTokens: 2000
});
chat.use(openaiAdapter);
chat.use(toolCallingPlugin);
chat.on('message', msg => console.log(msg.content));
await chat.send('Hello!');Using modelParams (Recommended for Many Parameters)
const chat = new ChatService({
apiKey: 'your-api-key',
baseUrl: 'https://api.deepseek.com',
model: 'deepseek-chat', // still at top level for convenience
modelParams: { // optional parameters grouped
temperature: 0.8,
maxTokens: 1500,
reasoningEffort: 'medium' // for deepseek-reasoner
}
});Registering a Tool
chat.registerTool('get_weather', 'Get current weather for a city',
async (args) => {
// args = { city: 'Beijing' }
return `Weather in ${args.city}: 22°C, sunny`;
},
{ // parameter schema (optional but recommended)
city: { type: 'string', description: 'City name', required: true }
}
);
await chat.send('What\'s the weather in Beijing?');
// → AI calls get_weather, framework executes it, AI responds with weather info🔌 Plugins & Adapters
openaiAdapter
Converts internal messages to OpenAI‑compatible format. Supports:
apiUrl– full URL (highest priority)baseUrl+path– base domain + API path- Defaults to
https://api.openai.com/v1/chat/completions
| Config field | Type | Default | Description |
|-------------|------|---------|-------------|
| apiKey | string | required | Bearer token for Authorization header |
| apiUrl | string | – | Full request URL (overrides baseUrl+path) |
| baseUrl | string | https://api.openai.com | API base domain |
| path | string | /v1/chat/completions | API endpoint path |
toolCallingPlugin
Detects tool_calls in assistant responses, executes registered tools, feeds results back, and continues the conversation (up to maxIterations = 5).
chat.registerTool(name, description, executor, parameters?)– register a tool- Automatically injects
toolrole messages into the conversation - Recovers from tool execution errors gracefully (logs error, returns error message to model)
📡 Events (EventEmitter)
ChatService extends EventEmitter. Subscribe with chat.on(event, handler):
| Event | Payload | When |
|-------|---------|------|
| sending | { addUser, userInput, timestamp } | Before each request |
| message | { role, content, ... } | Full assistant message received |
| stream-progress | chunk object | Each streaming chunk arrives |
| error | { error, timestamp } | Any error during request |
chat.on('sending', ({ userInput }) => console.log('Sending:', userInput));
chat.on('message', msg => console.log('Got:', msg.content));
chat.on('error', ({ error }) => console.error('Error:', error.message));
// on() returns an unsubscribe function
const unsubscribe = chat.on('message', handler);
unsubscribe(); // stop listening🧩 ChatService API
| Method | Returns | Description |
|--------|---------|-------------|
| chat.send(userInput) | Promise<Message> | Send a message, get reply (non‑streaming) |
| chat.stream(userInput, onProgress, onDone) | Promise<Message> | Send a message, get streaming reply |
| chat.sendExisting() | Promise<Message> | Re‑send current messages without adding user input |
| chat.sendExistingStream(onProgress, onDone) | Promise<Message> | Same as above, streaming |
| chat.use(plugin) | this | Install a plugin/adapter |
| chat.setAdapter(adapter) | void | Manually set the adapter |
| chat.on(event, handler) | unsubscribe function | Subscribe to events |
| chat.registerTool(name, desc, fn, params?) | this | Register a tool (requires toolCallingPlugin) |
| chat.messages | MessageStore | Access the message store directly |
🗄️ MessageStore API
| Method | Description |
|--------|-------------|
| add(message) | Add a raw message object |
| addUser(content, meta?) | Add a user message |
| addAssistant(content, meta?) | Add an assistant message |
| addSystem(content, meta?) | Add a system message |
| addTool(content, toolCallId, meta?) | Add a tool result message |
| getAll() | Return a shallow copy of all messages |
| getLast() | Return the last message (or null) |
| clear() | Remove all messages |
| undoToLastAssistant() | Remove messages after the last assistant message |
Message format:
{
id: string, // auto‑generated if not provided
role: 'user' | 'assistant' | 'system' | 'tool',
content: string,
toolCalls?: Array, // assistant messages with tool calls
toolCallId?: string, // tool messages
reasoningContent?: string, // deepseek-reasoner
timestamp?: number,
metadata?: any
}❌ Error Handling
The framework throws typed errors for different failure modes:
| Error Class | .name | When |
|------------|---------|------|
| APIError | 'APIError' | Non‑2xx HTTP responses (401, 429, 500, etc.) |
| NetworkError | 'NetworkError' | fetch failures, connection timeouts |
| ConfigurationError | 'ConfigurationError' | Missing required config |
| ParsingError | 'ParsingError' | Malformed API response |
import { APIError, NetworkError, ConfigurationError, ParsingError } from 'my-ai-chat-framework';
try {
await chat.send('Hello');
} catch (error) {
if (error instanceof APIError) {
console.error(`API ${error.statusCode}: ${error.message}`);
} else if (error instanceof NetworkError) {
console.error('Network issue:', error.message);
}
}📚 Configuration Reference
new ChatService(config) accepts:
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| apiKey | string | required | Your API key |
| baseUrl | string | 'https://api.openai.com' | API base URL (used with path) |
| path | string | '/v1/chat/completions' | API path (used with baseUrl) |
| apiUrl | string | – | Full API URL (overrides baseUrl+path) |
| model | string | required | Model name (e.g., deepseek-chat) |
| modelParams | object | {} | Grouped model parameters (see below) |
| temperature | number | 0.7 | Sampling temperature (0–2) |
| maxTokens | number | 2000 | Max tokens to generate |
| reasoningEffort | string | – | For deepseek-reasoner: 'low', 'medium', 'high' |
Both flat and
modelParamsstyles work.modelParamstakes precedence over top‑level values.
🧪 Testing
# 1. Create a .env file with your API key
echo "DEEPSEEK_API_KEY=sk-xxxxx" > .env
# 2. Run the test
npm testThe test script (test.js) sends a simple message and logs the response.
🛠️ Development
git clone https://github.com/your-username/my-ai-chat-framework.git
cd my-ai-chat-framework
npm install
# Dev mode (watching)
npm run dev
# Build the library (ES + UMD + CJS)
npm run build
# Run tests
npm test📄 License
MIT
