添加修改模型请求头
This commit is contained in:
parent
406a36af89
commit
e726f11bad
9 changed files with 109 additions and 1 deletions
|
|
@ -137,6 +137,8 @@ def add_mapping():
|
|||
'api_key': data.get('api_key', ''),
|
||||
'custom_instructions': data.get('custom_instructions', ''),
|
||||
'instructions_position': data.get('instructions_position', 'prepend'),
|
||||
'body_modifications': data.get('body_modifications') or {},
|
||||
'header_modifications': data.get('header_modifications') or {},
|
||||
}
|
||||
return _save_and_respond(s, f'映射已添加: {name}')
|
||||
|
||||
|
|
@ -161,6 +163,8 @@ def update_mapping(name):
|
|||
'api_key': data.get('api_key', ''),
|
||||
'custom_instructions': data.get('custom_instructions', ''),
|
||||
'instructions_position': data.get('instructions_position', 'prepend'),
|
||||
'body_modifications': data.get('body_modifications') or {},
|
||||
'header_modifications': data.get('header_modifications') or {},
|
||||
}
|
||||
if new_name != name:
|
||||
del mappings[name]
|
||||
|
|
|
|||
|
|
@ -28,6 +28,8 @@ from adapters.responses_cc_adapter import (
|
|||
from config import Config
|
||||
from routes.common import (
|
||||
RouteContext,
|
||||
apply_body_modifications,
|
||||
apply_header_modifications,
|
||||
build_anthropic_target,
|
||||
build_openai_target,
|
||||
build_responses_target,
|
||||
|
|
@ -118,6 +120,8 @@ def _handle_openai_backend(ctx: RouteContext, payload: dict[str, Any]):
|
|||
)
|
||||
|
||||
url, headers = build_openai_target(ctx)
|
||||
payload = apply_body_modifications(payload, ctx.body_modifications)
|
||||
headers = apply_header_modifications(headers, ctx.header_modifications)
|
||||
|
||||
if ctx.is_stream:
|
||||
return _handle_openai_stream(ctx, payload, url, headers)
|
||||
|
|
@ -208,6 +212,8 @@ def _handle_responses_backend(ctx: RouteContext, payload: dict[str, Any]):
|
|||
)
|
||||
|
||||
url, headers = build_responses_target(ctx)
|
||||
responses_payload = apply_body_modifications(responses_payload, ctx.body_modifications)
|
||||
headers = apply_header_modifications(headers, ctx.header_modifications)
|
||||
|
||||
if ctx.is_stream:
|
||||
return _handle_responses_stream(ctx, responses_payload, url, headers)
|
||||
|
|
@ -285,6 +291,8 @@ def _handle_anthropic_backend(ctx: RouteContext, payload: dict[str, Any]):
|
|||
)
|
||||
|
||||
url, headers = build_anthropic_target(ctx)
|
||||
anthropic_payload = apply_body_modifications(anthropic_payload, ctx.body_modifications)
|
||||
headers = apply_header_modifications(headers, ctx.header_modifications)
|
||||
|
||||
if ctx.is_stream:
|
||||
return _handle_anthropic_stream(ctx, anthropic_payload, url, headers)
|
||||
|
|
|
|||
|
|
@ -34,6 +34,8 @@ class RouteContext:
|
|||
is_stream: bool
|
||||
custom_instructions: str
|
||||
instructions_position: str
|
||||
body_modifications: dict
|
||||
header_modifications: dict
|
||||
|
||||
|
||||
def build_route_context(client_model: str, is_stream: bool) -> RouteContext:
|
||||
|
|
@ -48,6 +50,8 @@ def build_route_context(client_model: str, is_stream: bool) -> RouteContext:
|
|||
is_stream=is_stream,
|
||||
custom_instructions=mapping.get('custom_instructions', ''),
|
||||
instructions_position=mapping.get('instructions_position', 'prepend'),
|
||||
body_modifications=mapping.get('body_modifications', {}),
|
||||
header_modifications=mapping.get('header_modifications', {}),
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -193,3 +197,38 @@ def inject_instructions_anthropic(payload: dict[str, Any], instructions: str, po
|
|||
|
||||
logger.info('已注入自定义指令到 Anthropic system (%d 字符, %s)', len(instructions), position)
|
||||
return payload
|
||||
|
||||
|
||||
# ─── Body / Header 修改 ──────────────────────────
|
||||
|
||||
|
||||
def apply_body_modifications(payload: dict[str, Any], modifications: dict[str, Any]) -> dict[str, Any]:
|
||||
"""对转发请求体应用字段级修改。
|
||||
|
||||
规则与 CursorProxy 一致:值为 null 的字段会被删除,其余字段设置/覆盖。
|
||||
"""
|
||||
if not modifications:
|
||||
return payload
|
||||
for key, value in modifications.items():
|
||||
if value is None:
|
||||
payload.pop(key, None)
|
||||
else:
|
||||
payload[key] = value
|
||||
logger.info('已应用 body_modifications: %s', list(modifications.keys()))
|
||||
return payload
|
||||
|
||||
|
||||
def apply_header_modifications(headers: dict[str, str], modifications: dict[str, Any]) -> dict[str, str]:
|
||||
"""对转发请求头应用字段级修改。
|
||||
|
||||
规则同 body:值为 null 删除,其余设置/覆盖。
|
||||
"""
|
||||
if not modifications:
|
||||
return headers
|
||||
for key, value in modifications.items():
|
||||
if value is None:
|
||||
headers.pop(key, None)
|
||||
else:
|
||||
headers[key] = str(value)
|
||||
logger.info('已应用 header_modifications: %s', list(modifications.keys()))
|
||||
return headers
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ from flask import Blueprint, request, jsonify
|
|||
|
||||
import settings
|
||||
from config import Config
|
||||
from routes.common import inject_instructions_anthropic
|
||||
from routes.common import apply_body_modifications, apply_header_modifications, inject_instructions_anthropic
|
||||
from utils.http import build_anthropic_headers, forward_request, sse_response
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
|
@ -35,10 +35,14 @@ def messages_passthrough():
|
|||
api_key = mapping['api_key']
|
||||
custom_instructions = mapping.get('custom_instructions', '')
|
||||
instructions_position = mapping.get('instructions_position', 'prepend')
|
||||
body_mods = mapping.get('body_modifications', {})
|
||||
header_mods = mapping.get('header_modifications', {})
|
||||
headers = build_anthropic_headers(api_key)
|
||||
headers = apply_header_modifications(headers, header_mods)
|
||||
url = f'{url_base.rstrip("/")}/v1/messages'
|
||||
|
||||
payload = inject_instructions_anthropic(payload, custom_instructions, instructions_position)
|
||||
payload = apply_body_modifications(payload, body_mods)
|
||||
|
||||
if not is_stream:
|
||||
resp, err = forward_request(url, headers, payload)
|
||||
|
|
|
|||
|
|
@ -18,6 +18,8 @@ from adapters.responses_cc_adapter import ResponsesStreamConverter, cc_to_respon
|
|||
from config import Config
|
||||
from routes.common import (
|
||||
RouteContext,
|
||||
apply_body_modifications,
|
||||
apply_header_modifications,
|
||||
build_anthropic_target,
|
||||
build_openai_target,
|
||||
build_responses_target,
|
||||
|
|
@ -93,6 +95,8 @@ def _handle_openai_backend(ctx: RouteContext, cc_payload: dict[str, Any]):
|
|||
)
|
||||
|
||||
url, headers = build_openai_target(ctx)
|
||||
cc_payload = apply_body_modifications(cc_payload, ctx.body_modifications)
|
||||
headers = apply_header_modifications(headers, ctx.header_modifications)
|
||||
|
||||
if ctx.is_stream:
|
||||
return _handle_openai_stream(ctx, cc_payload, url, headers)
|
||||
|
|
@ -177,6 +181,8 @@ def _handle_responses_backend(ctx: RouteContext, payload: dict[str, Any]):
|
|||
payload['model'] = ctx.upstream_model
|
||||
payload = inject_instructions_responses(payload, ctx.custom_instructions, ctx.instructions_position)
|
||||
url, headers = build_responses_target(ctx)
|
||||
payload = apply_body_modifications(payload, ctx.body_modifications)
|
||||
headers = apply_header_modifications(headers, ctx.header_modifications)
|
||||
|
||||
if ctx.is_stream:
|
||||
return _handle_responses_stream(ctx, payload, url, headers)
|
||||
|
|
@ -241,6 +247,8 @@ def _handle_anthropic_backend(ctx: RouteContext, cc_payload: dict[str, Any]):
|
|||
)
|
||||
|
||||
url, headers = build_anthropic_target(ctx)
|
||||
anthropic_payload = apply_body_modifications(anthropic_payload, ctx.body_modifications)
|
||||
headers = apply_header_modifications(headers, ctx.header_modifications)
|
||||
|
||||
if ctx.is_stream:
|
||||
return _handle_anthropic_stream(ctx, anthropic_payload, url, headers)
|
||||
|
|
|
|||
|
|
@ -95,6 +95,8 @@ def resolve_model(model_name):
|
|||
'api_key': m.get('api_key') or base_key,
|
||||
'custom_instructions': m.get('custom_instructions') or '',
|
||||
'instructions_position': m.get('instructions_position') or 'prepend',
|
||||
'body_modifications': m.get('body_modifications') or {},
|
||||
'header_modifications': m.get('header_modifications') or {},
|
||||
}
|
||||
|
||||
return {
|
||||
|
|
@ -104,6 +106,8 @@ def resolve_model(model_name):
|
|||
'api_key': base_key,
|
||||
'custom_instructions': '',
|
||||
'instructions_position': 'prepend',
|
||||
'body_modifications': {},
|
||||
'header_modifications': {},
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -62,6 +62,7 @@ main{padding:28px 0 60px}
|
|||
.tag-auto{background:rgba(139,92,246,.15);color:#a78bfa}
|
||||
.tag-override{background:rgba(59,130,246,.1);color:var(--primary)}
|
||||
.tag-instructions{background:rgba(234,179,8,.15);color:var(--yellow)}
|
||||
.tag-mods{background:rgba(168,85,247,.15);color:#c084fc}
|
||||
.mapping-actions{margin-left:auto;display:flex;gap:6px}
|
||||
.empty{text-align:center;padding:40px;color:var(--muted)}
|
||||
|
||||
|
|
|
|||
|
|
@ -133,6 +133,22 @@
|
|||
</div>
|
||||
<div class="hint">前置:自定义指令放在系统提示词最前面,模型优先看到;后置:放在末尾</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>Body 修改 <span style="color:var(--muted)">(可选,JSON 格式)</span></label>
|
||||
<div class="input-wrap"><textarea id="mBodyMods" rows="3" placeholder='例: {"reasoning_effort": "high", "stream_options": null}' style="resize:vertical;min-height:60px;font-family:Consolas,Monaco,monospace;font-size:13px"></textarea></div>
|
||||
<div class="hint">
|
||||
对转发到上游的请求体做字段级增删改。值为 <code>null</code> 表示删除该字段,其余为设置/覆盖。<br>
|
||||
适用场景:注入 <code>reasoning_effort</code>、删除上游不支持的 <code>stream_options</code> 等。
|
||||
</div>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>Header 修改 <span style="color:var(--muted)">(可选,JSON 格式)</span></label>
|
||||
<div class="input-wrap"><textarea id="mHeaderMods" rows="3" placeholder='例: {"X-Custom-Header": "value", "X-Unwanted": null}' style="resize:vertical;min-height:60px;font-family:Consolas,Monaco,monospace;font-size:13px"></textarea></div>
|
||||
<div class="hint">
|
||||
对转发到上游的请求头做增删改,规则同 Body 修改。<br>
|
||||
适用场景:按模型设置不同的 <code>Authorization</code>、API 版本头等。
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-ghost" onclick="closeModal()">取消</button>
|
||||
<button class="btn btn-primary" id="modalSaveBtn" onclick="saveMapping()">保存</button>
|
||||
|
|
|
|||
|
|
@ -138,6 +138,8 @@ async function loadMappings() {
|
|||
: backend;
|
||||
const hasOverride = m.target_url || m.api_key;
|
||||
const hasInstructions = !!m.custom_instructions;
|
||||
const hasBodyMods = m.body_modifications && Object.keys(m.body_modifications).length > 0;
|
||||
const hasHeaderMods = m.header_modifications && Object.keys(m.header_modifications).length > 0;
|
||||
return `<div class="mapping-item">
|
||||
<div class="mapping-top">
|
||||
<span class="mapping-name">${esc(name)}</span>
|
||||
|
|
@ -147,6 +149,8 @@ async function loadMappings() {
|
|||
<span class="tag ${tagClass}">${tagLabel}</span>
|
||||
${hasOverride ? '<span class="tag tag-override">自定义地址</span>' : ''}
|
||||
${hasInstructions ? '<span class="tag tag-instructions">自定义指令</span>' : ''}
|
||||
${hasBodyMods ? '<span class="tag tag-mods">Body修改</span>' : ''}
|
||||
${hasHeaderMods ? '<span class="tag tag-mods">Header修改</span>' : ''}
|
||||
</div>
|
||||
<div class="mapping-actions">
|
||||
<button class="btn btn-ghost btn-sm" onclick="openEditModal('${esc(name)}')">编辑</button>
|
||||
|
|
@ -171,6 +175,8 @@ function openAddModal() {
|
|||
document.getElementById('mKey').value = '';
|
||||
document.getElementById('mInstructions').value = '';
|
||||
document.getElementById('mInsPosition').value = 'prepend';
|
||||
document.getElementById('mBodyMods').value = '';
|
||||
document.getElementById('mHeaderMods').value = '';
|
||||
document.getElementById('modal').classList.add('active');
|
||||
}
|
||||
|
||||
|
|
@ -189,6 +195,8 @@ async function openEditModal(name) {
|
|||
document.getElementById('mKey').value = m.api_key || '';
|
||||
document.getElementById('mInstructions').value = m.custom_instructions || '';
|
||||
document.getElementById('mInsPosition').value = m.instructions_position || 'prepend';
|
||||
document.getElementById('mBodyMods').value = m.body_modifications && Object.keys(m.body_modifications).length ? JSON.stringify(m.body_modifications, null, 2) : '';
|
||||
document.getElementById('mHeaderMods').value = m.header_modifications && Object.keys(m.header_modifications).length ? JSON.stringify(m.header_modifications, null, 2) : '';
|
||||
document.getElementById('modal').classList.add('active');
|
||||
} catch (e) {
|
||||
toast('错误: ' + e.message, false);
|
||||
|
|
@ -206,6 +214,20 @@ async function saveMapping() {
|
|||
if (!name) { toast('请填写 Cursor 模型名', false); return; }
|
||||
if (!upstream) { toast('请填写上游模型名', false); return; }
|
||||
|
||||
let bodyMods = {};
|
||||
const bodyModsStr = document.getElementById('mBodyMods').value.trim();
|
||||
if (bodyModsStr) {
|
||||
try { bodyMods = JSON.parse(bodyModsStr); }
|
||||
catch { toast('Body 修改不是有效的 JSON', false); return; }
|
||||
}
|
||||
|
||||
let headerMods = {};
|
||||
const headerModsStr = document.getElementById('mHeaderMods').value.trim();
|
||||
if (headerModsStr) {
|
||||
try { headerMods = JSON.parse(headerModsStr); }
|
||||
catch { toast('Header 修改不是有效的 JSON', false); return; }
|
||||
}
|
||||
|
||||
const payload = {
|
||||
name,
|
||||
upstream_model: upstream,
|
||||
|
|
@ -214,6 +236,8 @@ async function saveMapping() {
|
|||
api_key: document.getElementById('mKey').value.trim(),
|
||||
custom_instructions: document.getElementById('mInstructions').value,
|
||||
instructions_position: document.getElementById('mInsPosition').value,
|
||||
body_modifications: bodyMods,
|
||||
header_modifications: headerMods,
|
||||
};
|
||||
|
||||
try {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue