Enable deepseek using the router mode

This commit is contained in:
jinhui.li
2025-02-28 22:47:47 +08:00
parent 6837e0ea4b
commit c9af9e50dc
6 changed files with 291 additions and 407 deletions

1
.gitignore vendored
View File

@@ -1,2 +1,3 @@
node_modules
.env
log.txt

25
CLAUDE.md Normal file
View 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

422
README.md

File diff suppressed because one or more lines are too long

View File

@@ -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'}));
// 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,
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);
},
};
}
// 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,7 +77,9 @@ app.post("/v1/messages", async (req, res) => {
stream: true,
};
if (tools) {
data.tools = tools.map((item) => ({
data.tools = tools
.filter((tool) => !["StickerRequest"].includes(tool.name))
.map((item) => ({
type: "function",
function: {
name: item.name,
@@ -78,7 +88,7 @@ app.post("/v1/messages", async (req, res) => {
},
}));
}
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();

View File

@@ -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
View 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 users 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 users 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);
}
}