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
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@

### Added

- 本地大模型上下文优化:AI 通道新增 `context_window` 与 `context_profile: local`,支持从 Ollama / llama.cpp / LM Studio / vLLM 自动探测运行时窗口,按窗口收紧压缩预算与工具输出阈值,并让内置工具进入 `tool_search` 动态池;local 档位自动改用 `max_tokens` 兼容本地端点。
- 本地上下文增强:检测结果展示按档位折算后的有效预算,探测支持反向代理子路径;local 档位为 token 估算保留 10% 余量,补充 llama.cpp / LM Studio / vLLM 的超窗报错识别并触发激进压缩重试;攻击链、FOFA、工作流、HITL 等旁路调用同样按档位切换输出字段,并合并通道级 `reasoning.extra_request_fields`。

- 启动时以统一最新 SQLite 定义自动检查全部项目管理表,缺表建表、缺字段补字段,无需维护逐字段迁移列表;保留已有数据和额外表/字段,结构补齐失败回滚并阻止启动。HITL 与通知表纳入统一初始化,历史漏洞外键迁移保留额外列、索引、触发器及依赖记录。

### Fixed
Expand Down
21 changes: 21 additions & 0 deletions config.example.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,8 @@ ai:
model: qwen3-max
max_total_tokens: 120000
max_completion_tokens: 32768
context_window: 262144 # 模型原生上下文窗口(token);0/省略=未知。检测到窗口后,预算会被自动收紧
context_profile: auto # auto=通用;local=本地模型档位(32K-256K),会收紧压缩/工具输出/常驻工具,token 估算留 10% 保守余量
# Eino 路径模型推理:DeepSeek/OpenAI 为 thinking / reasoning_effort;Claude 4.6+ 为 adaptive + output_config.effort(仅显式配置 effort 时下发);3.7 为 enabled+budget_tokens:10000(文档示例),effort 不映射,自定义预算用 extra_request_fields
reasoning:
mode: on # auto | on | off;off:OpenAI/Claude 不附加推理字段,DeepSeek 发送 thinking.type=disabled(其默认开启思考)
Expand All @@ -123,6 +125,24 @@ ai:
# model: qwen-plus
# max_total_tokens: 120000
# max_completion_tokens: 32768
# 本地模型示例(Ollama / llama.cpp / vLLM / LM Studio 均走 OpenAI 兼容端点)
# local-qwen:
# name: Local Qwen
# provider: openai_compatible
# base_url: http://127.0.0.1:11434/v1
# api_key: ollama
# model: qwen3.5:9b
# max_total_tokens: 65536 # 运行时会再被 context_window * 60% 收紧
# max_completion_tokens: 8192
# context_window: 131072 # 建议用页面「自动检测」填入实际 runtime 窗口
# context_profile: local
# # Ollama 的 /v1 接口忽略 num_ctx:请同时设置 OLLAMA_CONTEXT_LENGTH=131072 或 Modelfile PARAMETER num_ctx;
# # 否则 Ollama 会按显存使用 4K/32K/256K 默认值,并从开头截断系统提示词。
# reasoning:
# mode: off
# extra_request_fields:
# chat_template_kwargs:
# enable_thinking: false # Qwen3.5 等思考模型在 agent 工具循环中建议关闭思考
# 视觉分析(analyze_image MCP 工具;图片仅在单次 VL 调用中出现,Agent 上下文只保留文字摘要)
vision:
enabled: false # true 且 model 非空时注册 analyze_image
Expand Down Expand Up @@ -342,6 +362,7 @@ multi_agent:
tool_search_min_tools: 20 # 达到该数量才启用 tool_search(避免工具很少时多此一举);与 always_visible 配合使用
tool_search_always_visible: 12 # 始终直接暴露给模型的工具个数(顺序与角色工具列表一致);其余工具进入动态池,需 tool_search 解锁
tool_search_always_visible_tools: [read_file, glob, grep, analyze_image, write_file, edit_file, execute, task, transfer_to_agent, exit, write_todos, skill, tool_search, TaskCreate, TaskGet, TaskUpdate, TaskList, record_vulnerability, list_vulnerabilities, get_vulnerability, get_tool_execution, wait_tool_execution, cancel_tool_execution, list_knowledge_risk_types, search_knowledge_base, webshell_exec, webshell_file_list, webshell_file_read, webshell_file_write, manage_webshell_list, manage_webshell_add, manage_webshell_update, manage_webshell_delete, manage_webshell_test, batch_task_list, batch_task_get, batch_task_start, batch_task_rerun, batch_task_pause, batch_task_update_metadata, batch_task_update_schedule, batch_task_schedule_enabled, batch_task_update_task, batch_task_remove_task, batch_task_delete, batch_task_create, batch_task_add_task, http-framework-test, exec] # 后端内置常驻工具白名单(优先于 always_visible 数量策略)
tool_search_include_builtins: true # false:后端内置工具也进入 tool_search 动态池;context_profile=local 时自动为 false
plantask_enable: true # P0:主代理挂载 TaskCreate/Get/Update/List 结构化任务板;需 eino_skills 可用且 skills_dir 存在
plantask_rel_dir: .eino/plantask # 任务文件相对 skills_dir,按会话分子目录:skills/.eino/plantask/<conversationId>/
reduction_enable: true # true:大工具输出截断并落盘到本地文件(tmp/reduction/.../trunc/<id>),上下文只留 <persisted-output> 预览;不依赖 eino_skills
Expand Down
23 changes: 21 additions & 2 deletions docs/en-US/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,10 +70,29 @@ ai:
| `name` | Display name in the Web UI; falls back to the ID. |
| `provider` | `openai_compatible` or `claude`. OpenAI-compatible channels map to runtime `openai`; Claude channels use Eino's native Anthropic Messages API component. |
| `base_url/api_key/model` | Required. Base URL usually includes a version path such as `/v1`. |
| `max_total_tokens` | Shared context budget for compression, attack-chain generation, multi-agent summaries, and similar paths. |
| `max_completion_tokens` | Per-response output cap; default is used when empty. |
| `max_total_tokens` | Shared context budget for compression, attack-chain generation, multi-agent summaries, and similar paths. When `context_window` is known, it is clamped to the window minus the output reserve. |
| `max_completion_tokens` | Per-response output cap; default is used when empty, and it is clamped to half of the known/assumed window. |
| `context_window` | Native model context window in tokens. When set, the runtime clamps `max_total_tokens` to it; 0/omitted means unknown. |
| `context_profile` | `auto` (default) or `local`. `local` targets 32K–256K local models: the budget is 60% of the window (capped at 128K), while summary reserve, tool-output truncation/clearing, user-message and ledger caps are tightened, and `tool_search` with a small always-visible tool set is enabled. It only tightens numeric middleware settings and enables tool_search/reduction where needed. |
| `reasoning` | Default reasoning fields for the channel. Gateway support varies; try `mode: off` first when a provider rejects requests. |

