feat: manually mirror opencoze's code from bytedance

Change-Id: I09a73aadda978ad9511264a756b2ce51f5761adf
This commit is contained in:
fanlv
2025-07-20 17:36:12 +08:00
commit 890153324f
14811 changed files with 1923430 additions and 0 deletions

View File

@@ -0,0 +1,18 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export const primitiveExhaustiveCheck = (_: never) => 0;
export const recordExhaustiveCheck = (_: Record<string, never>) => 0;

View File

@@ -0,0 +1,87 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import type { DataField, Form, GeneralField } from '@formily/core';
import { I18n, type I18nKeysNoOptionsType } from '@coze-arch/i18n';
import {
ModelStyle,
type ModelParameter,
} from '@coze-arch/bot-api/developer_api';
import { type FormilyCoreType } from '../context/formily-context/type';
interface FieldInitStrategy {
execute: (parameter: ModelParameter, formilyCore: FormilyCoreType) => void;
}
/**
* 用户修改模型“生成多样性”参数时,切换为“自定义”模式
*/
class CustomModelStyleStrategy implements FieldInitStrategy {
handler: (_field: DataField, form: Form) => void = (_field, form) =>
form.setValues({ model_style: ModelStyle.Custom });
execute: FieldInitStrategy['execute'] = (
param,
{ onFieldInputValueChange },
) => {
if (param.param_class?.class_id === 1) {
onFieldInputValueChange(param.name, this.handler);
}
};
}
function isDataField(field: GeneralField): field is DataField {
return Object.prototype.hasOwnProperty.call(field, 'value');
}
/**
* 当某个字段值小于阈值时,使用 field.feedbacks 显示提示文案
* https://core.formilyjs.org/zh-CN/api/models/field#ifieldfeedback
*/
class MinimumValueStrategy implements FieldInitStrategy {
constructor(
private name: string,
private min: number,
private i18nKey: I18nKeysNoOptionsType,
) {}
validator: (field: GeneralField, _form: Form) => void = (field, _form) => {
if (isDataField(field)) {
field.setSelfWarnings(
(field.value ?? 0) < this.min ? [I18n.t(this.i18nKey)] : undefined,
);
}
};
execute: FieldInitStrategy['execute'] = (
param,
{ onFieldInit, onFieldValueChange },
) => {
if (param.name === this.name) {
onFieldInit(this.name, this.validator);
onFieldValueChange(this.name, this.validator);
}
};
}
export const fieldInitStrategies = [
new CustomModelStyleStrategy(),
// 重复语句惩罚 < 0 时,显示提示文案
new MinimumValueStrategy('frequency_penalty', 0, 'model_setting_alert'),
// 最大回复长度 < 100 时,显示提示文案
new MinimumValueStrategy('max_tokens', 100, 'model_setting_alert_2'),
];

View File

@@ -0,0 +1,35 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export type NestedObject = Record<string, never>;
export function flattenObject(obj: NestedObject): NestedObject {
const flatten = Object.keys(obj).reduce((acc: NestedObject, key: string) => {
const target = obj[key];
if (
typeof target === 'object' &&
target !== null &&
!Array.isArray(target)
) {
Object.assign(acc, flattenObject(target));
} else {
Object.assign(acc, { [key]: target });
}
return acc;
}, {});
return flatten;
}

View File

