支持自定义大模型接入 80%

This commit is contained in:
锦恢 2025-04-21 18:47:11 +08:00
parent 39919e7273
commit 36744f2a71
26 changed files with 574 additions and 272 deletions

View File

@ -4,6 +4,7 @@
- 修复选择模型后点击确认跳转回 deepseek 的 bug
- 修复 mcp 项目初始化点击工具全部都是空的 bug
- 修复无法重新连接的 bug
- 支持自定义第三方 openai 兼容的模型服务
## [main] 0.0.3

View File

@ -41,7 +41,7 @@
| `all` | 完成最基本的各类基础设施 | `完整版本` | 100% | `Done` |
| `render` | chat 模式下支持进行成本分析 | `迭代版本` | 100% | `Done` |
| `ext` | 支持基本的 MCP 项目管理 | `MVP` | 0% | `P0` |
| `service` | 支持自定义大模型接入 | `MVP` | 20% | `P0` |
| `service` | 支持自定义大模型接入 | `MVP` | 80% | `P0` |
| `all` | 支持同时调试多个 MCP Server | `MVP` | 0% | `P1` |
| `all` | 支持通过大模型进行在线验证 | `迭代版本` | 100% | `Done` |
| `all` | 支持 completion/complete 协议字段 | `MVP` | 0% | `P1` |

View File

@ -1,8 +1,8 @@
@font-face {
font-family: "iconfont"; /* Project id 4870215 */
src: url('iconfont.woff2?t=1744476757936') format('woff2'),
url('iconfont.woff?t=1744476757936') format('woff'),
url('iconfont.ttf?t=1744476757936') format('truetype');
src: url('iconfont.woff2?t=1745222758998') format('woff2'),
url('iconfont.woff?t=1745222758998') format('woff'),
url('iconfont.ttf?t=1745222758998') format('truetype');
}
.iconfont {
@ -13,6 +13,22 @@
-moz-osx-font-smoothing: grayscale;
}
.icon-test:before {
content: "\e8ad";
}
.icon-save:before {
content: "\e67c";
}
.icon-more:before {
content: "\e612";
}
.icon-edit:before {
content: "\e63b";
}
.icon-connect:before {
content: "\ecda";
}

Binary file not shown.

View File

