Browse Source

feat: 适配agent接口分为框架、业务两个端口

master
Senor-Liu 1 month ago
parent
commit
046f745411
  1. 3
      web/.vscode/launch.json
  2. 2
      web/client/src/sections/superAgent/actions/agentResources.js
  3. 13
      web/client/src/sections/superAgent/actions/api.js
  4. 6
      web/client/src/sections/superAgent/hooks/useSuperAgentStream.js
  5. 23
      web/client/src/sections/superAgent/utils/agentApi.js
  6. 5
      web/client/src/utils/superAgentApiTable.js
  7. 20
      web/config.cjs
  8. 16
      web/tests/agentApiAdapter.test.mjs
  9. 3
      web/tests/superAgentFlowContract.test.mjs

3
web/.vscode/launch.json

@ -63,7 +63,8 @@
"TIANCHI_REPORT_API": "http://127.0.0.1:8000", "TIANCHI_REPORT_API": "http://127.0.0.1:8000",
"FS_TIANCHI_REPORT_VISIBLE_PHONES": "13800000000,13900000000", "FS_TIANCHI_REPORT_VISIBLE_PHONES": "13800000000,13900000000",
"FS_FINANCE_DEPARTMENT_ID": "152", "FS_FINANCE_DEPARTMENT_ID": "152",
"FS_SUPER_AGENT_API": "http://127.0.0.1:8000", "FS_SUPER_AGENT_BUSINESS_API": "http://127.0.0.1:8000",
"FS_SUPER_AGENT_FRAMEWORK_API": "http://127.0.0.1:8005",
} }
}, },
{ {

2
web/client/src/sections/superAgent/actions/agentResources.js

@ -189,6 +189,7 @@ export const sendThreadCommand = (threadId, body) =>
path: superAgentServiceApiTable.threadCommands, path: superAgentServiceApiTable.threadCommands,
params: { threadId }, params: { threadId },
body, body,
service: "framework",
fallbackMessage: "发送线程命令失败", fallbackMessage: "发送线程命令失败",
}); });
@ -198,5 +199,6 @@ export const cancelThreadRun = (threadId, runId, query = { wait: false, action:
path: superAgentServiceApiTable.threadCancel, path: superAgentServiceApiTable.threadCancel,
params: { threadId, runId }, params: { threadId, runId },
query, query,
service: "framework",
fallbackMessage: "取消运行失败", fallbackMessage: "取消运行失败",
}); });

13
web/client/src/sections/superAgent/actions/api.js