@@ -0,0 +1,97 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { type Agent } from '@coze-studio/bot-detail-store';
import { type Dataset, FormatType } from '@coze-arch/bot-api/knowledge';
import {
type Model,
ModelFuncConfigStatus,
ModelFuncConfigType,
RecognitionMode,
} from '@coze-arch/bot-api/developer_api';
interface AgentModelFuncConfigCheckContext {
// agent 中的 dataset 可能缺少元信息,需要获取完整数据的方法
getDatasetById: (id: string) => Dataset | undefined;
config: Model['func_config'];
}
type GetAgentHasValidDataByFuncConfigType = (
agent: Agent,
context: AgentModelFuncConfigCheckContext,
) => boolean;
const getAgentHasValidDataMethodMap: {
[key in ModelFuncConfigType]?: GetAgentHasValidDataByFuncConfigType;
} = {
[ModelFuncConfigType.KnowledgeText]: (agent, { getDatasetById }) =>
!!agent.skills.knowledge.dataSetList?.some(
item =>
(getDatasetById(item.dataset_id ?? '') ?? item).format_type ===
FormatType.Text,
),
[ModelFuncConfigType.KnowledgeTable]: (agent, { getDatasetById }) =>
!!agent.skills.knowledge.dataSetList?.some(
item =>
(getDatasetById(item.dataset_id ?? '') ?? item).format_type ===
FormatType.Table,
),
[ModelFuncConfigType.KnowledgePhoto]: (agent, { getDatasetById }) =>
!!agent.skills.knowledge.dataSetList?.some(
item =>
(getDatasetById(item.dataset_id ?? '') ?? item).format_type ===
FormatType.Image,
),
[ModelFuncConfigType.KnowledgeAutoCall]: agent =>
!!agent.skills.knowledge.dataSetInfo.auto,
[ModelFuncConfigType.KnowledgeOnDemandCall]: agent =>
!agent.skills.knowledge.dataSetInfo.auto,
[ModelFuncConfigType.Plugin]: agent => agent.skills.pluginApis.length > 0,
[ModelFuncConfigType.Workflow]: agent => agent.skills.workflows.length > 0,
[ModelFuncConfigType.MultiAgentRecognize]: agent =>
agent.jump_config.recognition === RecognitionMode.FunctionCall,
};
export const agentModelFuncConfigCheck = ({
config,
agent,
context,
}: {
config: Model['func_config'];
agent: Agent;
context: AgentModelFuncConfigCheckContext;
}) => {
if (!config) {
return { poorSupported: [], notSupported: [] };
}
const poorSupported: ModelFuncConfigType[] = [];
const notSupported: ModelFuncConfigType[] = [];
Object.entries(config).forEach(([type, status]) => {
const hasValidData = getAgentHasValidDataMethodMap[
type as unknown as ModelFuncConfigType
]?.(agent, context);
if (hasValidData) {
if (status === ModelFuncConfigStatus.NotSupport) {
notSupported.push(type as unknown as ModelFuncConfigType);
}
if (status === ModelFuncConfigStatus.PoorSupport) {
poorSupported.push(type as unknown as ModelFuncConfigType);
}
}
});
return { poorSupported, notSupported };
};

View File

@@ -0,0 +1,41 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {
type ShortMemPolicy,
type ModelInfo,
} from '@coze-arch/bot-api/developer_api';
export const convertFormValueToModelInfo = (
values: Record<string, unknown>,
): ModelInfo => {
const { HistoryRound, ContextContentType, ...rest } = values;
// eslint-disable-next-line @typescript-eslint/naming-convention -- 不适用这个 case
const ShortMemPolicy: ShortMemPolicy = {};
if (typeof HistoryRound === 'number') {
ShortMemPolicy.HistoryRound = HistoryRound;
}
if (typeof ContextContentType === 'number') {
ShortMemPolicy.ContextContentType = ContextContentType;
}
return {
...rest,
ShortMemPolicy,
};
};

View File

@@ -0,0 +1,22 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { type ModelInfo } from '@coze-arch/bot-api/developer_api';
import { type NestedObject, flattenObject } from '../flatten-object';
export const convertModelInfoToFlatObject = (modelInfo: ModelInfo) =>
flattenObject(modelInfo as NestedObject);

View File