@ -205,39 +205,10 @@ a {
background-position: center;
}
.server-icon {
background-size: contain;
background-repeat: no-repeat;
background-position: center;
width: 20px;
height: 20px;
margin-right: 8px;
.el-button:hover {
color: var(--foreground) !important;
}
.deepseek-icon {
background-image: url('./images/deepseek.com.ico');
}
.openai-icon {
background-image: url('./images/openai.com.ico');
}
.mistral-icon {
background-image: url('./images/mistral.ai.ico');
}
.ollama-icon {
background-image: url('./images/ollama.png');
}
.grop-icon {
background-image: url('./images/grok.com.png');
}
.perplexity-icon {
background-image: url('./images/perplexity.ai.ico');
}
.kimi-icon {
background-image: url('./images/kimichat.cn.png');
.el-dropdown-menu__item:hover {
background-color: var(--background) !important;
}

View File

@ -105,7 +105,7 @@ export class TaskLoop {
const chunkHandler = this.bridge.addCommandListener('llm/chat/completions/chunk', data => {
if (data.code !== 200) {
this.onError(data.msg || '请求模型服务时发生错误');
reject(new Error(data.msg || '请求模型服务时发生错误'));
resolve();
return;
}
const { chunk } = data.msg as { chunk: ChatCompletionChunk };

View File

@ -25,6 +25,7 @@ export function setDefaultCss() {
document.body.style.setProperty('--el-color-primary-light-5', 'var(--button-disabled)');
document.body.style.setProperty('--el-bg-color', 'var(--background)');
document.body.style.setProperty('--el-text-color-primary', 'var(--foreground)');
document.body.style.setProperty('--el-button-hover-text-color', 'var(--background)');
// document.body.style.setProperty('--el-color-white', 'var(--background)');

View File

@ -123,5 +123,12 @@
"input-token": "إدخال",
"output-token": "إخراج",
"total": "الإجمالي",
"cache-hit-ratio": "معدل ضربات التخزين المؤقت"
"cache-hit-ratio": "معدل ضربات التخزين المؤقت",
"success-save": "تم الحفظ بنجاح",
"confirm-delete-model": "هل تريد حذف موفر النموذج؟",
"reserve-one-last-model": "احتفظ بنموذج واحد على الأقل",
"edit": "تعديل",
"delete": "حذف",
"test": "اختبار",
"add-new-server": "إضافة خدمة"
}

View File

@ -123,5 +123,12 @@
"input-token": "Eingabe",
"output-token": "Ausgabe",
"total": "Gesamt",
"cache-hit-ratio": "Cache-Trefferquote"
"cache-hit-ratio": "Cache-Trefferquote",
"success-save": "Erfolgreich gespeichert",
"confirm-delete-model": "Modellanbieter wirklich löschen?",
"reserve-one-last-model": "Behalten Sie mindestens ein Modell",
"edit": "Bearbeiten",
"delete": "Löschen",
"test": "Test",
"add-new-server": "Dienst hinzufügen"
}

View File

@ -99,7 +99,7 @@
"choose-a-project-debug": "Select a project to debug",
"model": "Model",
"server-provider": "Service Provider",
"api-root-url": "API root path",
"api-root-url": "Base Url",
"api-token": "API key",
"connection-method": "Connection method",
"command": "Command",
@ -123,5 +123,12 @@
"input-token": "Input",
"output-token": "Output",
"total": "Total",
"cache-hit-ratio": "Cache hit rate"
"cache-hit-ratio": "Cache hit rate",
"success-save": "Successfully saved",
"confirm-delete-model": "Are you sure you want to delete the model provider?",
"reserve-one-last-model": "Keep at least one model",
"edit": "Edit",
"delete": "Delete",
"test": "Test",
"add-new-server": "Add service"
}

View File

@ -123,5 +123,12 @@
"input-token": "Entrée",
"output-token": "Sortie",
"total": "Total",
"cache-hit-ratio": "Taux de réussite du cache"
"cache-hit-ratio": "Taux de réussite du cache",
"success-save": "Enregistré avec succès",
"confirm-delete-model": "Êtes-vous sûr de vouloir supprimer le fournisseur de modèles ?",
"reserve-one-last-model": "Conservez au moins un modèle",
"edit": "Modifier",
"delete": "Supprimer",
"test": "Test",
"add-new-server": "Ajouter un service"
}

View File

@ -123,5 +123,12 @@
"input-token": "入力",
"output-token": "出力",
"total": "合計",
"cache-hit-ratio": "キャッシュヒット率"
"cache-hit-ratio": "キャッシュヒット率",
"success-save": "正常に保存されました",
"confirm-delete-model": "このモデルプロバイダーを削除しますか?",
"reserve-one-last-model": "少なくとも1つのモデルを保持してください",
"edit": "編集",
"delete": "削除",
"test": "テスト",
"add-new-server": "サービスを追加"
}

View File

@ -123,5 +123,12 @@
"input-token": "입력",
"output-token": "출력",
"total": "총계",
"cache-hit-ratio": "캐시 적중률"
"cache-hit-ratio": "캐시 적중률",
"success-save": "성공적으로 저장됨",
"confirm-delete-model": "이 모델 공급자를 삭제하시겠습니까?",
"reserve-one-last-model": "적어도 하나의 모델을 유지하세요",
"edit": "편집",
"delete": "삭제",
"test": "테스트",
"add-new-server": "서비스 추가"
}

View File

@ -123,5 +123,12 @@
"input-token": "Ввод",
"output-token": "Вывод",
"total": "Итого",
"cache-hit-ratio": "Коэффициент попаданий в кэш"
"cache-hit-ratio": "Коэффициент попаданий в кэш",
"success-save": "Успешно сохранено",
"confirm-delete-model": "Вы уверены, что хотите удалить поставщика моделей?",
"reserve-one-last-model": "Оставьте хотя бы одну модель",
"edit": "Редактировать",
"delete": "Удалить",
"test": "Тест",
"add-new-server": "Добавить услугу"
}

View File

