♻️ refactor: refactor context engine (#9821)

* refactor context engine

* fix LocalSystem issue

* refactor mcp in local
This commit is contained in:
Arvin Xu
2025-10-21 20:32:42 +08:00
committed by GitHub
parent bbc037912c
commit e99f12f840
11 changed files with 91 additions and 74 deletions

View File

@@ -36,19 +36,9 @@ describe('ContextEngine', () => {
});
const createInitialContext = (): {
initialState: any;
maxTokens: number;
messages: any[];
model: string;
} => ({
initialState: {
messages: [],
model: 'test-model',
provider: 'test-provider',
},
maxTokens: 4000,
messages: [{ content: 'test', role: 'user' }],
model: 'test-model',
});
describe('constructor', () => {
@@ -206,8 +196,7 @@ describe('ContextEngine', () => {
const processor = createMockProcessor('p1');
const engine = new ContextEngine({ pipeline: [processor] });
const input = { ...createInitialContext(), messages: undefined };
const result = await engine.process(input);
const result = await engine.process({ messages: [] });
expect(result.messages).toEqual([]);
});
@@ -216,19 +205,14 @@ describe('ContextEngine', () => {
const processor: ContextProcessor = {
name: 'test',
process: vi.fn(async (context) => {
expect(context.metadata.maxTokens).toBe(4000);
expect(context.metadata.model).toBe('test-model');
expect(context.metadata.customKey).toBe('customValue');
expect(context.metadata).toBeDefined();
return context;
}),
};
const engine = new ContextEngine({ pipeline: [processor] });
await engine.process({
...createInitialContext(),
metadata: { customKey: 'customValue' },
});
await engine.process(createInitialContext());
});
it('should track execution stats', async () => {
@@ -278,12 +262,7 @@ describe('ContextEngine', () => {
pipeline: [processor1, processor2],
});
const input = {
...createInitialContext(),
};
input.initialState = { ...input.initialState, messages: [] };
const result = await engine.process(input);
const result = await engine.process(createInitialContext());
expect(result.isAborted).toBe(true);
expect(result.stats.processedCount).toBe(1);
@@ -333,16 +312,17 @@ describe('ContextEngine', () => {
});
it('should preserve initial state', async () => {
const testContext = createInitialContext();
const processor: ContextProcessor = {
name: 'test',
process: vi.fn(async (context) => {
expect(context.initialState).toEqual(createInitialContext().initialState);
expect(context.initialState.messages).toEqual(testContext.messages);
return context;
}),
};
const engine = new ContextEngine({ pipeline: [processor] });
await engine.process(createInitialContext());
await engine.process(testContext);
});
});

View File

@@ -1,12 +1,6 @@
import debug from 'debug';
import type {
AgentState,
ContextProcessor,
PipelineContext,
PipelineResult,
ProcessorOptions,
} from './types';
import type { ContextProcessor, PipelineContext, PipelineResult, ProcessorOptions } from './types';
import { PipelineError } from './types';
const log = debug('context-engine:ContextEngine');
@@ -70,26 +64,16 @@ export class ContextEngine {
/**
* Execute pipeline processing
*/
async process(input: {
initialState: AgentState;
maxTokens: number;
messages?: Array<any>;
metadata?: Record<string, any>;
model: string;
}): Promise<PipelineResult> {
async process(input: { messages: Array<any> }): Promise<PipelineResult> {
const startTime = Date.now();
const processorDurations: Record<string, number> = {};
// Create initial pipeline context
let context: PipelineContext = {
initialState: input.initialState,
initialState: { messages: input.messages },
isAborted: false,
messages: Array.isArray(input.messages) ? [...input.messages] : [],
metadata: {
maxTokens: input.maxTokens,
model: input.model,
...input.metadata,
},
messages: [...input.messages],
metadata: {},
};
log('Starting pipeline processing');

View File

@@ -60,9 +60,9 @@ export interface PipelineContext {
/** 当前 token 估算值 */
currentTokenCount?: number;
/** 最大 token 限制 */
maxTokens: number;
maxTokens?: number;
/** 模型标识 */
model: string;
model?: string;
};
}

View File

@@ -28,7 +28,7 @@ const TitleTags = memo(() => {
agentSelectors.isAgentConfigLoading(s),
]);
const plugins = useAgentStore(agentSelectors.currentAgentPlugins, isEqual);
const plugins = useAgentStore(agentSelectors.displayableAgentPlugins, isEqual);
const enabledKnowledge = useAgentStore(agentSelectors.currentEnabledKnowledge, isEqual);
const enableHistoryCount = useAgentStore(agentChatConfigSelectors.enableHistoryCount);

View File

@@ -29,7 +29,7 @@ const Preview = memo<PreviewProps>(
({ title, withBackground, withFooter, message, previewId = 'preview' }) => {
const [model, plugins] = useAgentStore((s) => [
agentSelectors.currentAgentModel(s),
agentSelectors.currentAgentPlugins(s),
agentSelectors.displayableAgentPlugins(s),
]);
const [isInbox, description, avatar, backgroundColor] = useSessionStore((s) => [

View File

@@ -22,7 +22,7 @@ const Preview = memo<FieldType & { title?: string }>(
({ title, withSystemRole, withBackground, withFooter }) => {
const [model, plugins, systemRole] = useAgentStore((s) => [
agentSelectors.currentAgentModel(s),
agentSelectors.currentAgentPlugins(s),
agentSelectors.displayableAgentPlugins(s),
agentSelectors.currentAgentSystemRole(s),
]);
const [isInbox, description, avatar, backgroundColor] = useSessionStore((s) => [

View File

@@ -6,12 +6,13 @@ import type { PluginEnableChecker } from '@lobechat/context-engine';
import { ChatCompletionTool, WorkingModel } from '@lobechat/types';
import { LobeChatPluginManifest } from '@lobehub/chat-plugin-sdk';
import { getSearchConfig } from '@/helpers/getSearchConfig';
import { getToolStoreState } from '@/store/tool';
import { pluginSelectors } from '@/store/tool/selectors';
import { WebBrowsingManifest } from '@/tools/web-browsing';
import { getSearchConfig } from '../getSearchConfig';
import { isCanUseFC } from '../isCanUseFC';
import { shouldEnableTool } from '../toolFilters';
/**
* Tools engine configuration options
@@ -58,6 +59,11 @@ export const createChatToolsEngine = (workingModel: WorkingModel) =>
defaultToolIds: [WebBrowsingManifest.identifier],
// Create search-aware enableChecker for this request
enableChecker: ({ pluginId }) => {
// Check platform-specific constraints (e.g., LocalSystem desktop-only)
if (!shouldEnableTool(pluginId)) {
return false;
}
// For WebBrowsingManifest, apply search logic
if (pluginId === WebBrowsingManifest.identifier) {
const searchConfig = getSearchConfig(workingModel.model, workingModel.provider);

View File

@@ -0,0 +1,35 @@
/**
* Shared tool filtering logic used across both runtime (ToolsEngine)
* and display layer (selectors)
*/
import { isDesktop } from '@lobechat/const';
import { LocalSystemManifest } from '@/tools/local-system';
/**
* Check if a tool should be enabled based on platform-specific constraints
* @param toolId - The tool identifier to check
* @returns true if the tool should be enabled, false otherwise
*/
export const shouldEnableTool = (toolId: string): boolean => {
// Filter LocalSystem tool in non-desktop environment
if (toolId === LocalSystemManifest.identifier) {
return isDesktop;
}
// Add more platform-specific filters here as needed
// if (toolId === SomeOtherPlatformSpecificTool.identifier) {
// return someCondition;
// }
return true;
};
/**
* Filter tool IDs based on platform constraints
* @param toolIds - Array of tool identifiers to filter
* @returns Filtered array of tool identifiers
*/
export const filterToolIds = (toolIds: string[]): string[] => {
return toolIds.filter(shouldEnableTool);
};

View File

@@ -1,6 +1,5 @@
import { INBOX_GUIDE_SYSTEMROLE, INBOX_SESSION_ID, isDesktop, isServerMode } from '@lobechat/const';
import {
type AgentState,
ContextEngine,
HistorySummaryProvider,
HistoryTruncateProcessor,
@@ -122,14 +121,7 @@ export const contextEngineering = async ({
],
});
const initialState: AgentState = { messages, model, provider, systemRole, tools };
const result = await pipeline.process({
initialState,
maxTokens: 10_000_000,
messages,
model,
});
const result = await pipeline.process({ messages });
return result.messages;
};

View File

@@ -1,16 +1,16 @@
import { VoiceList } from '@lobehub/tts';
import { INBOX_SESSION_ID } from '@/const/session';
import {
DEFAULT_AGENT_CONFIG,
DEFAULT_MODEL,
DEFAULT_PROVIDER,
DEFAUTT_AGENT_TTS_CONFIG,
} from '@/const/settings';
INBOX_SESSION_ID,
} from '@lobechat/const';
import { KnowledgeItem, KnowledgeType, LobeAgentConfig, LobeAgentTTSConfig } from '@lobechat/types';
import { VoiceList } from '@lobehub/tts';
import { DEFAULT_OPENING_QUESTIONS } from '@/features/AgentSetting/store/selectors';
import { filterToolIds } from '@/helpers/toolFilters';
import { AgentStoreState } from '@/store/agent/initialState';
import { LobeAgentConfig, LobeAgentTTSConfig } from '@/types/agent';
import { KnowledgeItem, KnowledgeType } from '@/types/knowledgeBase';
import { merge } from '@/utils/merge';
const isInboxSession = (s: AgentStoreState) => s.activeId === INBOX_SESSION_ID;
@@ -68,6 +68,15 @@ const currentAgentPlugins = (s: AgentStoreState) => {
return config?.plugins || [];
};
/**
* Get displayable agent plugins by filtering out platform-specific tools
* that shouldn't be shown in the current environment
*/
const displayableAgentPlugins = (s: AgentStoreState) => {
const plugins = currentAgentPlugins(s);
return filterToolIds(plugins);
};
const currentAgentKnowledgeBases = (s: AgentStoreState) => {
const config = currentAgentConfig(s);
@@ -172,6 +181,7 @@ export const agentSelectors = {
currentAgentTTSVoice,
currentEnabledKnowledge,
currentKnowledgeIds,
displayableAgentPlugins,
getAgentConfigByAgentId,
getAgentConfigById,
hasEnabledKnowledge,

View File

@@ -1,5 +1,7 @@
import { LobeToolMeta } from '@lobechat/types';
import { shouldEnableTool } from '@/helpers/toolFilters';
import { DalleManifest } from '@/tools/dalle';
import { LobeToolMeta } from '@/types/tool/tool';
import type { ToolStoreState } from '../../initialState';
@@ -7,10 +9,18 @@ const metaList =
(showDalle?: boolean) =>
(s: ToolStoreState): LobeToolMeta[] =>
s.builtinTools
.filter(
(item) =>
!item.hidden && (!showDalle ? item.identifier !== DalleManifest.identifier : true),
)
.filter((item) => {
// Filter hidden tools
if (item.hidden) return false;
// Filter Dalle if not enabled
if (!showDalle && item.identifier === DalleManifest.identifier) return false;
// Filter platform-specific tools (e.g., LocalSystem desktop-only)
if (!shouldEnableTool(item.identifier)) return false;
return true;
})
.map((t) => ({
author: 'LobeHub',
identifier: t.identifier,