Enable deepseek using the router mode
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -1,2 +1,3 @@
|
||||
node_modules
|
||||
.env
|
||||
.env
|
||||
log.txt
|
||||
25
CLAUDE.md
Normal file
25
CLAUDE.md
Normal file
@@ -0,0 +1,25 @@
|
||||
# CLAUDE.md
|
||||
|
||||
## Build/Lint/Test Commands
|
||||
- Install dependencies: `npm i`
|
||||
- Start server: `node index.mjs` (requires OPENAI_API_KEY, OPENAI_BASE_URL, OPENAI_MODEL env vars)
|
||||
- Set environment variables:
|
||||
```shell
|
||||
export DISABLE_PROMPT_CACHING=1
|
||||
export ANTHROPIC_AUTH_TOKEN="test"
|
||||
export ANTHROPIC_BASE_URL="http://127.0.0.1:3456"
|
||||
export API_TIMEOUT_MS=600000
|
||||
```
|
||||
|
||||
## Code Style Guidelines
|
||||
- Follow existing formatting in README.md and other files
|
||||
- Use ES module syntax (`import`/`export`)
|
||||
- Environment variables are uppercase with underscores
|
||||
- API endpoints use `/v1/` prefix
|
||||
- JSON payloads follow strict structure with model, max_tokens, messages, system, etc.
|
||||
- Include type information in JSON payloads where possible
|
||||
- Use descriptive variable names
|
||||
- Keep code modular - separate files for router, index, etc.
|
||||
- Include example usage/documentation in README
|
||||
- Use markdown code blocks for code samples
|
||||
- Document API endpoints and parameters
|
||||
82
index.mjs
82
index.mjs
@@ -1,26 +1,31 @@
|
||||
import express from "express";
|
||||
import { OpenAI } from "openai";
|
||||
import dotenv from 'dotenv';
|
||||
import dotenv from "dotenv";
|
||||
import { existsSync } from "fs";
|
||||
import { writeFile } from "fs/promises";
|
||||
import { Router } from "./router.mjs";
|
||||
|
||||
// Load environment variables
|
||||
dotenv.config();
|
||||
|
||||
const app = express();
|
||||
const port = 3456;
|
||||
app.use(express.json({ limit: "500mb" }));
|
||||
|
||||
app.use(express.json({limit:'500mb'}));
|
||||
let client;
|
||||
if (process.env.ENABLE_ROUTER) {
|
||||
client = new Router();
|
||||
} else {
|
||||
const openai = new OpenAI({
|
||||
apiKey: process.env.OPENAI_API_KEY,
|
||||
baseURL: process.env.OPENAI_BASE_URL,
|
||||
});
|
||||
client = {
|
||||
call: (data) => {
|
||||
data.model = process.env.OPENAI_MODEL;
|
||||
return openai.chat.completions.create(data);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// Read variables from .env file
|
||||
const apiKey = process.env.OPENAI_API_KEY;
|
||||
const baseUrl = process.env.OPENAI_BASE_URL;
|
||||
const defaultModel = process.env.OPENAI_MODEL;
|
||||
|
||||
const chatClient = new OpenAI({
|
||||
apiKey: apiKey,
|
||||
baseURL: baseUrl,
|
||||
});
|
||||
|
||||
// Define POST /v1/messages interface
|
||||
app.post("/v1/messages", async (req, res) => {
|
||||
try {
|
||||
let {
|
||||
@@ -32,6 +37,7 @@ app.post("/v1/messages", async (req, res) => {
|
||||
metadata,
|
||||
tools,
|
||||
} = req.body;
|
||||
|
||||
messages = messages.map((item) => {
|
||||
if (item.content instanceof Array) {
|
||||
return {
|
||||
@@ -43,9 +49,11 @@ app.post("/v1/messages", async (req, res) => {
|
||||
? "text"
|
||||
: it?.type,
|
||||
};
|
||||
if (msg.type === 'text') {
|
||||
msg.text = it?.content ? JSON.stringify(it.content) : it?.text || ""
|
||||
delete msg.content
|
||||
if (msg.type === "text") {
|
||||
msg.text = it?.content
|
||||
? JSON.stringify(it.content)
|
||||
: it?.text || "";
|
||||
delete msg.content;
|
||||
}
|
||||
return msg;
|
||||
}),
|
||||
@@ -57,7 +65,7 @@ app.post("/v1/messages", async (req, res) => {
|
||||
};
|
||||
});
|
||||
const data = {
|
||||
model: defaultModel,
|
||||
model,
|
||||
messages: [
|
||||
...system.map((item) => ({
|
||||
role: "system",
|
||||
@@ -69,16 +77,18 @@ app.post("/v1/messages", async (req, res) => {
|
||||
stream: true,
|
||||
};
|
||||
if (tools) {
|
||||
data.tools = tools.map((item) => ({
|
||||
type: "function",
|
||||
function: {
|
||||
name: item.name,
|
||||
description: item.description,
|
||||
parameters: item.input_schema,
|
||||
},
|
||||
}));
|
||||
data.tools = tools
|
||||
.filter((tool) => !["StickerRequest"].includes(tool.name))
|
||||
.map((item) => ({
|
||||
type: "function",
|
||||
function: {
|
||||
name: item.name,
|
||||
description: item.description,
|
||||
parameters: item.input_schema,
|
||||
},
|
||||
}));
|
||||
}
|
||||
const completion = await chatClient.chat.completions.create(data);
|
||||
const completion = await client.call(data);
|
||||
|
||||
// Set SSE response headers
|
||||
res.setHeader("Content-Type", "text/event-stream");
|
||||
@@ -114,7 +124,6 @@ app.post("/v1/messages", async (req, res) => {
|
||||
|
||||
for await (const chunk of completion) {
|
||||
const delta = chunk.choices[0].delta;
|
||||
// Handle tool call response
|
||||
if (delta.tool_calls && delta.tool_calls.length > 0) {
|
||||
const toolCall = delta.tool_calls[0];
|
||||
|
||||
@@ -284,24 +293,23 @@ app.post("/v1/messages", async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
import { existsSync, writeFileSync } from 'fs';
|
||||
|
||||
async function initializeClaudeConfig() {
|
||||
const homeDir = process.env.HOME;
|
||||
const configPath = `${homeDir}/.claude.json`;
|
||||
|
||||
if (!existsSync(configPath)) {
|
||||
const userID = Array.from({ length: 64 }, () => Math.random().toString(16)[2]).join('');
|
||||
const userID = Array.from(
|
||||
{ length: 64 },
|
||||
() => Math.random().toString(16)[2]
|
||||
).join("");
|
||||
const configContent = {
|
||||
numStartups: 184,
|
||||
autoUpdaterStatus: "enabled",
|
||||
userID,
|
||||
hasCompletedOnboarding: true,
|
||||
lastOnboardingVersion: "0.2.9",
|
||||
projects: {}
|
||||
projects: {},
|
||||
};
|
||||
|
||||
writeFileSync(configPath, JSON.stringify(configContent, null, 2));
|
||||
await writeFile(configPath, JSON.stringify(configContent, null, 2));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -312,4 +320,4 @@ async function run() {
|
||||
console.log(`Example app listening on port ${port}`);
|
||||
});
|
||||
}
|
||||
run()
|
||||
run();
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "claude-code-reverse",
|
||||
"version": "1.0.0",
|
||||
"description": "> You can switch the API endpoint by modifying the ANTHROPIC_BASE_URL environment variable.",
|
||||
"description": "You can switch the API endpoint by modifying the ANTHROPIC_BASE_URL environment variable.",
|
||||
"main": "server.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
|
||||
164
router.mjs
Normal file
164
router.mjs
Normal file
@@ -0,0 +1,164 @@
|
||||
import { OpenAI } from "openai";
|
||||
|
||||
const useToolRouter = {
|
||||
name: "use-tool",
|
||||
description: `This agent can call user-specified tools to perform tasks. The user provides a list of tools to be used, and the agent integrates these tools to complete the specified tasks efficiently. The agent follows user instructions and ensures proper tool utilization for each request`,
|
||||
run(args) {
|
||||
const client = new OpenAI({
|
||||
apiKey: process.env.TOOL_AGENT_API_KEY,
|
||||
baseURL: process.env.TOOL_AGENT_BASE_URL,
|
||||
});
|
||||
return client.chat.completions.create({
|
||||
...args,
|
||||
messages: [
|
||||
...args.messages,
|
||||
{
|
||||
role: "system",
|
||||
content: "You need to select the appropriate tool for the task based on the user’s request. Review the requirements and choose the tool that fits the task best.",
|
||||
},
|
||||
],
|
||||
model: process.env.TOOL_AGENT_MODEL,
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
const coderRouter = {
|
||||
name: "coder",
|
||||
description: `This agent is solely responsible for helping users write code. This agent could not call tools. This agent is used for writing and modifying code when the user provides clear and specific coding requirements. For example, tasks like implementing a quicksort algorithm in JavaScript or creating an HTML layout. If the user's request is unclear or cannot be directly translated into code, please route the task to 'Thinker' first for clarification or further processing.`,
|
||||
run(args) {
|
||||
const client = new OpenAI({
|
||||
apiKey: process.env.CODER_AGENT_API_KEY,
|
||||
baseURL: process.env.CODER_AGENT_BASE_URL,
|
||||
});
|
||||
delete args.tools;
|
||||
args.messages.forEach((item) => {
|
||||
if (Array.isArray(item.content)) {
|
||||
item.content = JSON.stringify(item.content);
|
||||
}
|
||||
});
|
||||
return client.chat.completions.create({
|
||||
...args,
|
||||
messages: [
|
||||
...args.messages,
|
||||
{
|
||||
role: "system",
|
||||
content: "You are a code writer who helps users write code based on their specific requirements. You create algorithms, implement functionality, and build structures according to the clear instructions provided by the user. Your focus is solely on writing code, ensuring that the task is completed accurately and efficiently.",
|
||||
},
|
||||
],
|
||||
model: process.env.CODER_AGENT_MODEL,
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
const thinkRouter = {
|
||||
name: "thinker",
|
||||
description: `This agent is used solely for complex reasoning and thinking tasks. It should not be called for information retrieval or repetitive, frequent requests. Only use this agent for tasks that require deep analysis or problem-solving. If there is an existing result from the Thinker agent, do not call this agent again.`,
|
||||
run(args) {
|
||||
const client = new OpenAI({
|
||||
apiKey: process.env.THINK_AGENT_API_KEY,
|
||||
baseURL: process.env.THINK_AGENT_BASE_URL,
|
||||
});
|
||||
const messages = JSON.parse(JSON.stringify(args.messages));
|
||||
messages.forEach((msg) => {
|
||||
if (Array.isArray(msg.content)) {
|
||||
msg.content = JSON.stringify(msg.content);
|
||||
}
|
||||
});
|
||||
|
||||
let startIdx = messages.findIndex((msg) => msg.role !== "system");
|
||||
if (startIdx === -1) startIdx = messages.length;
|
||||
|
||||
for (let i = startIdx; i < messages.length; i++) {
|
||||
const expectedRole = (i - startIdx) % 2 === 0 ? "user" : "assistant";
|
||||
messages[i].role = expectedRole;
|
||||
}
|
||||
|
||||
if (
|
||||
messages.length > 0 &&
|
||||
messages[messages.length - 1].role === "assistant"
|
||||
) {
|
||||
messages.push({
|
||||
role: "user",
|
||||
content:
|
||||
"Please follow the instructions provided above to resolve the issue.",
|
||||
});
|
||||
}
|
||||
delete args.tools;
|
||||
return client.chat.completions.create({
|
||||
...args,
|
||||
messages,
|
||||
model: process.env.THINK_AGENT_MODEL,
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
export class Router {
|
||||
constructor() {
|
||||
this.routers = [useToolRouter, coderRouter, thinkRouter];
|
||||
this.client = new OpenAI({
|
||||
apiKey: process.env.ROUTER_AGENT_API_KEY,
|
||||
baseURL: process.env.ROUTER_AGENT_BASE_URL,
|
||||
});
|
||||
}
|
||||
async route(args) {
|
||||
const res = await this.client.chat.completions.create({
|
||||
...args,
|
||||
messages: [
|
||||
...args.messages,
|
||||
{
|
||||
role: "system",
|
||||
content: `You are an AI task router that receives user requests and forwards them to the appropriate AI models for task handling. You do not process any requests directly but are responsible for understanding the user's request and choosing the correct router based on the task and necessary steps. The available routers are: ${JSON.stringify(
|
||||
this.routers.map((router) => {
|
||||
return {
|
||||
name: router.name,
|
||||
description: router.description,
|
||||
};
|
||||
})
|
||||
)}. Each router is designated for specific types of tasks, and you ensure that the request is routed accordingly for efficient processing. Use the appropriate router based on the user’s request:
|
||||
|
||||
If external tools are needed to gather more information, use the 'use-tool' router.
|
||||
If the task involves writing code, use the 'coder' router.
|
||||
If deep reasoning or analysis is required to break down steps, use the 'thinker' router.
|
||||
Instead, format your response as a JSON object with one field: 'use' (string)`,
|
||||
},
|
||||
],
|
||||
model: process.env.ROUTER_AGENT_MODEL,
|
||||
stream: false,
|
||||
});
|
||||
let result;
|
||||
try {
|
||||
const text = res.choices[0].message.content;
|
||||
result = JSON.parse(
|
||||
text.slice(text.indexOf("{"), text.lastIndexOf("}") + 1)
|
||||
);
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
res.choices[0].delta = res.choices[0].message;
|
||||
return [res];
|
||||
}
|
||||
const router = this.routers.find((item) => item.name === result.use);
|
||||
if (!router) {
|
||||
res.choices[0].delta = res.choices[0].message;
|
||||
return [res];
|
||||
}
|
||||
if (router.name === "thinker" || router.name === "coder") {
|
||||
const agentResult = await router.run({
|
||||
...args,
|
||||
stream: false,
|
||||
});
|
||||
try {
|
||||
args.messages.push({
|
||||
role: "assistant",
|
||||
content:
|
||||
`${router.name} Agent Result: ` +
|
||||
agentResult.choices[0].message.content,
|
||||
});
|
||||
return await this.route(args);
|
||||
} catch (error) {
|
||||
console.log(agentResult);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
return router.run(args);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user