@ -123,5 +123,12 @@
"input-token": "输入",
"output-token": "输出",
"total": "总计",
"cache-hit-ratio": "缓存命中率"
"cache-hit-ratio": "缓存命中率",
"success-save": "成功保存",
"confirm-delete-model": "确定删除该模型提供商?",
"reserve-one-last-model": "至少保留一个模型",
"edit": "编辑",
"delete": "删除",
"test": "测试",
"add-new-server": "添加服务"
}

View File

@ -123,5 +123,12 @@
"input-token": "輸入",
"output-token": "輸出",
"total": "總計",
"cache-hit-ratio": "緩存命中率"
"cache-hit-ratio": "緩存命中率",
"success-save": "成功儲存",
"confirm-delete-model": "確定刪除該模型提供商?",
"reserve-one-last-model": "至少保留一個模型",
"edit": "編輯",
"delete": "刪除",
"test": "測試",
"add-new-server": "新增服務"
}

View File

@ -0,0 +1,53 @@
import { ChatStorage } from '@/components/main-panel/chat/chat';
import { TaskLoop } from '@/components/main-panel/chat/task-loop';
import { llmManager } from './llm';
import { reactive, ref } from 'vue';
export const simpleTestResult = reactive<{
done: boolean,
start: boolean,
error: any
}>({
done: false,
start: false,
error: '',
});
export function makeSimpleTalk() {
simpleTestResult.done = false;
simpleTestResult.start = true;
// 使用最简单的 hello 来测试
const testMessage = 'hello';
const s1 = ref('');
const s2 = ref([]);
const loop = new TaskLoop(s1, s2);
const chatStorage: ChatStorage = {
messages: [],
settings: {
temperature: 0.7,
modelIndex: llmManager.currentModelIndex,
systemPrompt: '',
enableTools: [],
enableWebSearch: false,
contextLength: 5
}
};
loop.registerOnDone(() => {
console.log('done');
simpleTestResult.error = '';
simpleTestResult.done = true;
simpleTestResult.start = false;
});
loop.registerOnError(error => {
console.log(error);
simpleTestResult.error = error;
simpleTestResult.start = false;
});
loop.start(chatStorage, testMessage);
}

View File

