adaptive-memory-multi-model-router 2.15.3 → 2.15.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/adapters-ci.yml +142 -0
- package/.github/workflows/ci.yml +2 -5
- package/.github/workflows/pypi-publish.yml +102 -0
- package/CHANGELOG.md +7 -1
- package/README.md +260 -137
- package/README_ja.md +2 -2
- package/README_zh.md +1 -1
- package/adapters/README.md +36 -0
- package/adapters/__init__.py +25 -0
- package/adapters/a3m_adapter/__init__.py +51 -0
- package/adapters/a3m_adapter/adapter/__init__.py +22 -0
- package/adapters/a3m_adapter/adapter/autogen.py +169 -0
- package/adapters/a3m_adapter/adapter/config.py +100 -0
- package/adapters/a3m_adapter/adapter/haystack.py +197 -0
- package/adapters/a3m_adapter/adapter/langchain.py +155 -0
- package/adapters/a3m_adapter/adapter/langgraph.py +196 -0
- package/adapters/a3m_adapter/adapter/llamaindex.py +162 -0
- package/adapters/a3m_adapter/adapter/pinecone.py +217 -0
- package/adapters/a3m_adapter/adapter/vercel.py +188 -0
- package/adapters/a3m_adapter/tests/__init__.py +1 -0
- package/adapters/a3m_adapter/tests/test_adapters.py +118 -0
- package/adapters/a3m_adapter/tests/test_integration.py +80 -0
- package/adapters/requirements-dev.txt +6 -0
- package/adapters/requirements.txt +4 -0
- package/adapters/setup.py +23 -0
- package/apps/cost-calculator/README.md +72 -0
- package/apps/cost-calculator/calculator.css +280 -0
- package/apps/cost-calculator/calculator.js +150 -0
- package/apps/cost-calculator/index.html +321 -0
- package/apps/cost-calculator/package.json +13 -0
- package/articles/ANNOUNCEMENT_reddit_ml.md +76 -0
- package/articles/ANNOUNCEMENT_vc/347/244/276/345/214/272.md +71 -0
- package/articles/ANNOUNCEMENT_vercel.md +85 -0
- package/demo.py +251 -0
- package/dist/providers/providerConfig.d.ts +5 -1
- package/dist/providers/providerConfig.js +1006 -1
- package/dist/providers/providerConfig.js.map +1 -1
- package/docker-compose.yml +84 -60
- package/docs/comparison.md +2 -2
- package/docs/llms-full.txt +360 -138
- package/docs/llms.txt +70 -71
- package/docs-site/index.html +9 -9
- package/llms.txt +70 -62
- package/package.json +41 -91
- package/packages/a3m-vercel-ai/README.md +161 -0
- package/packages/a3m-vercel-ai/dist/a3m-language-model.d.ts +12 -0
- package/packages/a3m-vercel-ai/dist/a3m-language-model.d.ts.map +1 -0
- package/packages/a3m-vercel-ai/dist/a3m-language-model.js +289 -0
- package/packages/a3m-vercel-ai/dist/a3m-language-model.js.map +1 -0
- package/packages/a3m-vercel-ai/dist/index.d.ts +82 -0
- package/packages/a3m-vercel-ai/dist/index.d.ts.map +1 -0
- package/packages/a3m-vercel-ai/dist/index.js +79 -0
- package/packages/a3m-vercel-ai/dist/index.js.map +1 -0
- package/packages/a3m-vercel-ai/dist/types.d.ts +97 -0
- package/packages/a3m-vercel-ai/dist/types.d.ts.map +1 -0
- package/packages/a3m-vercel-ai/dist/types.js +5 -0
- package/packages/a3m-vercel-ai/dist/types.js.map +1 -0
- package/packages/a3m-vercel-ai/package-lock.json +969 -0
- package/packages/a3m-vercel-ai/package.json +46 -0
- package/packages/a3m-vercel-ai/src/a3m-language-model.ts +381 -0
- package/packages/a3m-vercel-ai/src/index.ts +104 -0
- package/packages/a3m-vercel-ai/src/types.ts +116 -0
- package/packages/a3m-vercel-ai/tsconfig.json +20 -0
- package/packages/agentkit-adapter/LICENSE +21 -0
- package/packages/agentkit-adapter/README.md +126 -0
- package/packages/agentkit-adapter/examples/agentkit-example.ts +139 -0
- package/packages/agentkit-adapter/package.json +57 -0
- package/packages/agentkit-adapter/src/adapter.ts +381 -0
- package/packages/agentkit-adapter/src/index.ts +36 -0
- package/packages/agentkit-adapter/src/types.ts +105 -0
- package/packages/agentkit-adapter/src/util.ts +13 -0
- package/packages/agentkit-adapter/tsconfig.json +22 -0
- package/prometheus.yml +8 -0
- package/python/README.md +92 -65
- package/python/a3m/__init__.py +32 -3
- package/python/a3m/adapters/__init__.py +21 -0
- package/python/a3m/adapters/langchain.py +190 -0
- package/python/a3m/adapters/llamaindex.py +249 -0
- package/python/a3m/adapters/qdrant.py +240 -0
- package/python/a3m/adapters/weaviate.py +263 -0
- package/python/a3m/client.py +5 -0
- package/python/a3m_router.egg-info/PKG-INFO +172 -0
- package/python/a3m_router.egg-info/SOURCES.txt +17 -0
- package/python/a3m_router.egg-info/dependency_links.txt +1 -0
- package/python/a3m_router.egg-info/requires.txt +24 -0
- package/python/a3m_router.egg-info/top_level.txt +1 -0
- package/python/build_verify.sh +32 -0
- package/python/dist/a3m_router-2.2.1-py3-none-any.whl +0 -0
- package/python/dist/a3m_router-2.2.1.tar.gz +0 -0
- package/python/dist/a3m_router-2.2.2-py3-none-any.whl +0 -0
- package/python/dist/a3m_router-2.2.2.tar.gz +0 -0
- package/python/mcp-server/README.md +172 -0
- package/python/mcp-server/a3m_mcp/__init__.py +15 -0
- package/python/mcp-server/a3m_mcp/__main__.py +15 -0
- package/python/mcp-server/a3m_mcp/server.py +339 -0
- package/python/mcp-server/pyproject.toml +24 -0
- package/python/pyproject.toml +56 -5
- package/python/setup.py +3 -28
- package/src/providers/providerConfig.ts +1053 -1
- package/summary.txt +38 -0
- package/tsconfig.build.json +1 -2
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
# A3M Router AgentKit Adapter
|
|
2
|
+
|
|
3
|
+
AgentKit adapter that routes LLM calls through A3M Router for intelligent, cost-optimized model selection.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @a3m/agentkit-adapter
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
import { createAgentKitAdapter } from '@a3m/agentkit-adapter';
|
|
15
|
+
import { createAgent, run } from '@stablelib/agentkit';
|
|
16
|
+
|
|
17
|
+
// Create A3M-powered adapter
|
|
18
|
+
const a3mAdapter = createAgentKitAdapter({
|
|
19
|
+
baseUrl: 'http://localhost:8787',
|
|
20
|
+
model: 'auto',
|
|
21
|
+
temperature: 0.7,
|
|
22
|
+
maxTokens: 4096,
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
// Create agent with A3M routing
|
|
26
|
+
const agent = createAgent({
|
|
27
|
+
name: 'a3m-assistant',
|
|
28
|
+
description: 'AI assistant powered by A3M Router',
|
|
29
|
+
llm: a3mAdapter,
|
|
30
|
+
tools: [
|
|
31
|
+
// your tools
|
|
32
|
+
],
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
// Run the agent
|
|
36
|
+
const result = await run(agent, {
|
|
37
|
+
input: 'Hello, what is 2+2?',
|
|
38
|
+
});
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## API
|
|
42
|
+
|
|
43
|
+
### `createAgentKitAdapter(config)`
|
|
44
|
+
|
|
45
|
+
Creates an A3M Router adapter for AgentKit.
|
|
46
|
+
|
|
47
|
+
**Config options:**
|
|
48
|
+
|
|
49
|
+
| Option | Type | Default | Description |
|
|
50
|
+
|--------|------|---------|-------------|
|
|
51
|
+
| `baseUrl` | `string` | `'http://localhost:8787'` | A3M Router server URL |
|
|
52
|
+
| `model` | `string` | `'auto'` | Model to use (`'auto'` for intelligent routing) |
|
|
53
|
+
| `temperature` | `number` | `0.7` | Sampling temperature |
|
|
54
|
+
| `maxTokens` | `number` | `4096` | Max tokens to generate |
|
|
55
|
+
| `parallelEnsemble` | `number` | `1` | Number of providers for ensemble |
|
|
56
|
+
| `apiKey` | `string` | — | Optional API key |
|
|
57
|
+
| `systemPrompt` | `string` | — | Optional system prompt |
|
|
58
|
+
| `tools` | `AgentTool[]` | `[]` | Available tools |
|
|
59
|
+
|
|
60
|
+
**Returns:** `A3MAgentKitAdapter` instance
|
|
61
|
+
|
|
62
|
+
### Adapter Methods
|
|
63
|
+
|
|
64
|
+
#### `chat(messages, tools?)`
|
|
65
|
+
|
|
66
|
+
Send a chat completion request.
|
|
67
|
+
|
|
68
|
+
```typescript
|
|
69
|
+
const response = await a3mAdapter.chat([
|
|
70
|
+
{ role: 'user', content: 'What is AI?' },
|
|
71
|
+
]);
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
#### `stream(messages)`
|
|
75
|
+
|
|
76
|
+
Stream a chat completion response.
|
|
77
|
+
|
|
78
|
+
```typescript
|
|
79
|
+
for await (const chunk of a3mAdapter.stream(messages)) {
|
|
80
|
+
process.stdout.write(chunk.content);
|
|
81
|
+
}
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
#### `getTools()`
|
|
85
|
+
|
|
86
|
+
Get configured tools for function calling.
|
|
87
|
+
|
|
88
|
+
```typescript
|
|
89
|
+
const tools = a3mAdapter.getTools();
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## How It Works
|
|
93
|
+
|
|
94
|
+
1. Incoming requests are forwarded to A3M Router at `baseUrl`
|
|
95
|
+
2. A3M Router analyzes query complexity and routes to cheapest capable provider
|
|
96
|
+
3. Response is returned with routing metadata
|
|
97
|
+
4. Falls back gracefully if A3M Router is unavailable
|
|
98
|
+
|
|
99
|
+
## Example with Tools
|
|
100
|
+
|
|
101
|
+
```typescript
|
|
102
|
+
import { createAgentKitAdapter } from '@a3m/agentkit-adapter';
|
|
103
|
+
|
|
104
|
+
const calculatorTool = {
|
|
105
|
+
name: 'calculator',
|
|
106
|
+
description: 'Evaluate a mathematical expression',
|
|
107
|
+
parameters: {
|
|
108
|
+
expression: { type: 'string', description: 'The math expression to evaluate' },
|
|
109
|
+
},
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
const adapter = createAgentKitAdapter({
|
|
113
|
+
baseUrl: 'http://localhost:8787',
|
|
114
|
+
model: 'auto',
|
|
115
|
+
tools: [calculatorTool],
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
const response = await adapter.chat(
|
|
119
|
+
[{ role: 'user', content: 'What is 2+2?' }],
|
|
120
|
+
[calculatorTool]
|
|
121
|
+
);
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
## License
|
|
125
|
+
|
|
126
|
+
MIT
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A3M Router AgentKit Adapter - Example Usage
|
|
3
|
+
*
|
|
4
|
+
* Run: npx ts-node examples/agentkit-example.ts
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { createAgentKitAdapter, A3MAdapterConfig } from '../src';
|
|
8
|
+
|
|
9
|
+
// Example 1: Basic chat
|
|
10
|
+
async function basicChat() {
|
|
11
|
+
console.log('=== Example 1: Basic Chat ===');
|
|
12
|
+
|
|
13
|
+
const adapter = createAgentKitAdapter({
|
|
14
|
+
baseUrl: 'http://localhost:8787',
|
|
15
|
+
model: 'auto',
|
|
16
|
+
temperature: 0.7,
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
const response = await adapter.chat([
|
|
20
|
+
{ role: 'user', content: 'What is 2+2?' },
|
|
21
|
+
]);
|
|
22
|
+
|
|
23
|
+
console.log('Response:', response.content);
|
|
24
|
+
console.log('Provider:', response.provider);
|
|
25
|
+
console.log('Model:', response.model);
|
|
26
|
+
console.log('Tier:', response.tier);
|
|
27
|
+
console.log();
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Example 2: Streaming
|
|
31
|
+
async function streamingChat() {
|
|
32
|
+
console.log('=== Example 2: Streaming ===');
|
|
33
|
+
|
|
34
|
+
const adapter = createAgentKitAdapter({
|
|
35
|
+
baseUrl: 'http://localhost:8787',
|
|
36
|
+
model: 'auto',
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
process.stdout.write('Stream: ');
|
|
40
|
+
for await (const chunk of adapter.stream([
|
|
41
|
+
{ role: 'user', content: 'Count to 5' },
|
|
42
|
+
])) {
|
|
43
|
+
process.stdout.write(chunk.content);
|
|
44
|
+
}
|
|
45
|
+
console.log('\n');
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Example 3: With tools
|
|
49
|
+
async function toolChat() {
|
|
50
|
+
console.log('=== Example 3: With Tools ===');
|
|
51
|
+
|
|
52
|
+
const calculatorTool = {
|
|
53
|
+
name: 'calculator',
|
|
54
|
+
description: 'Evaluate a mathematical expression',
|
|
55
|
+
parameters: {
|
|
56
|
+
type: 'object',
|
|
57
|
+
properties: {
|
|
58
|
+
expression: {
|
|
59
|
+
type: 'string',
|
|
60
|
+
description: 'The math expression to evaluate'
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
required: ['expression'],
|
|
64
|
+
},
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
const weatherTool = {
|
|
68
|
+
name: 'get_weather',
|
|
69
|
+
description: 'Get current weather for a location',
|
|
70
|
+
parameters: {
|
|
71
|
+
type: 'object',
|
|
72
|
+
properties: {
|
|
73
|
+
location: {
|
|
74
|
+
type: 'string',
|
|
75
|
+
description: 'City name'
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
required: ['location'],
|
|
79
|
+
},
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
const adapter = createAgentKitAdapter({
|
|
83
|
+
baseUrl: 'http://localhost:8787',
|
|
84
|
+
model: 'auto',
|
|
85
|
+
temperature: 0.7,
|
|
86
|
+
tools: [calculatorTool, weatherTool],
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
const response = await adapter.chat(
|
|
90
|
+
[
|
|
91
|
+
{
|
|
92
|
+
role: 'user',
|
|
93
|
+
content: 'What is the weather in San Francisco and what is 50 * 23?'
|
|
94
|
+
},
|
|
95
|
+
],
|
|
96
|
+
[calculatorTool, weatherTool]
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
console.log('Response:', response.content);
|
|
100
|
+
console.log('Provider:', response.provider);
|
|
101
|
+
console.log('Tool Calls:', response.toolCalls);
|
|
102
|
+
console.log();
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Example 4: Parallel ensemble
|
|
106
|
+
async function ensembleChat() {
|
|
107
|
+
console.log('=== Example 4: Parallel Ensemble ===');
|
|
108
|
+
|
|
109
|
+
const adapter = createAgentKitAdapter({
|
|
110
|
+
baseUrl: 'http://localhost:8787',
|
|
111
|
+
model: 'auto',
|
|
112
|
+
parallelEnsemble: 3, // Call 3 providers, pick best
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
const response = await adapter.chat([
|
|
116
|
+
{ role: 'user', content: 'Explain quantum entanglement in one sentence' },
|
|
117
|
+
]);
|
|
118
|
+
|
|
119
|
+
console.log('Best provider:', response.provider);
|
|
120
|
+
console.log('Response:', response.content);
|
|
121
|
+
console.log('All candidates:', response.candidates);
|
|
122
|
+
console.log();
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Main
|
|
126
|
+
async function main() {
|
|
127
|
+
try {
|
|
128
|
+
await basicChat();
|
|
129
|
+
await streamingChat();
|
|
130
|
+
await toolChat();
|
|
131
|
+
await ensembleChat();
|
|
132
|
+
console.log('All examples completed!');
|
|
133
|
+
} catch (error) {
|
|
134
|
+
console.error('Error:', error);
|
|
135
|
+
console.log('\nMake sure A3M Router is running: npx a3m-router serve');
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
main();
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@a3m/agentkit-adapter",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "A3M Router adapter for AgentKit - enables AI agents with intelligent multi-provider routing",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"module": "dist/index.mjs",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.mjs",
|
|
12
|
+
"require": "./dist/index.js"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist"
|
|
17
|
+
],
|
|
18
|
+
"scripts": {
|
|
19
|
+
"build": "tsup src/index.ts --format cjs,esm --dts --clean",
|
|
20
|
+
"dev": "tsup src/index.ts --format cjs,esm --dts --watch",
|
|
21
|
+
"test": "vitest",
|
|
22
|
+
"lint": "eslint src/",
|
|
23
|
+
"prepublishOnly": "npm run build"
|
|
24
|
+
},
|
|
25
|
+
"keywords": [
|
|
26
|
+
"agentkit",
|
|
27
|
+
"a3m",
|
|
28
|
+
"router",
|
|
29
|
+
"llm",
|
|
30
|
+
"multi-provider",
|
|
31
|
+
"ai",
|
|
32
|
+
"agent",
|
|
33
|
+
"openai",
|
|
34
|
+
"anthropic",
|
|
35
|
+
"routing"
|
|
36
|
+
],
|
|
37
|
+
"author": "",
|
|
38
|
+
"license": "MIT",
|
|
39
|
+
"peerDependencies": {
|
|
40
|
+
"@inngest/agent-kit": ">=0.0.1"
|
|
41
|
+
},
|
|
42
|
+
"dependencies": {
|
|
43
|
+
"@inngest/ai": ">=0.1.0",
|
|
44
|
+
"zod": "^3.22.4"
|
|
45
|
+
},
|
|
46
|
+
"devDependencies": {
|
|
47
|
+
"@inngest/agent-kit": "^0.13.0",
|
|
48
|
+
"@types/node": "^20.10.0",
|
|
49
|
+
"tsup": "^8.0.0",
|
|
50
|
+
"typescript": "^5.3.0",
|
|
51
|
+
"vitest": "^1.0.0"
|
|
52
|
+
},
|
|
53
|
+
"repository": {
|
|
54
|
+
"type": "git",
|
|
55
|
+
"url": "https://github.com/Das-rebel/adaptive-memory-multi-model-router"
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -0,0 +1,381 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A3M Router AgentKit Adapter
|
|
3
|
+
*
|
|
4
|
+
* Wraps A3M Router as an AgentKit-compatible LLM backend.
|
|
5
|
+
* Supports tool calling, streaming, and parallel ensemble mode.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { type AiAdapter } from "@inngest/ai";
|
|
9
|
+
import { z } from "zod";
|
|
10
|
+
import { stringifyError } from "./util";
|
|
11
|
+
import type {
|
|
12
|
+
A3MConfig,
|
|
13
|
+
A3MInferenceOptions,
|
|
14
|
+
A3MMessage,
|
|
15
|
+
A3MTool,
|
|
16
|
+
A3MStreamChunk,
|
|
17
|
+
A3MToolCall,
|
|
18
|
+
} from "./types";
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* A3M Router adapter for AgentKit
|
|
22
|
+
*/
|
|
23
|
+
export class A3MAdapter {
|
|
24
|
+
private config: Required<A3MConfig>;
|
|
25
|
+
private format: AiAdapter.Format = "openai-chat";
|
|
26
|
+
private url: string;
|
|
27
|
+
private authKey: string = "";
|
|
28
|
+
|
|
29
|
+
constructor(config: A3MConfig = {}) {
|
|
30
|
+
this.config = {
|
|
31
|
+
baseUrl: config.baseUrl || "http://localhost:8787",
|
|
32
|
+
apiKey: config.apiKey || "",
|
|
33
|
+
defaultModel: config.defaultModel || "",
|
|
34
|
+
parallel: config.parallel ?? false,
|
|
35
|
+
temperature: config.temperature ?? 0.7,
|
|
36
|
+
maxTokens: config.maxTokens ?? 4096,
|
|
37
|
+
headers: config.headers || {},
|
|
38
|
+
};
|
|
39
|
+
this.url = `${this.config.baseUrl}/v1/chat/completions`;
|
|
40
|
+
this.authKey = this.config.apiKey;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Get the adapter format
|
|
45
|
+
*/
|
|
46
|
+
getFormat(): AiAdapter.Format {
|
|
47
|
+
return this.format;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Get adapter options (required by AgentKit)
|
|
52
|
+
*/
|
|
53
|
+
getOptions(): AiAdapter.Any["options"] {
|
|
54
|
+
return {
|
|
55
|
+
model: this.config.defaultModel || "auto",
|
|
56
|
+
apiKey: this.config.apiKey,
|
|
57
|
+
baseUrl: this.config.baseUrl,
|
|
58
|
+
} as AiAdapter.Any["options"];
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Convert AgentKit messages to A3M format
|
|
63
|
+
*/
|
|
64
|
+
private convertMessages(messages: Array<{
|
|
65
|
+
type: string;
|
|
66
|
+
role?: string;
|
|
67
|
+
content?: string;
|
|
68
|
+
tool?: { id?: string; name?: string };
|
|
69
|
+
tools?: unknown[];
|
|
70
|
+
}>): A3MMessage[] {
|
|
71
|
+
return messages.map((m) => {
|
|
72
|
+
if (m.type === "tool_result") {
|
|
73
|
+
return {
|
|
74
|
+
role: "tool" as const,
|
|
75
|
+
content: typeof m.content === "string" ? m.content : JSON.stringify(m.content),
|
|
76
|
+
toolCallId: m.tool?.id,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
if (m.type === "tool_call") {
|
|
80
|
+
// Tool calls are handled separately
|
|
81
|
+
return {
|
|
82
|
+
role: "assistant" as const,
|
|
83
|
+
content: "",
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
return {
|
|
87
|
+
role: (m.role as "user" | "assistant" | "system") || "user",
|
|
88
|
+
content: m.content || "",
|
|
89
|
+
};
|
|
90
|
+
}).filter((m) => m.content || m.role === "system");
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Convert AgentKit tools to A3M/OpenAI format
|
|
95
|
+
*/
|
|
96
|
+
private convertTools(tools: Array<{
|
|
97
|
+
name: string;
|
|
98
|
+
description?: string;
|
|
99
|
+
parameters?: z.ZodType<unknown>;
|
|
100
|
+
strict?: boolean;
|
|
101
|
+
}>) {
|
|
102
|
+
return tools.map((t) => ({
|
|
103
|
+
type: "function",
|
|
104
|
+
function: {
|
|
105
|
+
name: t.name,
|
|
106
|
+
description: t.description || "",
|
|
107
|
+
parameters: t.parameters ? z.toJSONSchema(t.parameters, { target: "draft-7" }) : undefined,
|
|
108
|
+
strict: t.strict ?? Boolean(t.parameters),
|
|
109
|
+
},
|
|
110
|
+
}));
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Parse A3M/OpenAI response to AgentKit messages
|
|
115
|
+
*/
|
|
116
|
+
private parseResponse(input: Record<string, unknown>): Array<{
|
|
117
|
+
type: string;
|
|
118
|
+
role?: string;
|
|
119
|
+
content?: string;
|
|
120
|
+
tools?: A3MToolCall[];
|
|
121
|
+
stop_reason?: string;
|
|
122
|
+
}> {
|
|
123
|
+
const result: Array<{
|
|
124
|
+
type: string;
|
|
125
|
+
role?: string;
|
|
126
|
+
content?: string;
|
|
127
|
+
tools?: A3MToolCall[];
|
|
128
|
+
stop_reason?: string;
|
|
129
|
+
}> = [];
|
|
130
|
+
|
|
131
|
+
if (input.error) {
|
|
132
|
+
throw new Error(`A3M request failed: ${JSON.stringify(input.error)}`);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const choices = (input.choices as Array<Record<string, unknown>>) || [];
|
|
136
|
+
|
|
137
|
+
for (const choice of choices) {
|
|
138
|
+
const message = choice.message as Record<string, unknown> || {};
|
|
139
|
+
const finishReason = choice.finish_reason as string || "stop";
|
|
140
|
+
|
|
141
|
+
// Text content
|
|
142
|
+
if (message.content && (message.content as string).trim()) {
|
|
143
|
+
result.push({
|
|
144
|
+
type: "text",
|
|
145
|
+
role: message.role as string,
|
|
146
|
+
content: message.content as string,
|
|
147
|
+
stop_reason: finishReason,
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Tool calls
|
|
152
|
+
const toolCalls = message.tool_calls as Array<Record<string, unknown>> || [];
|
|
153
|
+
if (toolCalls.length > 0) {
|
|
154
|
+
result.push({
|
|
155
|
+
type: "tool_call",
|
|
156
|
+
role: message.role as string,
|
|
157
|
+
tools: toolCalls.map((tc) => ({
|
|
158
|
+
id: tc.id as string,
|
|
159
|
+
name: (tc.function as Record<string, string>)?.name || "",
|
|
160
|
+
arguments: JSON.parse((tc.function as Record<string, string>)?.arguments || "{}"),
|
|
161
|
+
})),
|
|
162
|
+
stop_reason: "tool",
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return result;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Run inference through A3M Router
|
|
172
|
+
*/
|
|
173
|
+
async infer(
|
|
174
|
+
stepID: string,
|
|
175
|
+
messages: Array<{
|
|
176
|
+
type: string;
|
|
177
|
+
role?: string;
|
|
178
|
+
content?: string;
|
|
179
|
+
tool?: { id?: string; name?: string };
|
|
180
|
+
tools?: unknown[];
|
|
181
|
+
}>,
|
|
182
|
+
tools: Array<{
|
|
183
|
+
name: string;
|
|
184
|
+
description?: string;
|
|
185
|
+
parameters?: z.ZodType<unknown>;
|
|
186
|
+
strict?: boolean;
|
|
187
|
+
}>,
|
|
188
|
+
toolChoice: "auto" | "none" | { type: "function"; function: { name: string } } = "auto",
|
|
189
|
+
options: A3MInferenceOptions = {}
|
|
190
|
+
): Promise<{
|
|
191
|
+
output: Array<{ type: string; role?: string; content?: string; tools?: A3MToolCall[]; stop_reason?: string }>;
|
|
192
|
+
raw: Record<string, unknown>;
|
|
193
|
+
}> {
|
|
194
|
+
const body: Record<string, unknown> = {
|
|
195
|
+
model: options.model || this.config.defaultModel || "auto",
|
|
196
|
+
messages: this.convertMessages(messages),
|
|
197
|
+
temperature: options.temperature ?? this.config.temperature,
|
|
198
|
+
max_tokens: options.maxTokens ?? this.config.maxTokens,
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
if (tools.length > 0) {
|
|
202
|
+
body.tools = this.convertTools(tools);
|
|
203
|
+
if (toolChoice === "auto") {
|
|
204
|
+
body.tool_choice = "auto";
|
|
205
|
+
} else if (toolChoice === "none") {
|
|
206
|
+
body.tool_choice = "none";
|
|
207
|
+
} else if (toolChoice && typeof toolChoice === "object") {
|
|
208
|
+
body.tool_choice = {
|
|
209
|
+
type: "function",
|
|
210
|
+
function: { name: toolChoice.function.name },
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
if (options.provider) {
|
|
216
|
+
body.provider = options.provider;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
if (options.stop) {
|
|
220
|
+
body.stop = options.stop;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// Parallel ensemble mode
|
|
224
|
+
if (options.parallel ?? this.config.parallel) {
|
|
225
|
+
body.parallel = true;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const headers: Record<string, string> = {
|
|
229
|
+
"Content-Type": "application/json",
|
|
230
|
+
...this.config.headers,
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
if (this.authKey) {
|
|
234
|
+
headers["Authorization"] = `Bearer ${this.authKey}`;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
try {
|
|
238
|
+
const response = await fetch(this.url, {
|
|
239
|
+
method: "POST",
|
|
240
|
+
headers,
|
|
241
|
+
body: JSON.stringify(body),
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
if (!response.ok) {
|
|
245
|
+
throw new Error(`A3M request failed: ${response.status} ${response.statusText}`);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
const result = (await response.json()) as Record<string, unknown>;
|
|
249
|
+
return {
|
|
250
|
+
output: this.parseResponse(result),
|
|
251
|
+
raw: result,
|
|
252
|
+
};
|
|
253
|
+
} catch (err) {
|
|
254
|
+
throw new Error(`A3M inference error: ${stringifyError(err)}`);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Stream inference through A3M Router
|
|
260
|
+
*/
|
|
261
|
+
async *stream(
|
|
262
|
+
messages: A3MMessage[],
|
|
263
|
+
tools: A3MTool[] = [],
|
|
264
|
+
options: A3MInferenceOptions = {}
|
|
265
|
+
): AsyncGenerator<A3MStreamChunk> {
|
|
266
|
+
const body: Record<string, unknown> = {
|
|
267
|
+
model: options.model || this.config.defaultModel || "auto",
|
|
268
|
+
messages,
|
|
269
|
+
temperature: options.temperature ?? this.config.temperature,
|
|
270
|
+
max_tokens: options.maxTokens ?? this.config.maxTokens,
|
|
271
|
+
stream: true,
|
|
272
|
+
};
|
|
273
|
+
|
|
274
|
+
if (tools.length > 0) {
|
|
275
|
+
body.tools = this.convertTools(tools);
|
|
276
|
+
body.tool_choice = "auto";
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
if (options.provider) {
|
|
280
|
+
body.provider = options.provider;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
const headers: Record<string, string> = {
|
|
284
|
+
"Content-Type": "application/json",
|
|
285
|
+
...this.config.headers,
|
|
286
|
+
};
|
|
287
|
+
|
|
288
|
+
if (this.authKey) {
|
|
289
|
+
headers["Authorization"] = `Bearer ${this.authKey}`;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
try {
|
|
293
|
+
const response = await fetch(this.url, {
|
|
294
|
+
method: "POST",
|
|
295
|
+
headers,
|
|
296
|
+
body: JSON.stringify(body),
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
if (!response.ok) {
|
|
300
|
+
throw new Error(`A3M stream failed: ${response.status} ${response.statusText}`);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
if (!response.body) {
|
|
304
|
+
throw new Error("A3M stream: no response body");
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
const reader = response.body.getReader();
|
|
308
|
+
const decoder = new TextDecoder();
|
|
309
|
+
let buffer = "";
|
|
310
|
+
|
|
311
|
+
while (true) {
|
|
312
|
+
const { done, value } = await reader.read();
|
|
313
|
+
if (done) break;
|
|
314
|
+
|
|
315
|
+
buffer += decoder.decode(value, { stream: true });
|
|
316
|
+
const lines = buffer.split("\n");
|
|
317
|
+
buffer = lines.pop() || "";
|
|
318
|
+
|
|
319
|
+
for (const line of lines) {
|
|
320
|
+
const trimmed = line.trim();
|
|
321
|
+
if (!trimmed || !trimmed.startsWith("data: ")) continue;
|
|
322
|
+
|
|
323
|
+
const data = trimmed.slice(6);
|
|
324
|
+
if (data === "[DONE]") {
|
|
325
|
+
yield { type: "done" };
|
|
326
|
+
return;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
try {
|
|
330
|
+
const chunk = JSON.parse(data) as Record<string, unknown>;
|
|
331
|
+
const delta = chunk.choices?.[0]?.delta as Record<string, unknown>;
|
|
332
|
+
|
|
333
|
+
if (delta?.content) {
|
|
334
|
+
yield { type: "text", content: delta.content as string };
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
if (delta?.tool_calls) {
|
|
338
|
+
for (const tc of delta.tool_calls as Array<Record<string, unknown>>) {
|
|
339
|
+
yield {
|
|
340
|
+
type: "tool_call",
|
|
341
|
+
toolCall: {
|
|
342
|
+
id: tc.id as string,
|
|
343
|
+
name: (tc.function as Record<string, string>)?.name || "",
|
|
344
|
+
arguments: JSON.parse((tc.function as Record<string, string>)?.arguments || "{}"),
|
|
345
|
+
},
|
|
346
|
+
};
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
} catch {
|
|
350
|
+
// Skip malformed JSON
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
yield { type: "done" };
|
|
356
|
+
} catch (err) {
|
|
357
|
+
yield { type: "error", error: stringifyError(err) };
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/**
|
|
362
|
+
* Check if A3M Router is available
|
|
363
|
+
*/
|
|
364
|
+
async healthCheck(): Promise<boolean> {
|
|
365
|
+
try {
|
|
366
|
+
const response = await fetch(`${this.config.baseUrl}/health`, {
|
|
367
|
+
method: "GET",
|
|
368
|
+
});
|
|
369
|
+
return response.ok;
|
|
370
|
+
} catch {
|
|
371
|
+
return false;
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
/**
|
|
377
|
+
* Create an A3M adapter with default settings
|
|
378
|
+
*/
|
|
379
|
+
export function createA3MAdapter(config?: A3MConfig): A3MAdapter {
|
|
380
|
+
return new A3MAdapter(config);
|
|
381
|
+
}
|