@@ -0,0 +1,206 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { groupBy, merge, uniqBy } from 'lodash-es';
import { type SchemaTypes, type ISchema } from '@formily/react';
import {
type Model,
ModelParamType,
type ModelParameter,
type ModelParamClass,
} from '@coze-arch/bot-api/developer_api';
import { convertModelValueType } from '@coze-agent-ide/bot-editor-context-store';
import {
primitiveExhaustiveCheck,
recordExhaustiveCheck,
} from '../exhaustive-check';
import {
ModelFormComponent,
ModelFormVoidFieldComponent,
} from '../../constant/model-form-component';
import { type ModelFormComponentPropsMap } from '../../components/model-form/type';
const precisionToStep = (precision: number | undefined) => {
if (!precision) {
return 1;
}
return Number(`0.${'0'.repeat(precision - 1)}1`);
};
const getParamType = ({ type }: Pick<ModelParameter, 'type'>): SchemaTypes => {
switch (type) {
case ModelParamType.Float:
case ModelParamType.Int: {
return 'number';
}
case ModelParamType.Boolean: {
return 'boolean';
}
default: {
return 'string';
}
}
};
const getParamComponent = ({
type,
options,
}: Pick<ModelParameter, 'type' | 'options'>): ModelFormComponent => {
if (options?.length) {
return ModelFormComponent.RadioButton;
}
switch (type) {
case ModelParamType.Float:
case ModelParamType.Int: {
return ModelFormComponent.SliderInputNumber;
}
case ModelParamType.Boolean: {
return ModelFormComponent.Switch;
}
default: {
return ModelFormComponent.Input;
}
}
};
const getComponentProps = (
component: ModelFormComponent,
{
max,
min,
label,
precision,
options,
desc,
type,
...rest
}: Pick<
ModelParameter,
'max' | 'min' | 'label' | 'precision' | 'options' | 'desc' | 'type'
>,
): ModelFormComponentPropsMap[ModelFormComponent] => {
recordExhaustiveCheck(rest);
if (component === ModelFormComponent.RadioButton) {
return {
type: 'button',
options: options?.map(item => {
const convertedValue = convertModelValueType(item.value ?? '', type);
if (typeof convertedValue === 'boolean') {
return {
label: item.label,
value: undefined,
};
}
return {
label: item.label,
value: convertedValue,
};
}),
};
}
if (component === ModelFormComponent.Input) {
return {};
}
if (component === ModelFormComponent.SliderInputNumber) {
const numberedMax = Number(max);
const numberedMin = Number(min);
return {
max: numberedMax,
min: numberedMin,
step: precisionToStep(precision),
decimalPlaces: precision,
};
}
if (component === ModelFormComponent.Switch) {
return {};
}
if (component === ModelFormComponent.ModelFormItem) {
return {
label,
popoverContent: desc,
};
}
primitiveExhaustiveCheck(component);
return {};
};
export const convertModelParamsToSchema = ({
model_params: modelParams,
}: Required<Pick<Model, 'model_params'>>): ISchema => {
const paramClassList = uniqBy(
modelParams
.map(param => param.param_class)
.filter((paramClass): paramClass is ModelParamClass =>
Boolean(paramClass),
),
paramClass => paramClass.class_id,
).sort((a, b) => (a.class_id ?? 0) - (b.class_id ?? 0));
const paramDictionary = groupBy(
modelParams,
param => param.param_class?.class_id,
);
const schema: ISchema = {
type: 'object',
properties: {},
};
paramClassList.forEach((paramClass, index) => {
const voidField: ISchema = {
type: 'void',
properties: {},
'x-decorator':
paramClass.class_id === 1
? ModelFormVoidFieldComponent.ModelFormGenerationDiversityGroupItem
: ModelFormVoidFieldComponent.ModelFormGroupItem,
'x-decorator-props': {
title: paramClass.label,
},
'x-index': index + 1,
};
const parameterList = paramDictionary[paramClass.class_id ?? ''];
if (!parameterList) {
return;
}
parameterList.forEach((modelParam, paramIndex) => {
const paramField = modelParam.name;
const component = getParamComponent(modelParam);
const componentProps = getComponentProps(component, modelParam);
const decoratorProps = getComponentProps(
ModelFormComponent.ModelFormItem,
modelParam,
);
const paramSchema: ISchema = {
type: getParamType(modelParam),
'x-component': component,
'x-component-props': componentProps,
'x-decorator': ModelFormComponent.ModelFormItem,
'x-decorator-props': decoratorProps,
'x-index': paramIndex + 1,
};
merge(voidField.properties, { [paramField]: paramSchema });
});
merge(schema.properties, { [paramClass.class_id ?? '']: voidField });
});
return schema;
};

View File

@@ -0,0 +1,39 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { ModelStyle } from '@coze-arch/bot-api/developer_api';
import { type ModelPresetValues } from '@coze-agent-ide/bot-editor-context-store';
import { primitiveExhaustiveCheck } from '../exhaustive-check';
export const getDiversityPresetValueByStyle = (
style: ModelStyle,
presetValues: ModelPresetValues,
) => {
if (style === ModelStyle.Balance) {
return presetValues.balance;
}
if (style === ModelStyle.Creative) {
return presetValues.creative;
}
if (style === ModelStyle.Precise) {
return presetValues.precise;
}
if (style === ModelStyle.Custom) {
return;
}
primitiveExhaustiveCheck(style);
};

