Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 0 additions & 51 deletions packages/common/js/completion-files/context.md

This file was deleted.

137 changes: 1 addition & 136 deletions packages/common/js/completion.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,7 @@
* A PARTICULAR PURPOSE. SEE THE APPLICABLE LICENSES FOR MORE DETAILS.
*
*/
import { ref } from 'vue'
import { useCanvas, useResource, getMergeMeta, getMetaApi, META_SERVICE } from '@opentiny/tiny-engine-meta-register'
import completion from './completion-files/context.md?raw'
import { useCanvas, useResource } from '@opentiny/tiny-engine-meta-register'
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. 两种模型的表现不一致
  2. 只能补全代码,不支持修改或者删除代码场景
  3. 要加下快捷键触发补全
  4. 当前函数之间是否可以补全调用和跳转
  5. 部分场景的补全有些问题,例如接口有返回补全,但是没有看到提示
image

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1. 模型表现不一致的原因:

  • 模型架构差异:DeepSeek Coder 和 Qwen Coder 虽然都是 FIM(Fill-In-the-Middle)模型,但训练数据、模型参数、推理策略存在差异
  • 上下文理解能力:不同模型对相同 prompt 的理解和响应策略不同,导致补全质量和风格有差异
  • 温度参数影响:当前两个模型使用相同的采样参数,但最优参数可能因模型而异

2. 当前方案的核心技术限制,具体原因如下:

根本原因

FIM 模型的设计局限

FIM 模型的工作原理:

<fim_prefix>光标前的代码<fim_suffix>光标后的代码<fim_middle>

FIM(Fill-In-the-Middle)模型的训练目标是在光标位置插入代码,而不是修改已有代码。模型的训练数据主要来自:

  • 代码补全场景(从空白到完整代码)
  • 函数体生成
  • 代码续写

不包含:

  • 代码编辑历史(git diff)
  • 重构操作
  • 删除/替换操作

技术对比

为什么 Cursor 可以做到?
Cursor 使用的是专门训练的编辑预测模型(Cursor Tab / Fusion),而不是标准 FIM 模型:

特性 标准 FIM 模型(我们使用的) Cursor 编辑模型
训练数据 代码补全数据 代码编辑历史(diff)
核心能力 插入新代码 预测编辑操作
支持操作 Insert Insert + Replace + Delete
模型类型 通用代码生成 专用编辑预测

3. 已实现快捷键功能,当前支持:

Shift + Ctrl + Space

4. 当前函数之间可以补全调用和跳转

可以补全调用:AI 可以根据上下文补全其他函数的调用
支持跳转:monaco 默认支持函数定义跳转功能

5. 补全被过滤:

部分场景下补全未显示,这是由于 shouldTriggerCompletion 函数的过滤机制导致的。

过滤机制的设计目的

为了优化用户体验和降低 API 调用成本,系统会在以下场景主动过滤补全触发:

不触发补全的场景

  • 代码长度小于 2 个字符(避免过早触发)
  • 光标在语句结束符后(如分号 ; 后)
  • 光标在代码块结束符后(如右花括号 } 后)

设计考量

  • 性能优化:减少不必要的 AI 请求,降低服务器负载
  • 成本控制:避免频繁调用 API 产生额外费用
  • 用户体验:在不需要补全的位置减少干扰