### Local Model Context (32K–256K)

A local model's effective context is usually much smaller than its advertised window, and the “model maximum” can differ from the runtime window. Recommended setup:

1. **Confirm the runtime window**: click **Auto-detect** next to the context window field in **System Settings → Basic Settings → AI Channel Configuration**. The probe checks Ollama `/api/ps`/`/api/show`, llama.cpp `/props`, LM Studio `/api/v0/models`, then vLLM/OpenAI-compatible `/v1/models`; reverse-proxy subpaths such as `https://host/ollama/v1` are supported. The result also shows the effective budget for the current profile. Enter the server's actual value if detection fails.
2. **Enable the local profile**: set `context_profile: local`. The budget becomes 60% of the window, also bounded by “window − output reserve” (for example, a 32K window with the default 16K output cap yields a 16K budget), and the per-request output cap is clamped to half the window; compaction triggers earlier. Tool-output truncation is roughly window/6 (8–32 KB), historical tool-result clearing is roughly 35% of the budget, and always-visible tools shrink to a core set; backend builtins also join the `tool_search` dynamic pool, and the rest are unlocked on demand. Token estimates are inflated by 10% to absorb tokenizer differences between local models and tiktoken. The profile also switches the output cap to the more widely accepted `max_tokens` field, because many local servers do not recognize `max_completion_tokens`; the same applies to attack-chain, FOFA query, workflow draft, and HITL audit calls.
3. **Do not plan around a nominal 1M window**: Qwen3.5-class models ship with 262,144 native context and can be extended to 1M with YaRN, but local VRAM limits and context rot degrade quality. For tool-using agents, keep the effective budget around 64K–128K.
4. **Ollama requires `num_ctx`**: Ollama's `/v1` compatibility endpoint ignores `num_ctx` in requests and picks a default window from VRAM (roughly 4K/32K/256K). Oversized prompts are truncated from the beginning, dropping the system prompt and tool definitions first. Use `OLLAMA_CONTEXT_LENGTH=65536 ollama serve` or a Modelfile `PARAMETER num_ctx 65536`, then confirm `CONTEXT` in `ollama ps` after restart.
5. **KV cache and prefix caching**:
- llama.cpp: `--ctx-size 65536 --cache-type-k q8_0 --cache-type-v q8_0 -fa`; `--cache-prompt` is on by default. `--parallel` splits the context across slots; detection returns `context_slots` and warns when it is greater than 1, so confirm the per-request window for your KV mode (for example `--kv-unified`).
- vLLM: `--max-model-len 65536 --enable-prefix-caching --kv-cache-dtype fp8`; use `--max-num-seqs` to bound concurrent KV usage.
- Ollama: `OLLAMA_KV_CACHE_TYPE=q8_0`, `OLLAMA_NUM_PARALLEL=1`; larger windows consume more KV cache.
- LM Studio: set Context Length explicitly in the model settings and enable KV-cache quantization when available.
6. **Thinking models**: Qwen3.5-class models enable thinking by default, which burns context inside tool loops. Disable it per channel with `reasoning.extra_request_fields.chat_template_kwargs.enable_thinking: false` (supported by vLLM and llama.cpp with `--jinja`). `reasoning.mode: off` only clears OpenAI/DeepSeek reasoning fields and may not disable the local chat template's thinking.
7. **Minimum viable window**: an agent with 90+ tools is unreliable below 16K. 32K depends on `tool_search` and `context_profile: local`; 64K and above is closer to a cloud-model experience.
8. **Failover channels are not re-budgeted**: `multi_agent.eino_middleware.model_failover_channels` only switches the model endpoint; the budget still follows the primary channel. Keep each failover channel's available window at least as large as the primary, or expect overflow compaction/retry.