@ -1,21 +1,35 @@
<template>
<div class="setting-section">
<h2>{{ "API" }}</h2>
<h2 class="api-title">
{{ "API" }}
</h2>
<div class="setting-option">
<span>
<span class="iconfont icon-company"></span>
<span class="option-title">{{ t('server-provider') }}</span>
</span>
<div style="width: 160px;">
<el-select
name="language-setting"
class="language-setting"
v-model="llmManager.currentModelIndex"
<el-select name="language-setting" class="language-setting" v-model="llmManager.currentModelIndex"
@change="onmodelchange">
<el-option v-for="(option, index) in llms" :value="index" :label="option.name" :key="index">
<div class="llm-option">
<span>{{ option.name }}</span>
<el-dropdown trigger="hover" @command="handleCommand">
<span>
<span class="iconfont icon-more"></span>
</span>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item :command="{type: 'edit', index}">
<span class="iconfont icon-edit">&emsp;{{ t('edit') }}</span>
</el-dropdown-item>
<el-dropdown-item :command="{type: 'delete', index}" divided>
<span class="iconfont icon-delete">&emsp;{{ t('delete') }}</span>
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
</el-option>
</el-select>
@ -27,73 +41,76 @@
</div>
<div v-else>
<div class="setting-option">
<span>
<span class="iconfont icon-llm"></span>
<span class="option-title">{{ t('model') }}</span>
</span>
<div style="width: 160px;">
<el-select
v-if="llms[llmManager.currentModelIndex]"
name="language-setting" class="language-setting"
v-model="llms[llmManager.currentModelIndex].userModel"
@change="onmodelchange"
>
<el-option v-for="option in llms[llmManager.currentModelIndex].models"
:value="option"
:label="option"
:key="option"
></el-option>
</el-select>
</div>
</div>
<div class="setting-option">
<span>
<span class="iconfont icon-url-line"></span>
<span class="option-title">{{ t('api-root-url') }}</span>
</span>
<div style="width: 240px;">
<el-input
v-if="llms[llmManager.currentModelIndex]"
v-model="llms[llmManager.currentModelIndex].baseUrl"
placeholder="https://"
/>
</div>
</div>
<div class="setting-option">
<span>
<span class="iconfont icon-token"></span>
<span class="option-title">{{ t('api-token') }}</span>
</span>
<div style="width: 240px;">
<el-input
v-if="llms[llmManager.currentModelIndex]"
v-model="llms[llmManager.currentModelIndex].userToken"
show-password
/>
</div>
</div>
<ConnectInterfaceOpenai />
</div>
<br>
<div class="setting-save-container">
<el-button type="success" @click="addNewServer">
{{ t("add-new-server") }}
</el-button>
<el-button
type="primary"
@click="makeSimpleTalk"
:loading="simpleTestResult.start"
>
<span v-show="!simpleTestResult.start" class="iconfont icon-test"></span>
{{ t('test') }}
</el-button>
<el-button type="primary" @click="saveLlmSetting">
<span class="iconfont icon-save"></span>
{{ t('save') }}
</el-button>
</div>
</div>
<!-- 测试连通性的地方 -->
<ConnectTest />
<!-- 当前页面的聊天框 -->
<el-dialog v-model="dialogVisible" width="50%" style="min-width: 500px; max-width: 800px;padding: 20px;">
<br>
<el-form :model="newProviderForm" label-width="auto">
<el-form-item :label="t('server-provider')">
<el-input v-model="newProviderForm.provider" />
</el-form-item>
<el-form-item :label="t('model')">
<el-input-tag
v-model="newProviderForm.models"
placeholder="gpt-3.5-turbo"
/>
</el-form-item>
<el-form-item :label="t('api-root-url')">
<el-input v-model="newProviderForm.baseUrl" placeholder="https://" />
</el-form-item>
<el-form-item :label="t('api-token')">
<el-input v-model="newProviderForm.userToken" show-password />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="dialogVisible = false">{{ t("cancel") }}</el-button>
<el-button type="primary" @click="dialogConfirm">{{ t("confirm") }}</el-button>
</template>
</el-dialog>
</div>
</template>
<script setup lang="ts">
import { defineComponent } from 'vue';
import { llmManager, llms, onmodelchange } from './llm';
import { defineComponent, ref } from 'vue';
import { llmManager, llms } from './llm';
import { useI18n } from 'vue-i18n';
import { saveSetting } from '@/hook/setting';
import { ElMessage } from 'element-plus';
import { ElMessage, ElMessageBox } from 'element-plus';
import { pinkLog } from './util';
import ConnectInterfaceOpenai from './connect-interface-openai.vue';
import ConnectTest from './connect-test.vue';
import { makeSimpleTalk, simpleTestResult } from './api';
defineComponent({ name: 'api' });
const { t } = useI18n();
@ -101,16 +118,207 @@ const { t } = useI18n();
function saveLlmSetting() {
saveSetting(() => {
ElMessage({
message: '成功保存',
message: t('success-save'),
type: 'success'
});
});
}
const currentDialogMode = ref('');
const dialogVisible = ref(false);
function addNewServer() {
newProviderForm.value = {
provider: '',
models: [],
baseUrl: '',
userToken: ''
};
currentDialogMode.value = 'add';
dialogVisible.value = true;
}
function editModel(index: number) {
currentDialogMode.value = 'edit';
editingIndex.value = index;
const formData = JSON.parse(JSON.stringify(llms[index]));
newProviderForm.value = {
provider: formData.name,
models: formData.models,
baseUrl: formData.baseUrl,
userToken: formData.userToken
};
dialogVisible.value = true;
}
const newProviderForm = ref<{
provider: string;
models: string[];
baseUrl: string;
userToken: string;
}>({
provider: '',
models: [],
baseUrl: '',
userToken: ''
});
const editingIndex = ref(-1);
function dialogConfirm() {
if (currentDialogMode.value === 'add') {
addNewProvider();
} else if (currentDialogMode.value === 'edit') {
updateProvider();
}
}
function addNewProvider() {
const formData = JSON.parse(JSON.stringify(newProviderForm.value));
llms.push({
id: formData.provider,
name: formData.provider,
models: formData.models,
userModel: formData.models[0],
baseUrl: formData.baseUrl,
userToken: formData.userToken,
isOpenAICompatible: true,
description: "User Defined Server",
website: "",
});
dialogVisible.value = false;
newProviderForm.value = {
provider: '',
models: [],
baseUrl: '',
userToken: ''
};
}
function updateProvider() {
if (editingIndex.value < 0) {
return;
}
const formData = JSON.parse(JSON.stringify(newProviderForm.value));
llms[editingIndex.value] = {
...llms[editingIndex.value],
id: formData.provider,
name: formData.provider,
models: formData.models,
userModel: formData.models[0] || '',
baseUrl: formData.baseUrl,
userToken: formData.userToken
};
console.log(llms[editingIndex.value]);
newProviderForm.value = {
provider: '',
models: [],
baseUrl: '',
userToken: ''
};
dialogVisible.value = false;
saveLlmSetting();
}
function onmodelchange() {
pinkLog('切换模型到:' + llms[llmManager.currentModelIndex].id);
saveLlmSetting();
}
function deleteModel(index: number) {
if (llms.length <= 1) {
ElMessage.warning(t('reserve-one-last-model'));
return;
}
ElMessageBox.confirm(
t('confirm-delete-model'),
'warning',
{
confirmButtonText: t('confirm'),
cancelButtonText: t('cancel'),
type: 'warning',
}
).then(() => {
llms.splice(index, 1);
if (llmManager.currentModelIndex === index) {
llmManager.currentModelIndex = 0;
} else if (llmManager.currentModelIndex > index) {
llmManager.currentModelIndex --;
}
saveLlmSetting();
}).catch(() => {
//
});
}
function handleCommand(command: {type: string, index: number}) {
if (command.type === 'edit') {
editModel(command.index);
} else if (command.type === 'delete') {
deleteModel(command.index);
}
}
</script>
<style>
.setting-save-container {
margin: 5px;
}
.setting-save-container .iconfont {
margin-right: 5px;
font-weight: 700;
}
.api-title {
display: flex;
align-items: center;
}
.api-title .iconfont {
font-weight: 300;
font-size: 18px;
margin-left: 10px;
cursor: pointer;
transition: var(--animation-3s);
}
.api-title .iconfont:hover {
color: var(--main-color);
}
.llm-option {
display: flex;
justify-content: space-between;
align-items: center;
width: 100%;
--el-color-primary: white;
}
.delete-icon {
color: var(--el-color-danger);
cursor: pointer;
margin-left: 10px;
}
.delete-icon:hover {
opacity: 0.8;
}
</style>

