Skip to content
Merged
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
4 changes: 4 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,7 @@ swag:

lint:
go mod tidy && gofmt -w . && golangci-lint run

dev:
unset HTTP_PROXY HTTPS_PROXY ALL_PROXY http_proxy https_proxy all_proxy; go run test/backend/main.go & cd ui/ModelModal && pnpm install && pnpm build && cd ../../test/ui_example && pnpm install && pnpm dev

4 changes: 2 additions & 2 deletions docs/Chat.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# ModelKit Chat 介绍
- 支持 `OpenAI API`
- 支持的供应商:
`兼容OpenAI API 的所有供应商`、`AzureOpenAI`、`Ollama` 、 `DeepSeek`、`Gemini`
`兼容OpenAI API 的所有供应商`、`AzureOpenAI`、`Ollama` 、 `DeepSeek`、`Gemini`、`BaiLian`
# 创建chat

```go
Expand Down Expand Up @@ -40,7 +40,7 @@ func main() {

字段说明(ModelMetadata):

- `provider`:模型提供商,取值如 `OpenAI`、`AzureOpenAI`、`Ollama`、`DeepSeek`、`Gemini`。
- `provider`:模型提供商,取值如 `OpenAI`、`AzureOpenAI`、`Ollama`、`DeepSeek`、`Gemini`、`BaiLian`
- `model_name`:对话模型 ID,例如 `gpt-4o-mini`、`deepseek-chat`。
- `base_url`:OpenAI 兼容客户端会自动调用 `/chat/completions`;不要在 `base_url` 中包含该路径。`Ollama` 若以 `/v1` 结尾走兼容模式,否则走原生。
- `api_key`:鉴权密钥,作为 `Authorization: Bearer <API_KEY>` 使用。
Expand Down
15 changes: 14 additions & 1 deletion docs/ModelModal.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
## 🚀 功能特性

- **多模型提供商支持**: 支持 OpenAI、Ollama、DeepSeek、SiliconFlow、Moonshot、Azure OpenAI、百智云、腾讯混元、百炼、火山引擎、Gemini、智谱等主流AI服务商
- **多模型提供商支持**: 支持 OpenAI、Ollama、DeepSeek、SiliconFlow、Moonshot、Azure OpenAI、百智云、腾讯混元、百炼、火山引擎、Gemini、智谱等主流AI服务商(其中 Azure OpenAI、火山引擎支持手动输入模型名称)
- **模型类型管理**: 支持聊天模型、嵌入模型、重排序模型、视觉模型、代码模型、函数调用等多种模型类型
- **配置验证**: 提供模型配置的实时验证功能,确保API配置正确性
- **现代化Web界面**: 基于React 19和Material-UI构建的响应式用户界面
Expand Down Expand Up @@ -206,3 +206,16 @@ function App() {
pnpm install
```

### 运行示例环境

项目提供了一键启动测试环境的命令,可以同时启动后端服务和前端示例:

```bash
make dev
```

该命令会:
1. 启动后端服务 (`test/backend/main.go`)
2. 启动前端开发服务器 (`test/ui_example`)