The chat page reads saved channels into the “AI Channel” selector. A non-empty request `aiChannelId` selects a channel for that run/session without sending API credentials through the prompt path. Empty `aiChannelId` follows `ai.default_channel`.

Common Web UI operations:
Expand Down
23 changes: 21 additions & 2 deletions docs/zh-CN/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,10 +71,29 @@ ai:
| `name` | Web UI 展示名。留空时使用通道 ID。 |
| `provider` | `openai_compatible` 或 `claude`。`openai_compatible` 会在运行时映射为 `openai`;`claude` 使用 Eino 原生 Anthropic Messages API。 |
| `base_url/api_key/model` | 必填。Base URL 通常需要包含版本路径,如 OpenAI/兼容网关的 `/v1`。 |
| `max_total_tokens` | 上下文压缩、攻击链构建、多代理摘要等共用的总预算。 |
| `max_completion_tokens` | 单次模型输出上限;未填时使用默认值。 |
| `max_total_tokens` | 上下文压缩、攻击链构建、多代理摘要等共用的总预算。已知 `context_window` 时会收紧到“窗口 − 输出预留”。 |
| `max_completion_tokens` | 单次模型输出上限;未填时使用默认值,并会被压到已知/假定窗口的一半以内。 |
| `context_window` | 模型原生上下文窗口(token)。填写后运行时会按窗口收紧 `max_total_tokens`;0/省略表示未知。 |
| `context_profile` | `auto`(默认)或 `local`。`local` 面向 32K–256K 本地模型:预算折到窗口的 60%(上限 128K),收紧摘要预留、工具输出截断/清理、用户消息与账本上限,并启用 `tool_search` 与精简常驻工具。数值参数只收紧、不放大,同时按需启用 tool_search/reduction。 |
| `reasoning` | 该通道的默认推理扩展字段。不同网关支持差异较大,异常时先尝试 `mode: off`。 |