View File

@ -0,0 +1,77 @@
<template>
<div class="setting-option">
<span>
<span class="iconfont icon-llm"></span>
<span class="option-title">{{ t('model') }}</span>
</span>
<div style="width: 160px;">
<el-select v-if="llms[llmManager.currentModelIndex]"
name="language-setting"
v-model="llms[llmManager.currentModelIndex].userModel"
@change="onmodelchange"
>
<el-option
v-for="option in llms[llmManager.currentModelIndex].models"
:value="option"
:label="option"
:key="option.id"
></el-option>
</el-select>
</div>
</div>
<div class="setting-option">
<span>
<span class="iconfont icon-url-line"></span>
<span class="option-title">{{ t('api-root-url') }}</span>
</span>
<div style="width: 240px;">
<el-input v-if="llms[llmManager.currentModelIndex]"
v-model="llms[llmManager.currentModelIndex].baseUrl" placeholder="https://" />
</div>
</div>
<div class="setting-option">
<span>
<span class="iconfont icon-token"></span>
<span class="option-title">{{ t('api-token') }}</span>
</span>
<div style="width: 240px;">
<el-input v-if="llms[llmManager.currentModelIndex]"
v-model="llms[llmManager.currentModelIndex].userToken" show-password />
</div>
</div>
</template>
<script setup lang="ts">
/* eslint-disable */
import { defineComponent } from 'vue';
import { useI18n } from 'vue-i18n';
import { llmManager, llms } from './llm';
import { pinkLog } from './util';
import { saveSetting } from '@/hook/setting';
import { ElMessage } from 'element-plus';
defineComponent({ name: 'connect-interface-openai' });
const { t } = useI18n();
function saveLlmSetting() {
saveSetting(() => {
ElMessage({
message: t('success-save'),
type: 'success'
});
});
}
function onmodelchange() {
pinkLog('切换模型到:' + llms[llmManager.currentModelIndex].id);
saveLlmSetting();
}
</script>
<style>
</style>

