Skip to content

feat: complete Kthena inference service workflows - #495

Open
Jinghao-coding wants to merge 2 commits into
raids-lab:mainfrom
Jinghao-coding:codex/kthena-inference-services-v1
Open

feat: complete Kthena inference service workflows#495
Jinghao-coding wants to merge 2 commits into
raids-lab:mainfrom
Jinghao-coding:codex/kthena-inference-services-v1

Conversation

@Jinghao-coding

@Jinghao-coding Jinghao-coding commented Aug 2, 2026

Copy link
Copy Markdown
Member

背景与目标

Kthena 已升级到 v1 ModelBooster 资源模型。本 PR 将 Crater 的模型部署能力按该版本重新适配,并把部署、运行、调用、监测、会话历史和作业视图串成完整闭环。

模型部署默认保持关闭;只有管理员在“更多设置”中显式开启后,用户侧菜单、路由和接口才会生效,避免未完成配置的集群直接暴露该功能。

后端实现

Kthena v1 与部署生命周期

  • 基于 Kthena v1 ModelBooster 生成和解析部署资源,固定使用 Volcano 调度器,并兼容 vLLM 单服务端 worker 形态。
  • 支持平台模型与外部 URI 两种模型来源;对模型 URI、缓存 URI、服务名、镜像、后端、实例数和资源字段进行服务端校验。
  • Worker 资源同时写入 requestslimits,保证 CPU、内存与 GPU 资源能被调度和资源页正确识别。
  • 统一归一化 ModelBooster 状态、Conditions、运行 Pod、节点与容器状态;详情页可展示部署概览、Kthena 原始资源、诊断信息和资源占用。
  • 增加 OpenAI 兼容代理链路:请求会经 Crater 转发至 Kthena Router,并保留下游可读的错误响应和 HTTP 状态。

权限、开关与数据持久化

  • 新增 kthenaInferenceEnabled 系统配置,默认 false;功能关闭时,Kthena 模型部署相关路由会拒绝访问。
  • 部署资源写入用户和账户标识;列表、详情、调用、会话和私有模板均按当前用户/账户范围隔离。
  • 新增会话与消息表、迁移和回滚测试。会话记录绑定部署/模型/用户/账户,支持标题、消息序列、最近更新时间和幂等的客户端轮次标识。
  • 新增用户私有运行模板表和 CRUD 接口。模板配置保存为 JSON,并校验后端类型及大小限制,避免跨账户读取。

作业与门户整合

  • 引入统一 workload 响应,将 Volcano 作业与 Kthena ModelBooster 同时提供给作业列表和门户概览。
  • Kthena 条目以“模型部署”类型标识展示运行资源、状态、用户、节点和时间,不额外增加冗余的“调度器”列。
  • 模型部署行禁止进入作业批量删除选择,继续保留正常 Volcano 作业的批量删除行为,避免调用错误的删除 API。

前端体验

创建与管理

  • 新增模型部署列表、新建页、详情页和路由守卫;管理员开关关闭时不展示入口。
  • 重构新建部署布局:运行模板、模型来源与推理服务使用紧凑卡片/双列布局;部署摘要和调度约束放到右侧,避免长表单造成的视觉断裂。
  • 运行模板支持平台预置模板和用户私有模板;用户可保存当前配置后复用,再根据实际部署需求继续修改。
  • 消除重复的“开始部署”入口,保持与作业提交页相同的右侧摘要与主操作位置。

详情、调用与观测

  • 详情头部按作业页信息密度展示状态、用户、后端、服务模型、资源与创建时间;标签顺序调整为“调用”优先。
  • 调用页改为多会话布局:左侧历史会话、可折叠侧栏、新建会话、会话标题预览;中间为消息区和输入区;右侧为更紧凑的调用信息与 cURL 示例。
  • 会话记录由服务端持久化,刷新页面或切换会话后可恢复,调用时自动携带当前会话上下文。
  • 补充服务概览、Kthena 资源、资源占用与诊断页,资源展示逻辑与作业资源展示保持一致。

兼容性与安全边界

  • 仅接入 Kthena v1 的 vLLM 单服务端部署形态;不把 Kthena 部署误当作普通 Volcano Job 删除。
  • 管理员开关默认关闭,且后端实际拦截关闭状态下的 API,前端隐藏入口并非唯一保护。
  • 平台模型访问沿用用户/账户资源授权校验;外部 URI、镜像、环境变量和高级参数均经过输入规范化。

验证

  • pnpm run lint
  • pnpm build
  • python3 hack/format_translation.py --check
  • Kthena 会话、私有模板、系统开关、统一 Workload、ModelBooster 构造/状态解析及数据库迁移的聚焦 Go 测试
  • golangci-lint 全量与增量检查
  • 预提交 Swagger 文档生成与 Helm 检查

页面截图

以下截图仅作为 PR 描述附件,未提交到仓库。

模型部署列表与创建 01-inference-services-list 02-new-inference-service 03-new-inference-scheduling
部署详情与调用 04-inference-service-invoke 05-inference-service-chat 06-inference-service-overview
资源观测与诊断 07-inference-service-resources 08-inference-service-usage 09-inference-service-diagnostics
作业、门户概览与管理开关 10-jobs-and-model-deployments 11-portal-overview-workloads 12-admin-model-deployment-toggle

