Fix Bedrock tool history and add upload limit config
This commit is contained in:
Executable
+28
@@ -0,0 +1,28 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# 配置线上 nginx 上传体积限制。
|
||||||
|
# 需要 sudo 权限;应用服务本身仍然用普通用户运行。
|
||||||
|
|
||||||
|
LIMIT="${CLAW_NGINX_CLIENT_MAX_BODY_SIZE:-300m}"
|
||||||
|
CONF_PATH="${CLAW_NGINX_UPLOAD_CONF:-/etc/nginx/conf.d/zk-data-agent-upload.conf}"
|
||||||
|
|
||||||
|
if ! command -v nginx >/dev/null 2>&1; then
|
||||||
|
echo "WARN 未发现 nginx,跳过上传限制配置。"
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if ! command -v sudo >/dev/null 2>&1; then
|
||||||
|
echo "ERROR 配置 nginx 需要 sudo。" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
sudo tee "${CONF_PATH}" >/dev/null <<EOF
|
||||||
|
# Managed by zk-data-agent.
|
||||||
|
# 放开 Web 文件上传体积,避免大文件上传被 nginx 直接 413。
|
||||||
|
client_max_body_size ${LIMIT};
|
||||||
|
EOF
|
||||||
|
|
||||||
|
sudo nginx -t
|
||||||
|
sudo systemctl reload nginx
|
||||||
|
echo "已配置 nginx client_max_body_size=${LIMIT} (${CONF_PATH})"
|
||||||
@@ -122,6 +122,17 @@ bootstrap_system_packages() {
|
|||||||
systemd
|
systemd
|
||||||
}
|
}
|
||||||
|
|
||||||
|
configure_nginx_upload_limit() {
|
||||||
|
if [[ "${BOOTSTRAP_SYSTEM}" != "1" && -f "${ENV_FILE}" ]]; then
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
local script="${ROOT_DIR}/scripts/configure-nginx-upload-limit.sh"
|
||||||
|
if [[ -x "${script}" ]]; then
|
||||||
|
log "配置 nginx 上传体积限制"
|
||||||
|
"${script}" || warn "nginx 上传体积限制配置失败,可稍后手动执行:bash ${script}"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
prompt_value() {
|
prompt_value() {
|
||||||
local var_name="$1"
|
local var_name="$1"
|
||||||
local prompt="$2"
|
local prompt="$2"
|
||||||
@@ -345,6 +356,7 @@ health_check() {
|
|||||||
main() {
|
main() {
|
||||||
log "ZK Data Agent Ubuntu 部署"
|
log "ZK Data Agent Ubuntu 部署"
|
||||||
bootstrap_system_packages
|
bootstrap_system_packages
|
||||||
|
configure_nginx_upload_limit
|
||||||
update_git
|
update_git
|
||||||
ensure_env_file
|
ensure_env_file
|
||||||
configure_instance_names
|
configure_instance_names
|
||||||
|
|||||||
+94
-1
@@ -91,6 +91,99 @@ def _parse_tool_arguments(raw_arguments: Any) -> dict[str, Any]:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _tool_call_ids(message: dict[str, Any]) -> list[str]:
|
||||||
|
raw_tool_calls = message.get('tool_calls')
|
||||||
|
if not isinstance(raw_tool_calls, list):
|
||||||
|
return []
|
||||||
|
ids: list[str] = []
|
||||||
|
for index, raw_call in enumerate(raw_tool_calls):
|
||||||
|
if not isinstance(raw_call, dict):
|
||||||
|
continue
|
||||||
|
call_id = raw_call.get('id')
|
||||||
|
ids.append(call_id if isinstance(call_id, str) and call_id else f'call_{index}')
|
||||||
|
return ids
|
||||||
|
|
||||||
|
|
||||||
|
def _contiguous_tool_result_ids(
|
||||||
|
messages: list[dict[str, Any]],
|
||||||
|
start_index: int,
|
||||||
|
) -> set[str]:
|
||||||
|
ids: set[str] = set()
|
||||||
|
index = start_index
|
||||||
|
while index < len(messages) and messages[index].get('role') == 'tool':
|
||||||
|
tool_call_id = messages[index].get('tool_call_id')
|
||||||
|
if isinstance(tool_call_id, str) and tool_call_id:
|
||||||
|
ids.add(tool_call_id)
|
||||||
|
index += 1
|
||||||
|
return ids
|
||||||
|
|
||||||
|
|
||||||
|
def _filter_tool_calls_by_ids(
|
||||||
|
message: dict[str, Any],
|
||||||
|
valid_ids: set[str],
|
||||||
|
) -> dict[str, Any]:
|
||||||
|
raw_tool_calls = message.get('tool_calls')
|
||||||
|
if not isinstance(raw_tool_calls, list):
|
||||||
|
return message
|
||||||
|
filtered: list[dict[str, Any]] = []
|
||||||
|
for index, raw_call in enumerate(raw_tool_calls):
|
||||||
|
if not isinstance(raw_call, dict):
|
||||||
|
continue
|
||||||
|
call_id = raw_call.get('id')
|
||||||
|
normalized_id = (
|
||||||
|
call_id if isinstance(call_id, str) and call_id else f'call_{index}'
|
||||||
|
)
|
||||||
|
if normalized_id in valid_ids:
|
||||||
|
filtered.append(raw_call)
|
||||||
|
if len(filtered) == len(raw_tool_calls):
|
||||||
|
return message
|
||||||
|
next_message = dict(message)
|
||||||
|
if filtered:
|
||||||
|
next_message['tool_calls'] = filtered
|
||||||
|
else:
|
||||||
|
next_message.pop('tool_calls', None)
|
||||||
|
return next_message
|
||||||
|
|
||||||
|
|
||||||
|
def _sanitize_anthropic_tool_messages(
|
||||||
|
messages: list[dict[str, Any]],
|
||||||
|
) -> list[dict[str, Any]]:
|
||||||
|
"""清理 Anthropic/Bedrock 严格要求的 tool_use/tool_result 邻接关系。
|
||||||
|
|
||||||
|
OpenAI 风格历史在异常中断、裁剪或前端恢复时可能留下 assistant tool_calls,
|
||||||
|
但下一条消息不再紧跟对应 tool 结果。Anthropic Messages API 会直接拒绝
|
||||||
|
这种历史。这里在出网关前删除缺失结果的 tool_call,并跳过没有前置
|
||||||
|
tool_use 的孤儿 tool 结果,保留普通文本上下文继续对话。
|
||||||
|
"""
|
||||||
|
sanitized: list[dict[str, Any]] = []
|
||||||
|
pending_tool_ids: set[str] = set()
|
||||||
|
|
||||||
|
for index, message in enumerate(messages):
|
||||||
|
role = message.get('role')
|
||||||
|
if role == 'assistant':
|
||||||
|
tool_ids = _tool_call_ids(message)
|
||||||
|
if tool_ids:
|
||||||
|
result_ids = _contiguous_tool_result_ids(messages, index + 1)
|
||||||
|
valid_ids = set(tool_ids).intersection(result_ids)
|
||||||
|
message = _filter_tool_calls_by_ids(message, valid_ids)
|
||||||
|
pending_tool_ids = valid_ids
|
||||||
|
else:
|
||||||
|
pending_tool_ids = set()
|
||||||
|
sanitized.append(message)
|
||||||
|
continue
|
||||||
|
|
||||||
|
if role == 'tool':
|
||||||
|
tool_call_id = message.get('tool_call_id')
|
||||||
|
if isinstance(tool_call_id, str) and tool_call_id in pending_tool_ids:
|
||||||
|
sanitized.append(message)
|
||||||
|
continue
|
||||||
|
|
||||||
|
pending_tool_ids = set()
|
||||||
|
sanitized.append(message)
|
||||||
|
|
||||||
|
return sanitized
|
||||||
|
|
||||||
|
|
||||||
def _optional_int(value: Any) -> int:
|
def _optional_int(value: Any) -> int:
|
||||||
if isinstance(value, bool):
|
if isinstance(value, bool):
|
||||||
return 0
|
return 0
|
||||||
@@ -341,7 +434,7 @@ class OpenAICompatClient:
|
|||||||
) -> dict[str, Any]:
|
) -> dict[str, Any]:
|
||||||
system_parts: list[str] = []
|
system_parts: list[str] = []
|
||||||
anthropic_messages: list[dict[str, Any]] = []
|
anthropic_messages: list[dict[str, Any]] = []
|
||||||
for message in messages:
|
for message in _sanitize_anthropic_tool_messages(messages):
|
||||||
role = message.get('role')
|
role = message.get('role')
|
||||||
if role == 'system':
|
if role == 'system':
|
||||||
content = _normalize_content(message.get('content')).strip()
|
content = _normalize_content(message.get('content')).strip()
|
||||||
|
|||||||
Reference in New Issue
Block a user