View File

@ -0,0 +1,53 @@
<template>
<div class="connect-test" v-if="simpleTestResult.done || simpleTestResult.error">
<div class="test-result">
<div class="result-item" v-if="simpleTestResult.done">
<span class="iconfont icon-success"></span>
<span>{{ "✅ okey dockey :D" }}</span>
</div>
<div class="result-item error" v-if="simpleTestResult.error">
<span class="iconfont icon-error"></span>
<span>{{ '❌ ' + simpleTestResult.error }}</span>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { defineComponent } from 'vue';
import { simpleTestResult } from './api';
defineComponent({ name: 'connect-test' });
</script>
<style scoped>
.connect-test {
margin-top: 20px;
padding: 12px;
border-radius: 4px;
background-color: var(--el-bg-color);
}
.test-result {
display: flex;
flex-direction: column;
gap: 8px;
}
.result-item {
display: flex;
align-items: center;
gap: 8px;
color: var(--el-color-success);
padding: 6px 12px;
border-radius: 4px;
}
.result-item.error {
color: var(--el-color-danger);
}
.result-item .iconfont {
font-size: 16px;
}
</style>

View File

@ -1,27 +1,29 @@
<template>
<div class="setting-container">
<div>
<el-segmented
v-model="settingSections.current"
:options="settingSections.data"
size="large"
style="margin: 10px; font-size: 16px; background-color: var(--background);"
>
<template #default="scope">
<div class="setting-section-option">
{{ scope.item.label }}
</div>
</template>
</el-segmented>
<el-scrollbar height="100%">
<div class="setting-container">
<div>
<el-segmented
v-model="settingSections.current"
:options="settingSections.data"
size="large"
style="margin: 10px; font-size: 16px; background-color: var(--background);"
>
<template #default="scope">
<div class="setting-section-option">
{{ scope.item.label }}
</div>
</template>
</el-segmented>
<div>
<General v-show="settingSections.current === 'general'"></General>
<Api v-show="settingSections.current === 'api'"></Api>
<Appearance v-show="settingSections.current === 'appearance'"></Appearance>
<General v-show="settingSections.current === 'general'"></General>
<Api v-show="settingSections.current === 'api'"></Api>
<Appearance v-show="settingSections.current === 'appearance'"></Appearance>
</div>
</div>
</div>
</div>
</el-scrollbar>
</template>
<script setup lang="ts">

View File

@ -7,8 +7,3 @@ export const llms = reactive<any[]>([]);
export const llmManager = reactive({
currentModelIndex: 0,
});
export function onmodelchange() {
pinkLog('切换模型到:' + llms[llmManager.currentModelIndex].id);
saveSetting();
}

3
service/.gitignore vendored
View File

@ -21,4 +21,5 @@ pnpm-debug.log*
*.njsproj
*.sln
*.sw?
config.json
config.json
setting.json

View File