5 changes: 5 additions & 0 deletions domain/model.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,11 @@ func getHunyuanModels() []ModelMetadata {
// getBaiLianModels 返回阿里百炼模型列表
func getBaiLianModels() []ModelMetadata {
return []ModelMetadata{
{ModelName: "text-embedding-v1", Object: "model", Provider: consts.ModelProviderBaiLian, ModelType: consts.ModelTypeEmbedding, BaseURL: "https://dashscope.aliyuncs.com/api/v1/services/embeddings/text-embedding/text-embedding"},
{ModelName: "text-embedding-v2", Object: "model", Provider: consts.ModelProviderBaiLian, ModelType: consts.ModelTypeEmbedding, BaseURL: "https://dashscope.aliyuncs.com/api/v1/services/embeddings/text-embedding/text-embedding"},
{ModelName: "text-embedding-v3", Object: "model", Provider: consts.ModelProviderBaiLian, ModelType: consts.ModelTypeEmbedding, BaseURL: "https://dashscope.aliyuncs.com/api/v1/services/embeddings/text-embedding/text-embedding"},
{ModelName: "gte-rerank", Object: "model", Provider: consts.ModelProviderBaiLian, ModelType: consts.ModelTypeRerank, BaseURL: "https://dashscope.aliyuncs.com/api/v1/services/rerank/text-rerank/text-rerank#"},
{ModelName: "qwen3-rerank", Object: "model", Provider: consts.ModelProviderBaiLian, ModelType: consts.ModelTypeRerank, BaseURL: "https://dashscope.aliyuncs.com/api/v1/services/rerank/text-rerank/text-rerank#"},
Comment on lines +135 to +136
Copy link

Choose a reason for hiding this comment

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

Warning

⚠️ BaiLian rerank BaseURL 末尾包含 #,疑似无效/易导致拼接或请求异常

新增的 BaiLian rerank 模型 BaseURL 使用了 .../text-rerank#。URL fragment(# 后内容)不会被发送到服务端,且如果后续代码对 BaseURL 进行 path/query 拼接,可能造成不可预期行为。前端也同样硬编码了该 #(见 ModelModal.tsx),进一步放大风险。

建议: 确认该 # 是否有特殊语义;若无,建议移除,统一为可直接请求的服务端地址,并避免前后端分别硬编码不同/奇怪的 BaseURL。

Suggested change
{ModelName: "gte-rerank", Object: "model", Provider: consts.ModelProviderBaiLian, ModelType: consts.ModelTypeRerank, BaseURL: "https://dashscope.aliyuncs.com/api/v1/services/rerank/text-rerank/text-rerank#"},
{ModelName: "qwen3-rerank", Object: "model", Provider: consts.ModelProviderBaiLian, ModelType: consts.ModelTypeRerank, BaseURL: "https://dashscope.aliyuncs.com/api/v1/services/rerank/text-rerank/text-rerank#"},
{ModelName: "gte-rerank", Object: "model", Provider: consts.ModelProviderBaiLian, ModelType: consts.ModelTypeRerank, BaseURL: "https://dashscope.aliyuncs.com/api/v1/services/rerank/text-rerank/text-rerank"},
{ModelName: "qwen3-rerank", Object: "model", Provider: consts.ModelProviderBaiLian, ModelType: consts.ModelTypeRerank, BaseURL: "https://dashscope.aliyuncs.com/api/v1/services/rerank/text-rerank/text-rerank"},

{ModelName: "qwen3-coder-plus", Object: "model", Provider: consts.ModelProviderBaiLian, ModelType: consts.ModelTypeCoder},
{ModelName: "qwen3-coder-plus-2025-07-22", Object: "model", Provider: consts.ModelProviderBaiLian, ModelType: consts.ModelTypeCoder},
{ModelName: "qwen-plus-2025-07-14", Object: "model", Provider: consts.ModelProviderBaiLian, ModelType: consts.ModelTypeChat},
Expand Down
47 changes: 35 additions & 12 deletions ui/ModelModal/src/ModelModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -368,7 +368,10 @@ export const ModelModal: React.FC<ModelModalProps> = ({

const resetCurData = (value: Model) => {
// @ts-ignore
if (value.provider && value.provider !== 'Other') {
if (
value.provider &&
!['Other', 'AzureOpenAI', 'Volcengine'].includes(value.provider)
) {
getModel({
api_key: value.api_key || '',
base_url: value.base_url || '',
Expand Down Expand Up @@ -463,7 +466,15 @@ export const ModelModal: React.FC<ModelModalProps> = ({
onOk={handleSubmit(handleOk)}
okButtonProps={{
loading,
disabled: !success && providerBrand !== 'Other',
disabled:
!success &&
!['Other', 'AzureOpenAI', 'Volcengine'].includes(providerBrand) &&
!(
providerBrand === 'BaiLian' &&
(model_type === 'embedding' ||
model_type === 'rerank' ||
model_type === 'reranker')
),
}}
>
<Stack
Expand Down Expand Up @@ -578,10 +589,18 @@ export const ModelModal: React.FC<ModelModalProps> = ({
reset({
provider:
it.label as keyof typeof DEFAULT_MODEL_PROVIDERS,
base_url:
it.label === 'AzureOpenAI'
? ''
: it.defaultBaseUrl,
base_url: (() => {
if (it.label === 'AzureOpenAI') return '';
if (it.label === 'BaiLian') {
if (model_type === 'embedding') {
return 'https://dashscope.aliyuncs.com/api/v1/services/embeddings/text-embedding/text-embedding';
}
if (model_type === 'rerank' || model_type === 'reranker') {
return 'https://dashscope.aliyuncs.com/api/v1/services/rerank/text-rerank/text-rerank#';
}
}
return it.defaultBaseUrl;
})(),
model_name: '',
api_version: '',
api_key: '',
Expand Down Expand Up @@ -917,7 +936,11 @@ export const ModelModal: React.FC<ModelModalProps> = ({
/>
</>
)}
{providerBrand === 'Other' ? (
{['Other', 'AzureOpenAI', 'Volcengine'].includes(providerBrand) ||
(providerBrand === 'BaiLian' &&
(model_type === 'embedding' ||
model_type === 'rerank' ||
model_type === 'reranker')) ? (
<>
<Box sx={{ fontSize: 14, lineHeight: '32px', mt: 2 }}>
模型名称{' '}
Expand Down Expand Up @@ -1006,15 +1029,15 @@ export const ModelModal: React.FC<ModelModalProps> = ({
filteredModelList.length > 0
? filteredModelList
: modelUserList.map((item) => ({
model: item.model,
provider: providerBrand,
}));
model: item.model,
provider: providerBrand,
}));

const query = modelSearchQuery.trim().toLowerCase();
const modelsToShow = query
? modelsBase.filter((m) =>
m.model.toLowerCase().includes(query),
)
m.model.toLowerCase().includes(query),
)
: modelsBase;

// 按组分类模型
Expand Down
4 changes: 2 additions & 2 deletions ui/ModelModal/src/constants/providers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,8 @@ export const DEFAULT_MODEL_PROVIDERS: ModelProviderMap = {
customHeader: false,
chat: true,
code: true,
embedding: false,
rerank: false,
embedding: true,
rerank: true,
analysis: true,
analysis_vl: true,
modelDocumentUrl: 'https://bailian.console.aliyun.com/?tab=model#/api-key',
Expand Down
3 changes: 3 additions & 0 deletions usecase/helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -757,6 +757,9 @@ func buildOpenAIChatConfig(md *domain.ModelMetadata) *openai.ChatModelConfig {
if cfg.APIVersion == "" {
cfg.APIVersion = "2024-10-21"
}
cfg.AzureModelMapperFunc = func(model string) string {
return model
}
}
if md.APIHeader != "" {
hc := utils.GetHttpClientWithAPIHeaderMap(md.APIHeader)
Expand Down
32 changes: 32 additions & 0 deletions usecase/helper_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package usecase

import (
"testing"

"github.com/chaitin/ModelKit/v2/consts"
"github.com/chaitin/ModelKit/v2/domain"
)

func TestBuildOpenAIChatConfig_Azure(t *testing.T) {
md := &domain.ModelMetadata{
Provider: consts.ModelProviderAzureOpenAI,
ModelName: "gpt-4.1-mini",
BaseURL: "https://example.openai.azure.com",
APIKey: "test-key",
}

cfg := buildOpenAIChatConfig(md)

if !cfg.ByAzure {
t.Error("Expected ByAzure to be true")
}

if cfg.AzureModelMapperFunc == nil {
t.Error("Expected AzureModelMapperFunc to be set")
} else {
mapped := cfg.AzureModelMapperFunc("gpt-4.1-mini")
if mapped != "gpt-4.1-mini" {
t.Errorf("Expected mapped model to be 'gpt-4.1-mini', got '%s'", mapped)
}
}
}
Loading