### 本地模型(32K–256K)上下文要点

本地模型的有效上下文通常明显小于标称窗口,且不同后端的“模型上限”和“运行时窗口”可能不一致。建议:

1. **先确认运行时窗口**:在 **系统设置 → 基本设置 → AI 通道配置 → 模型上下文窗口** 右侧点击「自动检测」。探测顺序为 Ollama `/api/ps`、`/api/show`,llama.cpp `/props`,LM Studio `/api/v0/models`,vLLM/OpenAI 兼容 `/v1/models`;同时支持反向代理子路径(如 `https://host/ollama/v1`)。检测结果会显示按当前档位折算后的有效预算。检测不到时请手动填写服务端实际配置。
2. **启用本地档位**:把 `context_profile` 设为 `local`。预算按窗口 60% 计算,同时不超过“窗口 − 输出预留”(例如 32K 窗口、默认输出上限 16K 时预算为 16K);单次输出上限也会被压到窗口的一半以内。摘要更早触发;工具输出截断阈值约为窗口/6(8–32 KB),历史工具结果清理阈值约为预算的 35%;常驻工具收敛到核心集合,内置工具也进入 `tool_search` 动态池,其余工具按需解锁。token 估算会额外放大 10%,抵消本地 tokenizer 与 tiktoken 在中英文上的差异。该档位还会把输出上限字段切换为更通用的 `max_tokens`(Ollama/llama.cpp 等常不识别 `max_completion_tokens`),攻击链、FOFA 语法生成、工作流草稿、HITL 审计等旁路调用同样生效。
3. **不要按标称 1M 规划**:Qwen3.5 等模型原生 262,144,可用 YaRN 扩到 1M,但本地显存与长上下文性能衰减(context rot)会让实际效果明显下降;带工具的 agent 建议把有效预算控制在 64K–128K。
4. **Ollama 必须设置 `num_ctx`**:Ollama 的 `/v1` 兼容端点会忽略请求里的 `num_ctx`,并按显存选择默认窗口(约 4K/32K/256K);超长时从开头截断,系统提示词和工具定义会先丢失。请用 `OLLAMA_CONTEXT_LENGTH=65536 ollama serve` 或 Modelfile `PARAMETER num_ctx 65536`,重启后用 `ollama ps` 确认 `CONTEXT`。
5. **KV cache 与前缀缓存**:
- llama.cpp:`--ctx-size 65536 --cache-type-k q8_0 --cache-type-v q8_0 -fa`;`--cache-prompt` 默认开启。`--parallel` 会让每个槽位分摊上下文;检测结果会返回 `context_slots` 并在大于 1 时提示,请按实际 KV 模式(如 `--kv-unified`)确认单请求可用窗口。
- vLLM:`--max-model-len 65536 --enable-prefix-caching --kv-cache-dtype fp8`;用 `--max-num-seqs` 控制并发 KV 占用。
- Ollama:`OLLAMA_KV_CACHE_TYPE=q8_0`、`OLLAMA_NUM_PARALLEL=1`;窗口越大 KV cache 越大。
- LM Studio:在模型设置中显式设置 Context Length,并开启可用的 KV cache 量化。
6. **思考模型**:Qwen3.5 等默认开启 thinking,会在工具循环中消耗大量上下文。可通过通道的 `reasoning.extra_request_fields.chat_template_kwargs.enable_thinking: false` 关闭(vLLM、llama.cpp `--jinja` 支持);`reasoning.mode: off` 只清理 OpenAI/DeepSeek 推理字段,不一定能关闭本地 chat template 的 thinking。
7. **建议窗口下限**:带 90+ 工具的 agent 在 16K 以下很难稳定运行;32K 需要依赖 `tool_search` 和 `context_profile: local`;64K 以上更接近云端模型的使用体验。
8. **故障转移通道不会重新预算**:`multi_agent.eino_middleware.model_failover_channels` 只切换模型端点,预算仍按主通道计算;请让 failover 通道的可用窗口不小于主通道,否则可能依赖超窗压缩/重试。