@ -25,6 +25,7 @@ import {
buildSuperAgentHeaders, buildSuperAgentHeaders,
buildSuperAgentPath, buildSuperAgentPath,
buildSuperAgentServiceUrl, buildSuperAgentServiceUrl,
getSuperAgentFrameworkApiUrl,
requestAgentJson, requestAgentJson,
} from "../utils/agentApi"; } from "../utils/agentApi";
import request from "superagent"; import request from "superagent";
@ -130,6 +131,7 @@ export const respondSuperAgentCheckpoint = (options = {}) => async () => {
params: { sessionId }, params: { sessionId },
query: options.query, query: options.query,
body, body,
service: "framework",
fallbackMessage: "提交确认结果失败", fallbackMessage: "提交确认结果失败",
}); });
return { payload: { data } }; return { payload: { data } };
@ -203,6 +205,7 @@ export const cancelSuperAgentSession = async ({ sessionId, userId }) =>
query: { userId }, query: { userId },
fallbackMessage: "取消任务失败", fallbackMessage: "取消任务失败",
keepalive: true, keepalive: true,
service: "framework",
}); });
export const getSuperAgentSessionSectionsDirect = async ({ export const getSuperAgentSessionSectionsDirect = async ({
@ -654,15 +657,21 @@ const requestSuperAgentStream = async ({
traceId, traceId,
logScope, logScope,
sessionId, sessionId,
service = "framework",
}) => { }) => {
const response = await fetch(buildSuperAgentServiceUrl(path), { const response = await fetch(
service === "framework"
? `${getSuperAgentFrameworkApiUrl()}${path}`
: buildSuperAgentServiceUrl(path),
{
method: "POST", method: "POST",
headers: await buildLegacyCompatibleHeaders({ headers: await buildLegacyCompatibleHeaders({
"Content-Type": "application/json", "Content-Type": "application/json",
}), }),
body: JSON.stringify(body), body: JSON.stringify(body),
signal, signal,
}); }
);
superAgentLog(logScope, "response.headers", { superAgentLog(logScope, "response.headers", {
traceId, traceId,
sessionId, sessionId,

6
web/client/src/sections/superAgent/hooks/useSuperAgentStream.js

@ -22,7 +22,7 @@ import { ProtocolSseTransportAdapter } from "@langchain/langgraph-sdk";
import { ROOT_PUMP_CHANNELS } from "@langchain/langgraph-sdk/stream"; import { ROOT_PUMP_CHANNELS } from "@langchain/langgraph-sdk/stream";
import { getCompatibleToken } from "../../../utils/authCompat"; import { getCompatibleToken } from "../../../utils/authCompat";
import { import {
getSuperAgentApiUrl, getSuperAgentFrameworkApiUrl,
withAgentAuthorization, withAgentAuthorization,
} from "../utils/agentApi"; } from "../utils/agentApi";
@ -497,7 +497,7 @@ const useSuperAgentStream = ({
const eventTransport = useMemo( const eventTransport = useMemo(
() => { () => {
const transport = new SingleSubscriberSseTransportAdapter({ const transport = new SingleSubscriberSseTransportAdapter({
apiUrl: getSuperAgentApiUrl(), apiUrl: getSuperAgentFrameworkApiUrl(),
defaultHeaders, defaultHeaders,
fetch: fetchUseStreamTransport, fetch: fetchUseStreamTransport,
maxReconnectAttempts: 5, maxReconnectAttempts: 5,
@ -520,7 +520,7 @@ const useSuperAgentStream = ({
); );
const stream = useStream({ const stream = useStream({
assistantId: "freesun_agent", assistantId: "freesun_agent",
apiUrl: getSuperAgentApiUrl(), apiUrl: getSuperAgentFrameworkApiUrl(),
threadId: activeSessionId || null, threadId: activeSessionId || null,
defaultHeaders, defaultHeaders,
callerOptions, callerOptions,

23
web/client/src/sections/superAgent/utils/agentApi.js

@ -12,7 +12,12 @@
*/ */
"use strict"; "use strict";
import { superAgentServiceProxyMatch } from "../../../utils/superAgentApiTable"; import {
superAgentBusinessService,
superAgentFrameworkProxyMatch,
superAgentFrameworkService,
superAgentServiceProxyMatch,
} from "../../../utils/superAgentApiTable";
import { getCompatibleToken } from "../../../utils/authCompat"; import { getCompatibleToken } from "../../../utils/authCompat";
import { import {
SuperAgentApiError, SuperAgentApiError,
@ -38,14 +43,19 @@ export {
parseAgentPage, parseAgentPage,
} from "./agentApiParse"; } from "./agentApiParse";
export const buildSuperAgentServiceUrl = (path = "") => { export const buildSuperAgentServiceUrl = (path = "", service = superAgentBusinessService) => {
const rawPath = String(path || ""); const rawPath = String(path || "");
const servicePath = rawPath && !rawPath.startsWith("/") ? `/${rawPath}` : rawPath; const servicePath = rawPath && !rawPath.startsWith("/") ? `/${rawPath}` : rawPath;
const origin = typeof window !== "undefined" ? window.location.origin : ""; const origin = typeof window !== "undefined" ? window.location.origin : "";
return `${origin}${superAgentServiceProxyMatch}${servicePath}`; const proxy = service === superAgentFrameworkService
? superAgentFrameworkProxyMatch
: superAgentServiceProxyMatch;
return `${origin}${proxy}${servicePath}`;
}; };
export const getSuperAgentApiUrl = () => buildSuperAgentServiceUrl(""); export const getSuperAgentApiUrl = () => buildSuperAgentServiceUrl("");
export const getSuperAgentFrameworkApiUrl = () =>
buildSuperAgentServiceUrl("", superAgentFrameworkService);
export const buildSuperAgentPath = (path, params = {}) => { export const buildSuperAgentPath = (path, params = {}) => {
let nextPath = String(path || ""); let nextPath = String(path || "");
@ -157,6 +167,7 @@ export const requestAgentJson = async ({
keepalive = false, keepalive = false,
idempotencyKey, idempotencyKey,
retryOnUnauthorized = true, retryOnUnauthorized = true,
service = superAgentBusinessService,
} = {}) => { } = {}) => {
const resolvedPath = buildSuperAgentPath(path, params); const resolvedPath = buildSuperAgentPath(path, params);
const search = new URLSearchParams(); const search = new URLSearchParams();
@ -176,7 +187,10 @@ export const requestAgentJson = async ({
}); });
const response = await fetch( const response = await fetch(
buildSuperAgentServiceUrl(`${resolvedPath}${queryString ? `?${queryString}` : ""}`), buildSuperAgentServiceUrl(
`${resolvedPath}${queryString ? `?${queryString}` : ""}`,
service
),
{ {
method: methodName, method: methodName,
headers, headers,
@ -198,6 +212,7 @@ export const requestAgentJson = async ({
keepalive, keepalive,
idempotencyKey: key, idempotencyKey: key,
retryOnUnauthorized: false, retryOnUnauthorized: false,
service,
}); });
} }

5
web/client/src/utils/superAgentApiTable.js

@ -1,7 +1,12 @@
"use strict"; "use strict";
export const superAgentApiBaseEnvKey = "FS_SUPER_AGENT_API"; export const superAgentApiBaseEnvKey = "FS_SUPER_AGENT_API";
export const superAgentBusinessApiEnvKey = "FS_SUPER_AGENT_BUSINESS_API";
export const superAgentFrameworkApiEnvKey = "FS_SUPER_AGENT_FRAMEWORK_API";
export const superAgentServiceProxyMatch = "/_super-agent"; export const superAgentServiceProxyMatch = "/_super-agent";
export const superAgentFrameworkProxyMatch = "/_super-agent-framework";
export const superAgentBusinessService = "business";
export const superAgentFrameworkService = "framework";
export const superAgentServiceApiTable = { export const superAgentServiceApiTable = {
tasksets: "/tasksets", tasksets: "/tasksets",

20
web/config.cjs

@ -5,9 +5,17 @@ if (!process.env.FS_QINIU_DOMAIN) process.env.FS_QINIU_DOMAIN = "http://test.res
const hmrHost = process.env.HMR_HOST || "127.0.0.1"; const hmrHost = process.env.HMR_HOST || "127.0.0.1";
const hmrPort = Number(process.env.HMR_PORT || 24678); const hmrPort = Number(process.env.HMR_PORT || 24678);
const hmrClientPort = Number(process.env.HMR_CLIENT_PORT || hmrPort); const hmrClientPort = Number(process.env.HMR_CLIENT_PORT || hmrPort);
const superAgentTarget = (process.env.FS_SUPER_AGENT_API || "http://127.0.0.1:8000") const normalizeSuperAgentTarget = (target, fallback) => String(target || fallback)
.replace(/\/api\/v1\/?$/i, "") .replace(/\/api\/v1\/?$/i, "")
.replace(/\/+$/, ""); .replace(/\/+$/, "");
const superAgentBusinessTarget = normalizeSuperAgentTarget(
process.env.FS_SUPER_AGENT_BUSINESS_API || process.env.FS_SUPER_AGENT_API,
"http://127.0.0.1:8000"
);
const superAgentFrameworkTarget = normalizeSuperAgentTarget(
process.env.FS_SUPER_AGENT_FRAMEWORK_API,
"http://127.0.0.1:8005"
);
module.exports = { module.exports = {
env: process.env.NODE_ENV || "development", // 运行环境 development | production env: process.env.NODE_ENV || "development", // 运行环境 development | production
@ -23,11 +31,19 @@ module.exports = {
{ {
// SuperAgent 前端接口转发到 Agent 服务,避免浏览器跨域。 // SuperAgent 前端接口转发到 Agent 服务,避免浏览器跨域。
path: "/_super-agent", path: "/_super-agent",
target: superAgentTarget, target: superAgentBusinessTarget,
changeOrigin: true, changeOrigin: true,
rewrite: (path) => path.replace(/^\/_super-agent/, ""), rewrite: (path) => path.replace(/^\/_super-agent/, ""),
logs: process.env.NODE_ENV == "development", logs: process.env.NODE_ENV == "development",
}, },
{
// SuperAgent 框架线程/运行接口转发到 8005。
path: "/_super-agent-framework",
target: superAgentFrameworkTarget,
changeOrigin: true,
rewrite: (path) => path.replace(/^\/_super-agent-framework/, ""),
logs: process.env.NODE_ENV == "development",
},
{ {
// 保留旧版 SuperAgent API 代理供兼容调用。 // 保留旧版 SuperAgent API 代理供兼容调用。
path: "/_api/super-agent", path: "/_api/super-agent",

16
web/tests/agentApiAdapter.test.mjs

@ -14,13 +14,19 @@ const {
parseAgentPage, parseAgentPage,
shouldSendIdempotencyKey, shouldSendIdempotencyKey,
} = await import("../client/src/sections/superAgent/utils/agentApiParse.js"); } = await import("../client/src/sections/superAgent/utils/agentApiParse.js");
const { superAgentServiceApiTable } = await import("../client/src/utils/superAgentApiTable.js"); const {
superAgentFrameworkProxyMatch,
superAgentServiceProxyMatch,
superAgentServiceApiTable,
} = await import("../client/src/utils/superAgentApiTable.js");
const { const {
isUploadIntentExpired, isUploadIntentExpired,
normalizeAttachmentUploadIntent, normalizeAttachmentUploadIntent,
} = await import("../client/src/sections/superAgent/utils/attachmentUpload.js"); } = await import("../client/src/sections/superAgent/utils/attachmentUpload.js");
test("new api table uses live agent paths", () => { test("new api table uses live agent paths", () => {
assert.equal(superAgentServiceProxyMatch, "/_super-agent");
assert.equal(superAgentFrameworkProxyMatch, "/_super-agent-framework");
assert.equal(superAgentServiceApiTable.tasksets, "/tasksets"); assert.equal(superAgentServiceApiTable.tasksets, "/tasksets");
assert.equal(superAgentServiceApiTable.conversations, "/conversations"); assert.equal(superAgentServiceApiTable.conversations, "/conversations");
assert.equal( assert.equal(
@ -82,6 +88,7 @@ test("adapter source exchanges login token for agent jwt and omits identity head
const protocol = read("..", "api", "app", "controllers", "superAgentProtocol.js"); const protocol = read("..", "api", "app", "controllers", "superAgentProtocol.js");
const actions = read("client", "src", "sections", "superAgent", "actions", "api.js"); const actions = read("client", "src", "sections", "superAgent", "actions", "api.js");
const resources = read("client", "src", "sections", "superAgent", "actions", "agentResources.js"); const resources = read("client", "src", "sections", "superAgent", "actions", "agentResources.js");
const config = read("config.cjs");
const chatHook = read("client", "src", "sections", "superAgent", "hooks", "useSuperAgentChat.js"); const chatHook = read("client", "src", "sections", "superAgent", "hooks", "useSuperAgentChat.js");
const uploadUtils = read("client", "src", "sections", "superAgent", "utils", "attachmentUpload.js"); const uploadUtils = read("client", "src", "sections", "superAgent", "utils", "attachmentUpload.js");
assert.doesNotMatch(table, /\/api\/v1\/tasks/); assert.doesNotMatch(table, /\/api\/v1\/tasks/);
@ -98,6 +105,13 @@ test("adapter source exchanges login token for agent jwt and omits identity head
assert.match(protocol, /\/threads\/\$\{encodeURIComponent/); assert.match(protocol, /\/threads\/\$\{encodeURIComponent/);
assert.match(actions, /requestAgentJson/); assert.match(actions, /requestAgentJson/);
assert.match(resources, /createAttachmentUploadIntent/); assert.match(resources, /createAttachmentUploadIntent/);
assert.match(resources, /service: "framework"/);
assert.match(config, /FS_SUPER_AGENT_BUSINESS_API/);
assert.match(config, /FS_SUPER_AGENT_FRAMEWORK_API/);
assert.match(config, /_super-agent-framework/);
assert.match(agentApi, /buildSuperAgentServiceUrl = \(path = "", service = superAgentBusinessService\)/);
assert.match(agentApi, /superAgentFrameworkProxyMatch/);
assert.match(agentApi, /getSuperAgentFrameworkApiUrl/);
assert.match(resources, /completeAttachmentUpload/); assert.match(resources, /completeAttachmentUpload/);
assert.match(resources, /listAttachments/); assert.match(resources, /listAttachments/);
assert.match(chatHook, /createAttachmentUploadIntent\(conversationId/); assert.match(chatHook, /createAttachmentUploadIntent\(conversationId/);

3
web/tests/superAgentFlowContract.test.mjs

@ -18,7 +18,8 @@ const apiRoutes = read("..", "api", "app", "routes", "superAgent.js");
assert.equal(packageJson.dependencies["@langchain/react"], "1.0.29"); assert.equal(packageJson.dependencies["@langchain/react"], "1.0.29");
assert.match(streamHook, /useStream\(\{/); assert.match(streamHook, /useStream\(\{/);
assert.match(streamHook, /apiUrl:\s*getSuperAgentApiUrl\(\)/); assert.match(streamHook, /apiUrl:\s*getSuperAgentFrameworkApiUrl\(\)/);
assert.match(streamHook, /getSuperAgentFrameworkApiUrl/);
assert.doesNotMatch(streamHook, /\/api\/v1/); assert.doesNotMatch(streamHook, /\/api\/v1/);
assert.match(streamHook, /withAgentAuthorization/); assert.match(streamHook, /withAgentAuthorization/);
assert.doesNotMatch(streamHook, /X-User-Id/); assert.doesNotMatch(streamHook, /X-User-Id/);

Loading…
Cancel
Save