@ -19,9 +19,9 @@ function getConfigurationPath() {
if (!fs.existsSync(configDir)) {
fs.mkdirSync(configDir, { recursive: true });
}
return path.join(configDir, 'config.json');
return path.join(configDir, 'setting.json');
}
return 'config.json';
return 'setting.json';
}
function getTabSavePath(serverInfo: IServerVersion) {

View File

@ -1,103 +1,4 @@
{
"currentIndex": 0,
"tabs": [
{
"name": "交互测试",
"icon": "icon-robot",
"type": "blank",
"componentIndex": 3,
"storage": {
"messages": [
{
"role": "user",
"content": "请帮我找几张明日方舟 m3 的图片",
"extraInfo": {
"created": 1745169263803,
"serverName": "deepseek"
}
},
{
"role": "assistant",
"content": "",
"tool_calls": [
{
"id": "call_0_bdf947d9-41d9-4244-b145-55e5dc4f1bdd",
"index": 0,
"type": "function",
"function": {
"name": "image_crawler",
"arguments": "{\"key_word\":\"明日方舟 m3\",\"image_num\":5}"
}
}
],
"extraInfo": {
"created": 1745169269404,
"serverName": "deepseek",
"usage": {
"prompt_tokens": 224,
"completion_tokens": 33,
"total_tokens": 257,
"prompt_tokens_details": {
"cached_tokens": 0
},
"prompt_cache_hit_tokens": 0,
"prompt_cache_miss_tokens": 224
}
}
},
{
"role": "tool",
"tool_call_id": "call_0_bdf947d9-41d9-4244-b145-55e5dc4f1bdd",
"content": "[{\"type\":\"text\",\"text\":\"[ImageResult(url='https://embed.pixiv.net/decorate.php?illust_id=128971880', title='', source='bing'), ImageResult(url='https://embed.pixiv.net/artwork.php?illust_id=128973165', title='', source='bing'), ImageResult(url='https://embed.pixiv.net/artwork.php?illust_id=128981472', title='', source='bing'), ImageResult(url='https://embed.pixiv.net/decorate.php?illust_id=128933348', title='', source='bing'), ImageResult(url='https://embed.pixiv.net/artwork.php?illust_id=128952894', title='', source='bing')]\"}]",
"extraInfo": {
"created": 1745169270649,
"serverName": "deepseek",
"usage": {
"prompt_tokens": 224,
"completion_tokens": 33,
"total_tokens": 257,
"prompt_tokens_details": {
"cached_tokens": 0
},
"prompt_cache_hit_tokens": 0,
"prompt_cache_miss_tokens": 224
}
}
},
{
"role": "assistant",
"content": "以下是一些关于“明日方舟 m3”的图片\n\n1. ![Image](https://embed.pixiv.net/decorate.php?illust_id=128971880)\n2. ![Image](https://embed.pixiv.net/artwork.php?illust_id=128973165)\n3. ![Image](https://embed.pixiv.net/artwork.php?illust_id=128981472)\n4. ![Image](https://embed.pixiv.net/decorate.php?illust_id=128933348)\n5. ![Image](https://embed.pixiv.net/artwork.php?illust_id=128952894)\n\n如果需要更多图片或其他帮助请告诉我",
"extraInfo": {
"created": 1745169280218,
"serverName": "deepseek",
"usage": {
"prompt_tokens": 420,
"completion_tokens": 142,
"total_tokens": 562,
"prompt_tokens_details": {
"cached_tokens": 192
},
"prompt_cache_hit_tokens": 192,
"prompt_cache_miss_tokens": 228
}
}
}
],
"settings": {
"modelIndex": 0,
"enableTools": [
{
"name": "image_crawler",
"description": "根据关键词从指定搜索引擎爬取图片",
"enabled": true
}
],
"enableWebSearch": false,
"temperature": 0.7,
"contextLength": 10,
"systemPrompt": "你是一个聪明的机器人,我现在会询问你一些问题,这些问题可能会调用我给你的工具,如果用户的输入的问题内包含的参数不足以发起一次工具调用,那么你应该提醒用户缺失的参数。对于图片,请按照 ![]() 进行渲染,而不是将这个返回到 ``` 的 code 中。"
}
}
}
]
"tabs": []
}

View File

@ -1,40 +0,0 @@
{
"currentIndex": 2,
"tabs": [
{
"name": "工具",
"icon": "icon-tool",
"type": "blank",
"componentIndex": 2,
"storage": {
"currentToolName": "add"
}
},
{
"name": "资源",
"icon": "icon-file",
"type": "blank",
"componentIndex": 0,
"storage": {
"currentResourceName": "greeting"
}
},
{
"name": "交互测试",
"icon": "icon-robot",
"type": "blank",
"componentIndex": 3,
"storage": {
"messages": [],
"settings": {
"modelIndex": 0,
"enableTools": [],
"enableWebSearch": false,
"temperature": 0.7,
"contextLength": 10,
"systemPrompt": ""
}
}
}
]
}