对话页的“AI 通道”下拉框会读取已保存通道。请求体中的 `aiChannelId` 非空时仅对本次/本会话运行配置生效,不会把 API Key 发送给模型;为空时跟随 `ai.default_channel`。

常用操作:
Expand Down
1 change: 1 addition & 0 deletions internal/app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -1131,6 +1131,7 @@ func setupRoutes(
protected.PUT("/config", configHandler.UpdateConfig)
protected.POST("/config/apply", configHandler.ApplyConfig)
protected.POST("/config/test-openai", configHandler.TestOpenAI)
protected.POST("/config/detect-context", configHandler.DetectContext)
protected.POST("/config/test-typesafe", configHandler.TestTypeSafe)
protected.POST("/config/test-vision", configHandler.TestVision)
protected.POST("/config/list-models", configHandler.ListModels)
Expand Down
28 changes: 7 additions & 21 deletions internal/attackchain/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,25 +52,10 @@ func NewBuilder(db *database.DB, openAIConfig *config.OpenAIConfig, logger *zap.
}
httpClient := &http.Client{Timeout: 5 * time.Minute, Transport: transport}

// 优先使用配置文件中的统一 Token 上限(config.yaml -> openai.max_total_tokens)
maxTokens := 0
if openAIConfig != nil && openAIConfig.MaxTotalTokens > 0 {
maxTokens = openAIConfig.MaxTotalTokens
} else if openAIConfig != nil {
// 如果未显式配置 max_total_tokens,则根据模型设置一个合理的默认值
model := strings.ToLower(openAIConfig.Model)
if strings.Contains(model, "gpt-4") {
maxTokens = 128000 // gpt-4通常支持128k
} else if strings.Contains(model, "gpt-3.5") {
maxTokens = 16000 // gpt-3.5-turbo通常支持16k
} else if strings.Contains(model, "deepseek") {
maxTokens = 131072 // deepseek-chat通常支持131k
} else {
maxTokens = 100000 // 兜底默认值
}
} else {
// 没有 OpenAI 配置时使用兜底值,避免为 0
maxTokens = 100000
// 统一走通道的有效预算:已按 context_window / context_profile 收紧。
maxTokens := 100000
if openAIConfig != nil {
maxTokens = openAIConfig.EffectiveMaxTotalTokens()
}

return &Builder{
Expand Down Expand Up @@ -897,9 +882,10 @@ func (b *Builder) callAIForChainGeneration(ctx context.Context, prompt string) (
"content": prompt,
},
},
"temperature": 0.3,
"max_completion_tokens": attackChainMaxCompletionTokens(b.maxTokens),
"temperature": 0.3,
b.openAIConfig.OutputLimitField(): attackChainMaxCompletionTokens(b.maxTokens),
}
config.ApplyExtraRequestFields(requestBody, b.openAIConfig.Reasoning.ExtraRequestFields)

var apiResponse struct {
Choices []struct {
Expand Down
Loading
Loading