@Jinghao-coding
Jinghao-coding marked this pull request as ready for review August 2, 2026 08:00
Copilot AI review requested due to automatic review settings August 2, 2026 08:00
@Jinghao-coding
Jinghao-coding marked this pull request as draft August 2, 2026 08:01

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

本 PR 面向 Crater 的“在线模型部署/推理服务”能力补齐了 Kthena v1(ModelBooster 资源模型)适配与端到端闭环:后端引入特性开关与统一 Workload API(Volcano Job + Kthena ModelBooster),前端新增模型部署入口、列表页与管理员开关配置,并补齐必要的 RBAC、持久化表与迁移测试。

Changes:

  • 新增 kthenaInferenceEnabled 系统配置开关(默认关闭),并在前后端同时做“关闭即拒绝访问”的防护。
  • 引入统一 Workload 列表/Facet API,将 Volcano 作业与 Kthena ModelBooster 以统一表格行返回,同时限制模型部署不参与作业批量删除。
  • 前端新增模型部署相关 API client、路由与页面(含资源占用 Grafana iframe),并补齐多语言文案。

Reviewed changes

Copilot reviewed 44 out of 47 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
frontend/src/services/client.ts 扩展 v1 POST 支持 options,并新增 v1 PATCH helper
frontend/src/services/api/vcjob.ts 增加统一 Workload 类型/枚举与新接口封装
frontend/src/services/api/system-config.ts 新增 Kthena 推理开关状态的获取/设置 API
frontend/src/services/api/inference.ts 新增 Kthena 推理服务、模板、会话、转发调用等 API 定义
frontend/src/routeTree.gen.ts 注入模型部署相关路由(生成文件)
frontend/src/routes/portal/route.tsx 侧边栏按开关动态展示“模型部署”入口
frontend/src/routes/portal/overview/index.tsx 总览页从 Job 切换为 Workload(支持模型部署行)
frontend/src/routes/portal/inference-services/route.tsx 路由守卫:开关关闭/不可用时重定向
frontend/src/routes/portal/inference-services/index.tsx 新增模型部署列表页与删除交互
frontend/src/routes/portal/inference-services/-components/resource-usage.tsx Grafana iframe 资源占用面板(按 Pod 选择)
frontend/src/routes/admin/more/index.tsx 管理员“更多设置”中增加开关配置与请求联动
frontend/src/routes/admin/more/-components/kthena-inference-settings.tsx 新增开关设置卡片组件
frontend/src/i18n/locales/zhCN/translation.json 新增 Kthena/开关相关中文翻译
frontend/src/i18n/locales/ko/translation.json 新增 Kthena/开关相关韩文翻译
frontend/src/i18n/locales/ja/translation.json 新增 Kthena/开关相关日文翻译
frontend/src/i18n/locales/enUS/translation.json 新增 Kthena/开关相关英文翻译
frontend/src/components/query-table/remote.tsx RemoteDataTable 支持按行禁用选择(避免误批量操作)
frontend/src/components/job/overview/custom-jobs.tsx 作业概览表升级为 Workload,并禁止模型部署进入批量删除
frontend/src/components/form/env-form-field.tsx 修复按钮默认 submit:显式 type="button"
frontend/src/components/badge/kthena-status-badge.tsx 新增 Kthena 状态 Badge 与状态归一化展示
frontend/src/components/badge/job-type-badge.tsx 增加“模型部署”JobType 展示
charts/crater/templates/crater-backend/serviceaccount.yaml 增加 Kthena/Volcano serving 相关 RBAC 权限
backend/internal/service/config_service.go 增加 Kthena 推理开关读取/设置
backend/internal/service/config_service_test.go 增加开关默认值与更新测试
backend/internal/handler/vcjob/workload.go 新增统一 Workload 列表/Facet 处理与 ModelBooster 转换逻辑
backend/internal/handler/vcjob/workload_test.go 覆盖 ModelBooster 转换、过滤与开关屏蔽逻辑
backend/internal/handler/vcjob/vcjob.go 注册统一 Workload 路由(protected/admin)
backend/internal/handler/vcjob/list.go job list query 增加 workload_kind 过滤支持并校验枚举
backend/internal/handler/system_config.go 新增 Kthena 推理开关状态 GET/PUT 路由与 handler
backend/internal/handler/system_config_test.go 覆盖开关状态路由测试
backend/internal/handler/kthena_inference_template.go 新增用户私有推理模板 CRUD(强隔离)
backend/internal/handler/kthena_inference_template_test.go 覆盖模板私有性与校验逻辑
backend/internal/handler/inference_service_test.go 覆盖 v1 请求校验、资源构建、网关/诊断等核心逻辑
backend/internal/handler/inference_conversation_test.go 覆盖会话持久化、隔离、幂等 turn 等逻辑
backend/dao/model/system_config.go 新增系统配置 key:ENABLE_KTHENA_INFERENCE
backend/dao/model/kthena_inference_template.go 新增推理模板表模型
backend/dao/model/kthena_chat.go 新增会话/消息表模型(含幂等 turn 约束字段)
backend/cmd/gorm-gen/models/migrate.go 增加新表迁移与回滚,并加入 AutoMigrate 列表
backend/cmd/gorm-gen/models/migrate_test.go 覆盖迁移与回滚的幂等性测试
backend/cmd/gorm-gen/curd/generate.go 将新模型纳入 gorm-gen 代码生成清单

