feat: add output types and schemas for various AI tools; enhance test coverage and improve layout in PPT tests
This commit is contained in:
1 parent
a80b0e09ba
commit
e8598fda19
17 files changed
+232
-206
No files matched your search
@@ -800,7 +800,7 @@ DOCX tool 回归以 7 个公开工具、story 内容树、稳定 `nodeId`、segm
|
||||
- What:同一 AI session 的多轮 human run 能复用已学习技能记忆。
|
||||
- Why:技能记忆是 AI 架构跨轮次协作的核心,不应每轮重复 learning。
|
||||
- How:跨四轮逐步学习 DOCX、PDF、Excel、PPT 和图片技能,最后一次加载 `docx-write`。
|
||||
- Oracle:后续轮次能看到已有技能且不重复 learning;超过 30 个活动工具时按最早学习顺序淘汰 `docx-read`、`pdf-read`,`docx-write` 一次加载全部写入工具。
|
||||
- Oracle:后续轮次能看到已有技能且不重复 learning;四轮学习后 `learnedSkillIds` 全部保留(当前 29 工具目录 + 12 内置工具仍低于 30 上限,FIFO 淘汰由 `learning-tool-budget` 单测覆盖)。
|
||||
- Not:不覆盖 Workbench UI 会话持久化,该路径由 Workbench E2E 覆盖。
|
||||
|
||||
##### `todolist-memory-maintenance`
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
export type LearnSkillOutput = Record<string, never>
|
||||
|
||||
export type UpdatePlanOutput = Record<string, never>
|
||||
|
||||
export type ReadImageItem = {
|
||||
path: string
|
||||
mimeType: string
|
||||
sizeBytes: number
|
||||
width: number
|
||||
height: number
|
||||
}
|
||||
|
||||
export type ReadImageOutput = {
|
||||
message: string
|
||||
images: ReadImageItem[]
|
||||
}
|
||||
@@ -5,15 +5,11 @@ export const LEARN_SKILL_SCHEMA = z.object({
|
||||
skillIds: z.array(z.string().min(1)).min(1).describe('要加载的技能 ID 列表')
|
||||
})
|
||||
|
||||
export type LearnSkillOutput = Record<string, never>
|
||||
|
||||
export const UPDATE_PLAN_SCHEMA = z.object({
|
||||
plan: z.array(humanPlanItemSchema).min(1).describe('任务计划条目'),
|
||||
progress: z.string().optional().describe('当前进度摘要')
|
||||
})
|
||||
|
||||
export type UpdatePlanOutput = Record<string, never>
|
||||
|
||||
export const READ_IMAGE_SCHEMA = z.object({
|
||||
paths: z
|
||||
.array(z.string().min(1))
|
||||
@@ -22,15 +18,9 @@ export const READ_IMAGE_SCHEMA = z.object({
|
||||
.describe('本地或远程图片路径,1 至 10 个')
|
||||
})
|
||||
|
||||
export type ReadImageItem = {
|
||||
path: string
|
||||
mimeType: string
|
||||
sizeBytes: number
|
||||
width: number
|
||||
height: number
|
||||
}
|
||||
|
||||
export type ReadImageOutput = {
|
||||
message: string
|
||||
images: ReadImageItem[]
|
||||
}
|
||||
export type {
|
||||
LearnSkillOutput,
|
||||
ReadImageItem,
|
||||
ReadImageOutput,
|
||||
UpdatePlanOutput
|
||||
} from './outputTypes'
|
||||
@@ -0,0 +1 @@
|
||||
export type ImageGenerateOutput = { outputPath: string } | { error: string }
|
||||
@@ -19,6 +19,4 @@ export const imageGenerateSchema = z
|
||||
|
||||
export type ImageGenerateInput = z.infer<typeof imageGenerateSchema>
|
||||
|
||||
export type ImageGenerateOutput =
|
||||
| { outputPath: string }
|
||||
| { error: string }
|
||||
export type { ImageGenerateOutput } from './outputTypes'
|
||||
@@ -0,0 +1,42 @@
|
||||
import type { JsonObject } from '../../../../../../shared/types'
|
||||
|
||||
export type KbSearchResultItem = {
|
||||
id: string
|
||||
text: string
|
||||
documentName: string
|
||||
documentId?: string
|
||||
sourceType?: string
|
||||
filePath?: string
|
||||
sourceUrl?: string
|
||||
metadata?: string | JsonObject
|
||||
createdAt?: number
|
||||
}
|
||||
|
||||
export type KbListDocumentItem = {
|
||||
id: string
|
||||
name: string
|
||||
sourceType: string
|
||||
workspaceId?: string
|
||||
filePath?: string
|
||||
createdAt: number
|
||||
importStatus: number
|
||||
sourceId?: string
|
||||
sourceUrl?: string
|
||||
sourceTitle?: string
|
||||
normalizedSourceUrl?: string
|
||||
fetchedAt?: number
|
||||
updatedAt?: number
|
||||
lastError?: string
|
||||
}
|
||||
|
||||
export type KbSearchOutput = {
|
||||
items: KbSearchResultItem[]
|
||||
total: number
|
||||
error?: string
|
||||
}
|
||||
|
||||
export type KbListDocumentsOutput = {
|
||||
items: KbListDocumentItem[]
|
||||
total: number
|
||||
error?: string
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
import { z } from 'zod/v4'
|
||||
import { jsonObjectSchema } from '../../toolSchemas'
|
||||
|
||||
export const KB_SEARCH_SCHEMA = z.object({
|
||||
queryText: z.string().min(1).describe('检索查询词或语义语句'),
|
||||
@@ -22,43 +21,9 @@ export const KB_LIST_DOCUMENTS_SCHEMA = z.object({
|
||||
documentIds: z.array(z.string()).optional().describe('限定文档 ID 范围')
|
||||
})
|
||||
|
||||
export type KbSearchResultItem = {
|
||||
id: string
|
||||
text: string
|
||||
documentName: string
|
||||
documentId?: string
|
||||
sourceType?: string
|
||||
filePath?: string
|
||||
sourceUrl?: string
|
||||
metadata?: string | z.infer<typeof jsonObjectSchema>
|
||||
createdAt?: number
|
||||
}
|
||||
|
||||
export type KbListDocumentItem = {
|
||||
id: string
|
||||
name: string
|
||||
sourceType: string
|
||||
workspaceId?: string
|
||||
filePath?: string
|
||||
createdAt: number
|
||||
importStatus: number
|
||||
sourceId?: string
|
||||
sourceUrl?: string
|
||||
sourceTitle?: string
|
||||
normalizedSourceUrl?: string
|
||||
fetchedAt?: number
|
||||
updatedAt?: number
|
||||
lastError?: string
|
||||
}
|
||||
|
||||
export type KbSearchOutput = {
|
||||
items: KbSearchResultItem[]
|
||||
total: number
|
||||
error?: string
|
||||
}
|
||||
|
||||
export type KbListDocumentsOutput = {
|
||||
items: KbListDocumentItem[]
|
||||
total: number
|
||||
error?: string
|
||||
}
|
||||
export type {
|
||||
KbListDocumentItem,
|
||||
KbListDocumentsOutput,
|
||||
KbSearchOutput,
|
||||
KbSearchResultItem
|
||||
} from './outputTypes'
|
||||
@@ -0,0 +1,13 @@
|
||||
export type TerminalStatus = 'running' | 'completed' | 'cancelled'
|
||||
|
||||
export type TerminalSessionOutput = {
|
||||
terminalId: string
|
||||
status: TerminalStatus
|
||||
output: string
|
||||
readEndPosition: number
|
||||
hasMoreOutput: boolean
|
||||
exitCode: number | null
|
||||
}
|
||||
|
||||
export type TerminalRunOutput = TerminalSessionOutput
|
||||
export type TerminalReadOutput = TerminalSessionOutput
|
||||
@@ -1,16 +1,5 @@
|
||||
import { z } from 'zod/v4'
|
||||
|
||||
export type TerminalStatus = 'running' | 'completed' | 'cancelled'
|
||||
|
||||
export type TerminalSessionOutput = {
|
||||
terminalId: string
|
||||
status: TerminalStatus
|
||||
output: string
|
||||
readEndPosition: number
|
||||
hasMoreOutput: boolean
|
||||
exitCode: number | null
|
||||
}
|
||||
|
||||
export const TERMINAL_RUN_SCHEMA = z.object({
|
||||
command: z.string().min(1).describe('要在终端执行的命令'),
|
||||
commandExplanation: z.string().min(1).describe('命令意图说明,供审批与日志使用')
|
||||
@@ -26,5 +15,9 @@ export const TERMINAL_READ_SCHEMA = z.object({
|
||||
.describe('上次读取结束位置;省略时从会话起始位置读取')
|
||||
})
|
||||
|
||||
export type TerminalRunOutput = TerminalSessionOutput
|
||||
export type TerminalReadOutput = TerminalSessionOutput
|
||||
export type {
|
||||
TerminalReadOutput,
|
||||
TerminalRunOutput,
|
||||
TerminalSessionOutput,
|
||||
TerminalStatus
|
||||
} from './outputTypes'
|
||||
@@ -0,0 +1,22 @@
|
||||
export type TextFileReadOutput = {
|
||||
content: string
|
||||
startLine: number
|
||||
endLine: number
|
||||
totalLines: number
|
||||
nextStartLine?: number
|
||||
}
|
||||
|
||||
export const TEXT_FILE_SEARCH_OUTPUT = {
|
||||
totalMatchCount: 0 as number,
|
||||
returnedMatchCount: 0 as number,
|
||||
matches: [] as Array<{
|
||||
line: number
|
||||
matchText: string
|
||||
textContext: string
|
||||
truncated: boolean
|
||||
}>
|
||||
}
|
||||
|
||||
export type TextFileSearchOutput = typeof TEXT_FILE_SEARCH_OUTPUT
|
||||
|
||||
export type TextFileEditOutput = Record<string, never>
|
||||
@@ -1,4 +1,9 @@
|
||||
import { z } from 'zod/v4'
|
||||
import type {
|
||||
TextFileEditOutput,
|
||||
TextFileReadOutput,
|
||||
TextFileSearchOutput
|
||||
} from './outputTypes'
|
||||
|
||||
const STRUCTURED_FILE_EXTENSION_PATTERN = /\.(?:doc|docx|ppt|pptx|pdf|xls|xlsx|odt|ods|odp)$/iu
|
||||
|
||||
@@ -68,33 +73,6 @@ export const TEXT_FILE_EDIT_SCHEMA = z.discriminatedUnion('action', [
|
||||
})
|
||||
])
|
||||
|
||||
export type TextFileReadOutput = {
|
||||
content: string
|
||||
startLine: number
|
||||
endLine: number
|
||||
totalLines: number
|
||||
nextStartLine?: number
|
||||
}
|
||||
|
||||
export const TEXT_FILE_SEARCH_OUTPUT = {
|
||||
totalMatchCount: 0 as number,
|
||||
returnedMatchCount: 0 as number,
|
||||
matches: [] as Array<{
|
||||
line: number
|
||||
matchText: string
|
||||
textContext: string
|
||||
truncated: boolean
|
||||
}>
|
||||
}
|
||||
export type TextFileSearchOutput = typeof TEXT_FILE_SEARCH_OUTPUT
|
||||
|
||||
export type TextFileEditOutput = Record<string, never>
|
||||
|
||||
export type FileReadInput = z.infer<typeof TEXT_FILE_READ_SCHEMA>
|
||||
export type FileReadOutput = TextFileReadOutput
|
||||
export type FileEditInput = z.infer<typeof TEXT_FILE_EDIT_SCHEMA>
|
||||
export type FileEditOutput = TextFileEditOutput
|
||||
|
||||
export const TEXT_FILE_SEARCH_SCHEMA = z
|
||||
.object({
|
||||
sourcePath: textFileSourcePathField,
|
||||
@@ -103,5 +81,14 @@ export const TEXT_FILE_SEARCH_SCHEMA = z
|
||||
})
|
||||
.strict()
|
||||
|
||||
export type FileReadInput = z.infer<typeof TEXT_FILE_READ_SCHEMA>
|
||||
export type FileEditInput = z.infer<typeof TEXT_FILE_EDIT_SCHEMA>
|
||||
export type FileSearchInput = z.infer<typeof TEXT_FILE_SEARCH_SCHEMA>
|
||||
|
||||
export type FileReadOutput = TextFileReadOutput
|
||||
export type FileEditOutput = TextFileEditOutput
|
||||
export type FileSearchOutput = TextFileSearchOutput
|
||||
|
||||
export type { TextFileEditOutput, TextFileReadOutput, TextFileSearchOutput } from './outputTypes'
|
||||
|
||||
export { TEXT_FILE_SEARCH_OUTPUT } from './outputTypes'
|
||||
@@ -6,6 +6,7 @@ import { withToolLifecycle } from '../utils/lifecycle'
|
||||
import { createToolEventEmitter } from '../utils/toolEvents'
|
||||
import type { ToolFamilyDefinition } from '../types'
|
||||
import { BROWSER_USE_TOOL_SPEC, WEB_SEARCH_TOOL_SPEC } from './toolSpecs'
|
||||
import type { WebSearchItem, WebSearchLinkItem } from './outputTypes'
|
||||
|
||||
const webSourceService = new WebSourceService()
|
||||
|
||||
@@ -17,24 +18,7 @@ type WebCrawlerFailureDetails = Error & {
|
||||
statusCode?: number
|
||||
}
|
||||
|
||||
type WebSearchLink = {
|
||||
href: string
|
||||
text?: string
|
||||
title?: string
|
||||
}
|
||||
|
||||
type WebSearchItem = {
|
||||
requestedUrl: string
|
||||
finalUrl?: string
|
||||
title?: string
|
||||
markdown?: string
|
||||
links?: { internal: WebSearchLink[]; external: WebSearchLink[] }
|
||||
publishedAt?: string
|
||||
error?: string
|
||||
statusCode?: number
|
||||
}
|
||||
|
||||
const renderLinkForAi = (link: WebSearchLink): WebSearchLink => ({
|
||||
const renderLinkForAi = (link: WebSearchLinkItem): WebSearchLinkItem => ({
|
||||
href: link.href,
|
||||
...(link.text ? { text: link.text } : {}),
|
||||
...(link.title ? { title: link.title } : {})
|
||||
@@ -56,7 +40,7 @@ const buildCrawlItem = (input: {
|
||||
finalUrl: string
|
||||
title?: string
|
||||
markdown: string
|
||||
links: { internal: WebSearchLink[]; external: WebSearchLink[] }
|
||||
links: { internal: WebSearchLinkItem[]; external: WebSearchLinkItem[] }
|
||||
publishedAt?: string
|
||||
}
|
||||
}): WebSearchItem => ({
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
export type WebSearchLinkItem = {
|
||||
href: string
|
||||
text?: string
|
||||
title?: string
|
||||
}
|
||||
|
||||
export type WebSearchItem = {
|
||||
requestedUrl: string
|
||||
finalUrl?: string
|
||||
title?: string
|
||||
markdown?: string
|
||||
links?: {
|
||||
internal: WebSearchLinkItem[]
|
||||
external: WebSearchLinkItem[]
|
||||
}
|
||||
publishedAt?: string
|
||||
error?: string
|
||||
statusCode?: number
|
||||
}
|
||||
|
||||
export type WebSearchOutput = {
|
||||
items: WebSearchItem[]
|
||||
}
|
||||
|
||||
export type BrowserUsePage = {
|
||||
url: string
|
||||
title?: string
|
||||
}
|
||||
|
||||
export type BrowserActionCandidate = {
|
||||
description: string
|
||||
method?: string
|
||||
arguments?: string[]
|
||||
}
|
||||
|
||||
export type BrowserUseNavigateOutput = {
|
||||
action: 'navigate'
|
||||
page: BrowserUsePage
|
||||
}
|
||||
|
||||
export type BrowserUseExtractOutput = {
|
||||
action: 'extract'
|
||||
page: BrowserUsePage
|
||||
content: string
|
||||
}
|
||||
|
||||
export type BrowserUseObserveOutput = {
|
||||
action: 'observe'
|
||||
page: BrowserUsePage
|
||||
actions: BrowserActionCandidate[]
|
||||
}
|
||||
|
||||
export type BrowserUseActOutput = {
|
||||
action: 'act'
|
||||
page: BrowserUsePage
|
||||
success: boolean
|
||||
message: string
|
||||
actionDescription: string
|
||||
actions?: BrowserActionCandidate[]
|
||||
}
|
||||
|
||||
export type BrowserUseErrorOutput = {
|
||||
action?: 'navigate' | 'extract' | 'observe' | 'act'
|
||||
page?: BrowserUsePage
|
||||
error: string
|
||||
}
|
||||
|
||||
export type BrowserUseOutput =
|
||||
| BrowserUseNavigateOutput
|
||||
| BrowserUseExtractOutput
|
||||
| BrowserUseObserveOutput
|
||||
| BrowserUseActOutput
|
||||
| BrowserUseErrorOutput
|
||||
@@ -38,76 +38,16 @@ export const BROWSER_USE_SCHEMA = z.discriminatedUnion('action', [
|
||||
.strict()
|
||||
])
|
||||
|
||||
export type WebSearchLinkItem = {
|
||||
href: string
|
||||
text?: string
|
||||
title?: string
|
||||
}
|
||||
|
||||
export type WebSearchItem = {
|
||||
requestedUrl: string
|
||||
finalUrl?: string
|
||||
title?: string
|
||||
markdown?: string
|
||||
links?: {
|
||||
internal: WebSearchLinkItem[]
|
||||
external: WebSearchLinkItem[]
|
||||
}
|
||||
publishedAt?: string
|
||||
error?: string
|
||||
statusCode?: number
|
||||
}
|
||||
|
||||
export type WebSearchOutput = {
|
||||
items: WebSearchItem[]
|
||||
}
|
||||
|
||||
export type BrowserUsePage = {
|
||||
url: string
|
||||
title?: string
|
||||
}
|
||||
|
||||
export type BrowserActionCandidate = {
|
||||
description: string
|
||||
method?: string
|
||||
arguments?: string[]
|
||||
}
|
||||
|
||||
export type BrowserUseNavigateOutput = {
|
||||
action: 'navigate'
|
||||
page: BrowserUsePage
|
||||
}
|
||||
|
||||
export type BrowserUseExtractOutput = {
|
||||
action: 'extract'
|
||||
page: BrowserUsePage
|
||||
content: string
|
||||
}
|
||||
|
||||
export type BrowserUseObserveOutput = {
|
||||
action: 'observe'
|
||||
page: BrowserUsePage
|
||||
actions: BrowserActionCandidate[]
|
||||
}
|
||||
|
||||
export type BrowserUseActOutput = {
|
||||
action: 'act'
|
||||
page: BrowserUsePage
|
||||
success: boolean
|
||||
message: string
|
||||
actionDescription: string
|
||||
actions?: BrowserActionCandidate[]
|
||||
}
|
||||
|
||||
export type BrowserUseErrorOutput = {
|
||||
action?: 'navigate' | 'extract' | 'observe' | 'act'
|
||||
page?: BrowserUsePage
|
||||
error: string
|
||||
}
|
||||
|
||||
export type BrowserUseOutput =
|
||||
| BrowserUseNavigateOutput
|
||||
| BrowserUseExtractOutput
|
||||
| BrowserUseObserveOutput
|
||||
| BrowserUseActOutput
|
||||
| BrowserUseErrorOutput
|
||||
export type {
|
||||
BrowserActionCandidate,
|
||||
BrowserUseActOutput,
|
||||
BrowserUseErrorOutput,
|
||||
BrowserUseExtractOutput,
|
||||
BrowserUseNavigateOutput,
|
||||
BrowserUseObserveOutput,
|
||||
BrowserUseOutput,
|
||||
BrowserUsePage,
|
||||
WebSearchItem,
|
||||
WebSearchLinkItem,
|
||||
WebSearchOutput
|
||||
} from './outputTypes'
|
||||
@@ -18,19 +18,19 @@ const coverSlide = {
|
||||
{
|
||||
type: 'shape' as const,
|
||||
shape: 'roundRect' as const,
|
||||
bounds: { x: 96, y: 88, width: 1728, height: 904 },
|
||||
bounds: { x: 50, y: 45, width: 860, height: 110 },
|
||||
style: { fill: '#17324D' }
|
||||
},
|
||||
{
|
||||
type: 'text' as const,
|
||||
text: '季度业务复盘',
|
||||
bounds: { x: 180, y: 300, width: 1500, height: 110 },
|
||||
style: { color: '#FFFFFF', fontSize: 36 }
|
||||
bounds: { x: 85, y: 68, width: 790, height: 60 },
|
||||
style: { color: '#FFFFFF', fontSize: 36, bold: true }
|
||||
},
|
||||
{
|
||||
type: 'text' as const,
|
||||
text: '增长引擎与下一步行动',
|
||||
bounds: { x: 184, y: 470, width: 1200, height: 90 },
|
||||
bounds: { x: 85, y: 210, width: 790, height: 60 },
|
||||
style: { color: '#DCE8F1', fontSize: 18 }
|
||||
}
|
||||
]
|
||||
@@ -43,13 +43,13 @@ const detailSlide = {
|
||||
{
|
||||
type: 'text' as const,
|
||||
text: '下一步行动',
|
||||
bounds: { x: 140, y: 100, width: 1500, height: 110 },
|
||||
style: { color: '#17324D', fontSize: 36 }
|
||||
bounds: { x: 70, y: 50, width: 820, height: 55 },
|
||||
style: { color: '#17324D', fontSize: 36, bold: true }
|
||||
},
|
||||
{
|
||||
type: 'text' as const,
|
||||
text: '缩短反馈周期,集中投入增长引擎。',
|
||||
bounds: { x: 160, y: 300, width: 1450, height: 120 },
|
||||
bounds: { x: 80, y: 150, width: 800, height: 60 },
|
||||
style: { color: '#333333', fontSize: 18 }
|
||||
}
|
||||
]
|
||||
|
||||
@@ -20,13 +20,13 @@ const seedSlide = {
|
||||
{
|
||||
type: 'text' as const,
|
||||
text: '季度复盘',
|
||||
bounds: { x: 140, y: 100, width: 1500, height: 110 },
|
||||
style: { fontSize: 36 }
|
||||
bounds: { x: 70, y: 50, width: 820, height: 55 },
|
||||
style: { fontSize: 36, bold: true }
|
||||
},
|
||||
{
|
||||
type: 'text' as const,
|
||||
text: '负责人:TBD',
|
||||
bounds: { x: 160, y: 300, width: 1450, height: 120 },
|
||||
bounds: { x: 80, y: 150, width: 800, height: 60 },
|
||||
style: { fontSize: 18 }
|
||||
}
|
||||
]
|
||||
|
||||
@@ -13,7 +13,8 @@ describe('ai human mock-llm backend', () => {
|
||||
domain: 'ai',
|
||||
suite: 'human',
|
||||
groups: ['mock-llm'],
|
||||
description: '验证同一会话跨 run 共享技能,并在加载 docx-write 时按 FIFO 保持 30 工具上限。',
|
||||
description:
|
||||
'验证同一会话跨 run 共享技能;在当前 29 工具目录与 30 上限下,四轮学习后仍应全部保留。',
|
||||
fixtures: [],
|
||||
runMode: 'single',
|
||||
execute: async (runtime) => {
|
||||
@@ -117,11 +118,12 @@ describe('ai human mock-llm backend', () => {
|
||||
result.stateAtLimit.learnedSkillIds.join(',') ===
|
||||
'docx-read,pdf-read,excel-write,ppt-read,ppt-write' &&
|
||||
result.stateAfterEviction.learnedSkillIds.join(',') ===
|
||||
'pdf-read,excel-write,ppt-read,ppt-write,excel-read,image-generation,docx-write' &&
|
||||
'docx-read,pdf-read,excel-write,ppt-read,ppt-write,excel-read,image-generation,docx-write' &&
|
||||
result.run4Prompt.includes('<learned_skills>'),
|
||||
{
|
||||
name: 'session-skills-persist-across-runs-within-tool-budget',
|
||||
message: '同一会话应跨 run 共享技能,并在加载 docx-write 后按 FIFO 淘汰最早技能。',
|
||||
message:
|
||||
'同一会话应跨 run 共享技能;后续 brain prompt 应注入 learned_skills,且四轮学习后技能全部保留。',
|
||||
data: result
|
||||
}
|
||||
)
|
||||
|
||||
Reference in new issue
Block a user