View File

@@ -0,0 +1,75 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { cloneDeep } from 'lodash-es';
import {
ModelParamType,
type ModelParameter,
} from '@coze-arch/bot-api/developer_api';
import { convertModelValueType } from '@coze-agent-ide/bot-editor-context-store';
export const getFixedModelFormValues = (
values: Record<string, unknown>,
modelParameterList: ModelParameter[],
) => {
const draft = cloneDeep(values);
Object.keys(draft).forEach(key => {
const targetParameter = modelParameterList.find(
parameter => parameter.name === key,
);
if (!targetParameter) {
return;
}
const value = draft[key];
const parameterType = targetParameter.type;
const { options } = targetParameter;
// 修正 枚举 类型的参数不在枚举范围内
// IDL 无法写范型 转换成 string 比较
if (options?.length) {
if (options.findIndex(option => option.value === String(value)) >= 0) {
return;
}
draft[key] = convertModelValueType(
options.at(0)?.value ?? '',
parameterType,
);
}
// 修正 number 类型的参数超过最大、最小值
if (
parameterType === ModelParamType.Float ||
parameterType === ModelParamType.Int
) {
if (typeof value !== 'number') {
return;
}
const { max, min } = targetParameter;
const numberedMax = Number(max);
const numberedMin = Number(min);
if (max && value > numberedMax) {
draft[key] = numberedMax;
}
if (min && value < numberedMin) {
draft[key] = numberedMin;
}
}
});
return draft;
};

View File

@@ -0,0 +1,67 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { cloneDeep, isObject } from 'lodash-es';
import { type ISchema } from '@formily/react';
import { I18n } from '@coze-arch/i18n';
import { ModelFormComponent } from '../../constant/model-form-component';
import { type ModelFormComponentPropsMap } from '../../components/model-form/type';
export const getFixedSingleAgentSchema = (schema: ISchema): ISchema => {
const clonedSchema = cloneDeep(schema);
const { properties } = clonedSchema;
if (!properties || typeof properties === 'string') {
return clonedSchema;
}
Object.entries(properties).forEach(([classId, voidField]) => {
if (!isObject(voidField)) {
return;
}
if (!voidField.properties) {
return;
}
if (classId !== '2') {
return;
}
const decoratorProps: ModelFormComponentPropsMap[ModelFormComponent.ModelFormItem] =
{
label: I18n.t('model_config_history_round'),
popoverContent: I18n.t('model_config_history_round_explain'),
};
const componentProps: ModelFormComponentPropsMap[ModelFormComponent.SliderInputNumber] =
{
step: 1,
max: 100,
min: 0,
decimalPlaces: 0,
};
const historyRoundFiled: ISchema = {
type: 'number',
'x-component': ModelFormComponent.SliderInputNumber,
'x-decorator': ModelFormComponent.ModelFormItem,
'x-component-props': componentProps,
'x-decorator-props': decoratorProps,
'x-index': 0,
};
Object.assign(voidField.properties, {
HistoryRound: historyRoundFiled,
});
});
return clonedSchema;
};

View File

@@ -0,0 +1,22 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { uniq } from 'lodash-es';
/**
* 以 class id 首次出现的顺序进行排序
*/
export const getModelClassSortList = (classIdList: string[]) =>
uniq(classIdList);

View File

@@ -0,0 +1,36 @@
/*
* Copyright 2025 coze-dev Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { type Model } from '@coze-arch/bot-api/developer_api';
export const getModelOptionList = ({
onlineModelList,
offlineModelMap,
currentModelId,
}: {
onlineModelList: Model[];
offlineModelMap: Record<string, Model>;
currentModelId: string | undefined;
}) => {
if (!currentModelId) {
return onlineModelList;
}
const specialModel = offlineModelMap[currentModelId];
if (!specialModel) {
return onlineModelList;
}
return onlineModelList.concat([specialModel]);
};