Comment on lines +74 to +78
to="/portal/inference-services/$name"
params={{ name: workload.jobName }}
preload="intent"
className="text-primary hover:underline"
title={`查看模型部署 ${workload.name}`}
Comment on lines +53 to +56
{
value: 'model-deployment',
label: '模型部署',
},
Comment on lines +46 to +51
- apiGroups: ["workload.serving.volcano.sh"]
resources: ["modelboosters", "modelservings", "autoscalingpolicies"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
- apiGroups: ["networking.serving.volcano.sh"]
resources: ["modelroutes", "modelservers"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 83f6688280

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

return
}

rawResp, err := mgr.proxyKthenaRouter(c.Request.Context(), c.Request.Method, targetPath, body, c.Request.Header)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Stream proxy responses instead of buffering them

When an OpenAI-compatible request sets stream: true, DoRaw collects the entire router response into a byte slice before the handler writes anything to the client. The caller therefore receives no incremental SSE tokens, and line 616 subsequently labels the completed event stream as application/json. This breaks streaming clients and can make long generations appear stalled or hit timeouts; the proxy needs to forward the upstream response body and content type as a stream.

Useful? React with 👍 / 👎.

resputil.HandleError(c, bizerr.BadRequest.InvalidRequest.Wrap(err, "failed to read request body"))
return
}
body, err = withDefaultModel(body, servedModelFromModelBooster(obj))

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Default proxy requests to the route model name

When a caller omits model, this supplies the vLLM served-model-name, but Kthena routes by the ModelBooster/ModelRoute name: buildKthenaAccess exposes booster.GetName() as the route model, and the persisted conversation path deliberately sends scope.RouteModelName for the same reason. Deployments whose service name differs from their served-model name—the default for many external models—will therefore return a route-not-found error for otherwise valid proxied requests that rely on this advertised default.

Useful? React with 👍 / 👎.

Comment on lines +476 to +478
rawCompletion, err := mgr.proxyKthenaRouter(
c.Request.Context(), http.MethodPost, "v1/chat/completions", body, c.Request.Header,
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Reserve the client turn before invoking inference

When a client retries the same clientTurnId while the original request is still in flight, both requests can pass findTurn before either persists anything and both execute this expensive router call. appendTurn deduplicates only afterward, so the API's retry idempotency still consumes inference capacity twice and may discard a different second completion. Claim or lock the turn ID before calling the router, or make concurrent retries wait for the in-progress result.

Useful? React with 👍 / 👎.

Comment on lines +259 to +262
return applyJobFilters(ctx, scope, &jobRequest, defaultDays).
Preload(query.Job.User).
Preload(query.Job.Account).
Find()

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Avoid loading every job before workload pagination

The unified list fetches all matching job rows with Find() and only paginates the merged slice afterward. In the batch-workload view the self endpoint uses a default of days=-1, so every table refresh loads, preloads, converts, and sorts a user's entire job history just to return one page; users with large histories can make this endpoint increasingly slow and memory-heavy. Preserve database pagination or merge bounded, sorted result sets instead of materializing the complete job set.

Useful? React with 👍 / 👎.

@Jinghao-coding
Jinghao-coding marked this pull request as ready for review August 3, 2026 12:58

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 83f6688280

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +1348 to +1349
if servedModel != "" && (name == servedModel || strings.Contains(name, sanitizeKubeName(servedModel))) {
return true

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Match related resources by booster ownership

When two tenants have resources whose names share a common served-model fragment, this namespace-wide matcher treats the other tenant's resource as related; for example, a service serving qwen will accept an unrelated other-qwen-route. That resource is then included in the response and, for a matched ModelServing, its pod metadata and diagnostics are also collected, producing incorrect status/access data and potentially exposing another tenant's runtime details. Restrict association to an exact booster label or owner reference rather than a substring match.

Useful? React with 👍 / 👎.

Comment on lines +1172 to +1175
for i := range pod.Status.ContainerStatuses {
status := &pod.Status.ContainerStatuses[i]
logTail := mgr.containerLogTail(ctx, pod, status.Name, status.RestartCount)
diagnostics = append(diagnostics, diagnosticsFromContainerStatus(pod, status, false, logTail)...)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Fetch container logs only for unhealthy states

For every healthy running container, this calls containerLogTail and opens a Kubernetes pod-log stream before diagnosticsFromContainerStatus determines that no diagnostic is needed. Both service list and detail conversion execute this path, while the new list page polls every 10 seconds, so installations with multiple deployments repeatedly read logs for every container and can substantially increase API-server load and response latency. Inspect the container state first and fetch logs only for waiting or failed containers.

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants