mirror of
https://github.com/hellodigua/ChatLab.git
synced 2026-10-25 17:54:34 +08:00
feat(tools): localize tool descriptions by locale for every runtime
Tool and parameter descriptions were handed to the LLM in Chinese on every runtime regardless of locale: the English metadata table in packages/tools/src/tool-metadata.ts had no caller, the desktop translateTool looked up i18n keys that exist in no locale file, and the MCP server had no locale at all. Fill the table for every tool whose definition text is Chinese (37 entries, only translating what the definitions write in Chinese so the two copies cannot drift), and route the four consumers through getLocalizedToolMetadata: the cross-chat agent adapters, the CLI Web agent adapter, the desktop shared tool adapter (replacing the dead translateTool), and the MCP server, which now resolves its locale from config.locale.lang or the system locale for both the standalone binary and the chatlab mcp subcommand. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
e295a80bdf
commit
6e27968bb0
15 files changed
+362
-78
No files matched your search
@@ -227,7 +227,7 @@ export function createCliRunAgentStream(
|
||||
timeFilter: params.timeFilter,
|
||||
maxMessagesLimit: params.maxMessagesLimit,
|
||||
}),
|
||||
{ maxToolResultTokens }
|
||||
{ maxToolResultTokens, locale }
|
||||
)
|
||||
: []
|
||||
|
||||
|
||||
@@ -12,7 +12,12 @@ import type {
|
||||
RawMessage,
|
||||
ToolTimeRange,
|
||||
} from '@openchatlab/tools'
|
||||
import { CoreDataProvider, executeToolForAgent, toAgentToolParameters } from '@openchatlab/tools'
|
||||
import {
|
||||
CoreDataProvider,
|
||||
executeToolForAgent,
|
||||
getLocalizedToolMetadata,
|
||||
toAgentToolParameters,
|
||||
} from '@openchatlab/tools'
|
||||
import type { DatabaseAdapter } from '@openchatlab/core'
|
||||
import {
|
||||
applyPreprocessingPipeline,
|
||||
@@ -56,6 +61,8 @@ export interface ServerToolContext {
|
||||
|
||||
export interface AdaptToolsOptions {
|
||||
maxToolResultTokens?: number
|
||||
/** Request locale; decides whether the LLM sees Chinese or English tool descriptions. */
|
||||
locale?: string
|
||||
}
|
||||
|
||||
export function adaptToolsForAgent(
|
||||
@@ -66,13 +73,14 @@ export function adaptToolsForAgent(
|
||||
const tokenBudget = options?.maxToolResultTokens ?? DEFAULT_MAX_TOOL_RESULT_TOKENS
|
||||
const chartSchemaGateState = createChartSchemaGateState()
|
||||
|
||||
return tools.map((tool) =>
|
||||
wrapWithChartSchemaGate(
|
||||
return tools.map((tool) => {
|
||||
const localized = getLocalizedToolMetadata(tool, options?.locale)
|
||||
return wrapWithChartSchemaGate(
|
||||
{
|
||||
name: tool.name,
|
||||
label: tool.name,
|
||||
description: tool.description,
|
||||
parameters: toAgentToolParameters(tool.inputSchema) as any,
|
||||
description: localized.description,
|
||||
parameters: toAgentToolParameters(localized.inputSchema) as any,
|
||||
executionMode: tool.executionMode,
|
||||
async execute(_toolCallId: string, params: unknown, signal, onUpdate) {
|
||||
const ctx = getContext()
|
||||
@@ -122,5 +130,5 @@ export function adaptToolsForAgent(
|
||||
},
|
||||
chartSchemaGateState
|
||||
)
|
||||
)
|
||||
})
|
||||
}
|
||||
+2
-2
@@ -12,7 +12,7 @@ import {
|
||||
hasPendingElectronDataWarning,
|
||||
verifyCliDataPath,
|
||||
} from '@openchatlab/node-runtime'
|
||||
import { startMcpServer } from 'chatlab-mcp'
|
||||
import { resolveMcpLocale, startMcpServer } from 'chatlab-mcp'
|
||||
import { getVersion } from './version'
|
||||
import { resolveCliPath } from './paths'
|
||||
import { assertCliDataDirCompatible } from './runtime-compat'
|
||||
@@ -43,5 +43,5 @@ export function initMcpRuntime(): DatabaseManager {
|
||||
|
||||
export async function startCliMcpServer(): Promise<void> {
|
||||
const dbManager = initMcpRuntime()
|
||||
await startMcpServer({ version: getVersion(), dbManager })
|
||||
await startMcpServer({ version: getVersion(), dbManager, locale: resolveMcpLocale(loadConfig()) })
|
||||
}
|
||||
@@ -8,7 +8,6 @@
|
||||
import type { AgentTool } from '@openchatlab/node-runtime'
|
||||
import type { ToolContext, TruncationStrategy } from './types'
|
||||
import { isAnalysisToolAllowed } from './tool-filter'
|
||||
import { t as i18nT } from '../../i18n'
|
||||
import { applyPreprocessingPipeline, type PreprocessableMessage } from '@openchatlab/node-runtime'
|
||||
import { aiLogger } from '../logger'
|
||||
import { getSkillConfig } from '../skills/manager'
|
||||
@@ -51,36 +50,6 @@ const TRUNCATION_STRATEGY_MAP = new Map<string, TruncationStrategy>(
|
||||
// 导出类型
|
||||
export * from './types'
|
||||
|
||||
/**
|
||||
* 翻译 AgentTool 的描述(工具级 + 参数级)
|
||||
*
|
||||
* i18n 键命名规则:
|
||||
* - 工具描述:ai.tools.{toolName}.desc
|
||||
* - 参数描述:ai.tools.{toolName}.params.{paramName}
|
||||
*/
|
||||
function translateTool(tool: AgentTool<any>): AgentTool<any> {
|
||||
const name = tool.name
|
||||
|
||||
const descKey = `ai.tools.${name}.desc`
|
||||
const translatedDesc = i18nT(descKey)
|
||||
|
||||
const params = tool.parameters as Record<string, unknown>
|
||||
if (params?.properties && typeof params.properties === 'object') {
|
||||
for (const [paramName, param] of Object.entries(params.properties as Record<string, Record<string, unknown>>)) {
|
||||
const paramKey = `ai.tools.${name}.params.${paramName}`
|
||||
const translated = i18nT(paramKey)
|
||||
if (translated !== paramKey) {
|
||||
param.description = translated
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
...tool,
|
||||
description: translatedDesc !== descKey ? translatedDesc : tool.description,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 预处理包装层
|
||||
* 拦截工具的 execute 结果:如果 details 中包含 rawMessages,
|
||||
@@ -147,7 +116,6 @@ export async function getAllTools(context: ToolContext, allowedTools?: string[])
|
||||
const chartSchemaGateState = createChartSchemaGateState()
|
||||
|
||||
return [...coreTools, ...analysisTools, ...evidenceTools, ...semanticTools]
|
||||
.map(translateTool)
|
||||
.map((t) => wrapWithChartSchemaGate(t, chartSchemaGateState))
|
||||
.map((t) => wrapWithPreprocessing(t, context))
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
|
||||
import {
|
||||
executeToolForAgent,
|
||||
getLocalizedToolMetadata,
|
||||
toAgentToolParameters,
|
||||
type ToolDefinition,
|
||||
type ToolExecutionContext,
|
||||
@@ -58,13 +59,14 @@ export function adaptSharedTool(tool: ToolDefinition): ToolRegistryEntry {
|
||||
category: tool.category ?? 'core',
|
||||
truncationStrategy: tool.truncationStrategy,
|
||||
factory(context: ToolContext): AgentTool<any> {
|
||||
// Tool and parameter descriptions follow the chat locale: Chinese locales keep the definition text,
|
||||
// every other locale gets the English metadata table.
|
||||
const localized = getLocalizedToolMetadata(tool, context.locale)
|
||||
return {
|
||||
name: tool.name,
|
||||
label: tool.name,
|
||||
// 保留英文原始描述作为 fallback:translateTool 会在 i18n key 命中时覆盖为译文,
|
||||
// 缺 key 时回退到此英文描述,避免把裸 i18n key 当作工具描述传给 LLM。
|
||||
description: tool.description,
|
||||
parameters: toAgentToolParameters(tool.inputSchema) as any,
|
||||
description: localized.description,
|
||||
parameters: toAgentToolParameters(localized.inputSchema) as any,
|
||||
executionMode: tool.executionMode,
|
||||
async execute(_toolCallId: string, params: unknown, signal, onUpdate) {
|
||||
return executeToolForAgent(
|
||||
|
||||
Reference in new issue
Block a user