const keyWords = [
'state',
Expand Down Expand Up @@ -173,135 +171,6 @@ const getRange = (position, words) => ({
endColumn: words[words.length - 1].endColumn
})

const generateBaseReference = () => {
const { dataSource = [], utils = [], globalState = [] } = useResource().appSchemaState
const { state, methods } = useCanvas().getPageSchema()
const currentSchema = useCanvas().getCurrentSchema()
let referenceContext = completion
referenceContext = referenceContext.replace('$dataSource$', JSON.stringify(dataSource))
referenceContext = referenceContext.replace('$utils$', JSON.stringify(utils))
referenceContext = referenceContext.replace('$globalState$', JSON.stringify(globalState))
referenceContext = referenceContext.replace('$state$', JSON.stringify(state))
referenceContext = referenceContext.replace('$methods$', JSON.stringify(methods))
referenceContext = referenceContext.replace('$currentSchema$', JSON.stringify(currentSchema))
return referenceContext
}

const fetchAiInlineCompletion = (codeBeforeCursor, codeAfterCursor) => {
const { completeModel, apiKey, baseUrl } = getMetaApi(META_SERVICE.Robot).getSelectedQuickModelInfo() || {}
if (!completeModel || !apiKey || !baseUrl) {
return
}
const referenceContext = generateBaseReference()
return getMetaApi(META_SERVICE.Http).post(
'/app-center/api/chat/completions',
{
model: completeModel,
messages: [
{
role: 'user',
content: referenceContext
.replace('$codeBeforeCursor$', codeBeforeCursor)
.replace('$codeAfterCursor$', codeAfterCursor)
}
],
baseUrl,
stream: false
},
{
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${apiKey || ''}`
}
}
)
}

const initInlineCompletion = (monacoInstance, editorModel) => {
const requestAllowed = ref(true)
const timer = ref()
const inlineCompletionProvider = {
provideInlineCompletions(model, position, _context, _token) {
if (editorModel && model.id !== editorModel.id) {
return new Promise((resolve) => {
resolve({ items: [] })
})
}

if (timer.value) {
clearTimeout(timer.value)
}

const words = getWords(model, position)
const range = getRange(position, words)
const wordContent = words.map((item) => item.word).join('')
if (!wordContent || wordContent.lastIndexOf('}') === 0 || wordContent.length < 4) {
return new Promise((resolve) => {
resolve({ items: [] })
})
}
if (!requestAllowed.value) {
return new Promise((resolve) => {
resolve({
items: [
{
insertText: '',
range
}
]
})
})
}
const codeBeforeCursor = model.getValueInRange({
startLineNumber: 1,
startColumn: 1,
endLineNumber: position.lineNumber,
endColumn: position.column
})
const codeAfterCursor = model.getValueInRange({
startLineNumber: position.lineNumber,
startColumn: position.column,
endLineNumber: model.getLineCount(),
endColumn: model.getLineMaxColumn(model.getLineCount())
})
return new Promise((resolve) => {
// 延迟请求800ms
timer.value = setTimeout(() => {
// 节流操作,防止接口一直被请求
requestAllowed.value = false
fetchAiInlineCompletion(codeBeforeCursor, codeAfterCursor)
.then((res) => {
let insertText = res.choices[0].message.content.trim()
const wordContentIndex = insertText.indexOf(wordContent)
if (wordContentIndex === -1) {
insertText = `${wordContent}${insertText}\n`
}
if (wordContentIndex > 0) {
insertText = insertText.slice(wordContentIndex)
}
requestAllowed.value = true
resolve({
items: [
{
insertText,
range
}
]
})
})
.catch(() => {
requestAllowed.value = true
})
}, 800)
})
},
freeInlineCompletions() {}
}
return ['javascript', 'typescript'].map((lang) =>
monacoInstance.languages.registerInlineCompletionsProvider(lang, inlineCompletionProvider)
)
}

export const initCompletion = (monacoInstance, editorModel, conditionFn) => {
const completionItemProvider = {
provideCompletionItems(model, position, _context, _token) {
Expand Down Expand Up @@ -331,9 +200,5 @@ export const initCompletion = (monacoInstance, editorModel, conditionFn) => {
const completions = ['javascript', 'typescript'].map((lang) => {
return monacoInstance.languages.registerCompletionItemProvider(lang, completionItemProvider)
})
const { enableAICompletion } = getMergeMeta('engine.plugins.pagecontroller')?.options || {}
if (enableAICompletion) {
return completions.concat(initInlineCompletion(monacoInstance, editorModel))
}
return completions
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
快速模型
<tiny-tooltip
effect="light"
content="用于代码补全、话题命名等场景。建议选择轻量模型以实现更快的响应速度,例如flash类型或8b/14b模型。"
content="用于代码补全、话题命名等快速响应场景。推荐选择带「代码」标签的模型以获得更好的代码补全效果。"
placement="top"
>
<svg-icon class="help-link" name="plugin-icon-plugin-help"></svg-icon>
Expand All @@ -51,11 +51,25 @@
<tiny-select
clearable
v-model="state.modelSelection.quickModel"
:options="compactModelOptions"
filterable
placeholder="请选择"
@change="handleCompactModelChange"
></tiny-select>
popper-class="model-select-popper"
>
<template v-for="item in compactModelOptions" :key="item.value">
<tiny-option :label="item.label" :value="item.value">
<span class="left">{{ item.label }}</span>
<div>
<tiny-tag v-if="item.capabilities?.codeCompletion" type="success" effect="light" size="small"
>代码</tiny-tag
>
<tiny-tag v-if="item.capabilities?.compact" type="info" effect="light" size="small"
>轻量</tiny-tag
>
</div>
</tiny-option>
</template>
</tiny-select>
</tiny-form-item>

<div v-if="selectedDefaultModelInfo" class="model-info">
Expand Down Expand Up @@ -172,8 +186,8 @@ const emit = defineEmits(['close'])
const {
robotSettingState,
saveRobotSettingState,
getAllAvailableModels,
getCompactModels,
getNonCodeCompletionModels,
addCustomService,
updateService,
deleteService,
Expand All @@ -196,9 +210,9 @@ const state = reactive({
editingService: undefined as ModelService | undefined
})

// 获取所有可用模型选项
// 获取所有可用模型选项(排除代码补全专用模型)
const allModelOptions = computed(() => {
return getAllAvailableModels().map((model) => ({
return getNonCodeCompletionModels().map((model) => ({
label: model.displayLabel,
value: model.value,
capabilities: model.capabilities
Expand All @@ -207,10 +221,14 @@ const allModelOptions = computed(() => {

// 获取快速模型选项
const compactModelOptions = computed(() => {
return getCompactModels().map((model) => ({
const models = getCompactModels().map((model) => ({
label: model.displayLabel,
value: model.value
value: model.value,
capabilities: model.capabilities,
serviceName: model.serviceName
}))

return models.sort((a, b) => a.serviceName.localeCompare(b.serviceName, 'zh-CN'))
})

// 获取当前选择的默认模型信息
Expand Down Expand Up @@ -270,8 +288,8 @@ const addService = () => {
state.showServiceDialog = true
}

const editService = (service: ModelService) => {
state.editingService = JSON.parse(JSON.stringify(service))
const editService = (service: any) => {
state.editingService = JSON.parse(JSON.stringify(service)) as ModelService
state.showServiceDialog = true
}

Expand Down
16 changes: 14 additions & 2 deletions packages/plugins/robot/src/composables/core/useConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -318,9 +318,19 @@ const getAllAvailableModels = () => {
)
}

// 获取快速模型列表
// 获取快速模型列表(包含 compact 或 codeCompletion 的模型)
const getCompactModels = () => {
return getAllAvailableModels().filter((model) => model.capabilities?.compact)
return getAllAvailableModels().filter((model) => model.capabilities?.compact || model.capabilities?.codeCompletion)
}

// 获取代码补全优化模型列表
const getCodeCompletionModels = () => {
return getAllAvailableModels().filter((model) => model.capabilities?.codeCompletion)
}

// 获取非代码补全模型列表(用于默认助手模型)
const getNonCodeCompletionModels = () => {
return getAllAvailableModels().filter((model) => !model.capabilities?.codeCompletion)
}

const updateThinkingState = (value: boolean) => {
Expand Down Expand Up @@ -456,6 +466,8 @@ export default () => {
getModelCapabilities,
getAllAvailableModels,
getCompactModels,
getCodeCompletionModels, // 代码补全模型列表
getNonCodeCompletionModels, // 非代码补全模型列表
getSelectedModelInfo, // 对话模型信息
getSelectedQuickModelInfo, // 快速模型信息

Expand Down
32 changes: 22 additions & 10 deletions packages/plugins/robot/src/constants/model-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ export const DEFAULT_LLM_MODELS = [
name: 'qwen3-coder-plus',
capabilities: {
toolCalling: true,
codeCompletion: true,
reasoning: reasoningExtraBody,
jsonOutput: bailianJsonOutputExtraBody
}
Expand All @@ -81,23 +82,24 @@ export const DEFAULT_LLM_MODELS = [
}
},
{
label: 'Qwen Coder编程模型(Flash)',
name: 'qwen3-coder-flash',
label: 'Qwen2.5 Coder编程模型-最快响应',
name: 'qwen-coder-turbo-latest',
capabilities: {
toolCalling: true,
compact: true,
codeCompletion: true,
jsonOutput: bailianJsonOutputExtraBody
}
},
{
label: 'Qwen3(14b)',
name: 'qwen3-14b',
capabilities: { compact: true, toolCalling: true, jsonOutput: bailianJsonOutputExtraBody }
},
{
label: 'Qwen3(8b)',
name: 'qwen3-8b',
capabilities: { compact: true, toolCalling: true, jsonOutput: bailianJsonOutputExtraBody }
label: 'Qwen2.5 Coder编程模型(32B)',
name: 'qwen2.5-coder-32b-instruct',
capabilities: {
toolCalling: true,
compact: true,
codeCompletion: true,
jsonOutput: bailianJsonOutputExtraBody
}
}
]
},
Expand All @@ -120,6 +122,16 @@ export const DEFAULT_LLM_MODELS = [
},
jsonOutput: jsonOutputExtraBody
}
},
{
label: 'Deepseek Coder编程模型',
name: 'deepseek-coder',
capabilities: {
toolCalling: true,
compact: true,
codeCompletion: true,
jsonOutput: jsonOutputExtraBody
}
}
]
}
Expand Down
1 change: 1 addition & 0 deletions packages/plugins/robot/src/types/setting.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ export interface ModelConfig {
vision?: boolean
reasoning?: boolean | Capability
compact?: boolean
codeCompletion?: boolean
jsonOutput?: boolean | Capability
}
}
Expand Down
Loading