From 6eb24b6c2c9a84e99259b4618893392dec127d96 Mon Sep 17 00:00:00 2001 From: mozhou52 Date: Fri, 26 Sep 2025 15:18:40 +0800 Subject: [PATCH] feat: add e2e model tests and refactor model deployment logic - Add model download and deployment tests in CI script - Refactor NAS and OSS mount point configurations in function deployment - Remove outdated example files and test scripts - Simplify model server address generation logic --- __tests__/e2e/ci-mac-linux.sh | 9 + .../e2e/local/layer/python/code/index.py | 1 + __tests__/e2e/local/nas/code/index.py | 19 +- __tests__/e2e/model/deploy_and_test_model.py | 396 ++++++++++++++++++ __tests__/e2e/model/requirements.txt | 2 + __tests__/e2e/model/s.yaml | 7 + __tests__/e2e/nodejs/test-auto-code/index.js | 2 +- __tests__/it/code/index.py | 2 +- .../ut/commands/deploy/impl/function_test.ts | 3 +- example/s-pipeline.yaml | 58 --- example/s.yaml | 68 --- example/test_models_VLLM.sh | 125 ------ example/test_models_pipeline.sh | 120 ------ src/subCommands/deploy/impl/function.ts | 15 +- src/subCommands/model/index.ts | 2 +- 15 files changed, 434 insertions(+), 395 deletions(-) create mode 100644 __tests__/e2e/model/deploy_and_test_model.py create mode 100644 __tests__/e2e/model/requirements.txt create mode 100644 __tests__/e2e/model/s.yaml delete mode 100644 example/s-pipeline.yaml delete mode 100644 example/s.yaml delete mode 100755 example/test_models_VLLM.sh delete mode 100755 example/test_models_pipeline.sh diff --git a/__tests__/e2e/ci-mac-linux.sh b/__tests__/e2e/ci-mac-linux.sh index 2882db05..cee46330 100755 --- a/__tests__/e2e/ci-mac-linux.sh +++ b/__tests__/e2e/ci-mac-linux.sh @@ -26,6 +26,15 @@ else echo "skip test trigger" fi + +echo "test model download" +cd model +pip install -r requirements.txt +export fc_component_function_name=model-$(uname)-$(uname -m)-$RANDSTR +python deploy_and_test_model.py --model-id iic/cv_LightweightEdge_ocr-recognitoin-general_damo --region cn-shanghai --auto-cleanup +python deploy_and_test_model.py --model-id Qwen/Qwen2.5-0.5B-Instruct --region cn-shanghai --auto-cleanup +cd .. + echo "test go runtime" cd go export fc_component_function_name=go1-$(uname)-$(uname -m)-$RANDSTR diff --git a/__tests__/e2e/local/layer/python/code/index.py b/__tests__/e2e/local/layer/python/code/index.py index 28dcbb42..6a5bc328 100644 --- a/__tests__/e2e/local/layer/python/code/index.py +++ b/__tests__/e2e/local/layer/python/code/index.py @@ -5,6 +5,7 @@ from flask import Flask import pycurl + def handler(event, context): logger = logging.getLogger() logger.info(event) diff --git a/__tests__/e2e/local/nas/code/index.py b/__tests__/e2e/local/nas/code/index.py index 6239eb77..12325628 100644 --- a/__tests__/e2e/local/nas/code/index.py +++ b/__tests__/e2e/local/nas/code/index.py @@ -3,15 +3,16 @@ import logging import os + def handler(event, context): - filename = '/mnt/auto/test.txt' - if os.path.exists(filename): - with open(filename, 'r') as file: - content = file.read() + filename = "/mnt/auto/test.txt" + if os.path.exists(filename): + with open(filename, "r") as file: + content = file.read() return "read:{}".format(content) - else: - with open(filename, 'w') as file: - file.write("hello world") - with open(filename, 'r') as file: - content = file.read() + else: + with open(filename, "w") as file: + file.write("hello world") + with open(filename, "r") as file: + content = file.read() return "write:{}".format(content) diff --git a/__tests__/e2e/model/deploy_and_test_model.py b/__tests__/e2e/model/deploy_and_test_model.py new file mode 100644 index 00000000..f7e51d82 --- /dev/null +++ b/__tests__/e2e/model/deploy_and_test_model.py @@ -0,0 +1,396 @@ +#!/usr/bin/env python3 +""" +模型部署和测试脚本 +""" + +import hashlib +import json +import os +import subprocess +import urllib.parse +import secrets +import string + +import requests +import yaml + + +# 生成随机token +def generate_random_token(length=30): + """生成指定长度的随机token""" + characters = string.ascii_letters + string.digits + return "sk-" + "".join(secrets.choice(characters) for _ in range(length)) + + +token = generate_random_token() + + +def simple_hash(input_string: str) -> str: + """ + 生成固定长度16的字符串哈希值 + + 参数: + input_string: 要哈希的字符串 + + 返回: + 16个字符的十六进制哈希字符串 + """ + # 使用SHA-256算法生成哈希 + sha256_hash = hashlib.sha256(input_string.encode("utf-8")).hexdigest() + + # 取前8字符和后8字符组合成16字符 + return sha256_hash[:8] + sha256_hash[-8:] + + +def deploy_model(model_id: str, region: str = "cn-hangzhou"): + """ + 部署模型到函数计算 + + Args: + model_id: 模型ID + region: 部署区域 + + Returns: + tuple: (部署的URL, 配置文件路径) + """ + # 生成函数名称 + function_name = f"test-{simple_hash(model_id)}" + + # 准备请求数据 + deploy_data = { + "functionName": function_name, + "region": region, + "authType": "bearer", + "tokenData": token, + } + + # 打印生成的token + print(f"生成的随机token: {token}") + + # URL编码模型ID + encoded_model_id = urllib.parse.quote(model_id, safe="") + + # 调用部署接口 + model_registry_url = os.getenv( + "MODEL_REGISTRY_URL", "model-registry.devsapp.cn" + ) + deploy_url = ( + f"http://{model_registry_url}/api/v1/models/{encoded_model_id}/deploy-info" + ) + print(f"deploy url: {deploy_url}") + print(f"正在部署模型: {model_id}") + print(f"请求URL: {deploy_url}") + print(f"请求数据: {json.dumps(deploy_data, indent=2)}") + + response = requests.post( + deploy_url, + headers={"accept": "application/json", "Content-Type": "application/json"}, + data=json.dumps(deploy_data), + ) + + if response.status_code != 200: + raise Exception(f"部署请求失败: {response.status_code} - {response.text}") + + result = response.json() + print(f"部署响应: {json.dumps(result, indent=2)}") + + # 检查响应是否成功 + if not result.get("success", False): + raise Exception(f"部署失败: {result.get('error', 'Unknown error')}") + + # 提取部署信息 + deploy_info = result.get("data", {}) + + # 使用当前文件夹下的s.yaml内容作为基础配置 + with open("s.yaml", "r", encoding="utf-8") as f: + s_yaml = yaml.safe_load(f) + + # 更新resources中的props + s_yaml["resources"]["test_func"]["props"] = deploy_info + + # 保存配置到临时文件 + s_yaml_file = f"s.yaml" + with open(s_yaml_file, "w", encoding="utf-8") as f: + yaml.dump(s_yaml, f, default_flow_style=False, allow_unicode=True) + + print(f"使用配置文件部署: {s_yaml_file}") + + # 执行部署命令 + try: + # 下载模型 + print("正在下载模型...") + subprocess.check_call(f"s model download -y -t {s_yaml_file}", shell=True) + + # 部署函数 + print("正在部署函数...") + subprocess.check_call(f"s deploy -y -t {s_yaml_file} --skip-push", shell=True) + + # 获取部署信息 + print("正在获取部署信息...") + result = subprocess.check_output( + f"s info -t {s_yaml_file} --silent -o json", + shell=True, + ).strip() + + result_dict = json.loads(result) + deploy_url = result_dict["url"]["system_url"] + print(f"部署成功,访问URL: {deploy_url}") + + return deploy_url, s_yaml_file + + except subprocess.CalledProcessError as e: + raise Exception(f"部署过程失败: {e}") + except Exception as e: + raise Exception(f"获取部署信息失败: {e}") + + +def test_model(model_id: str, deploy_url: str, s_yaml_file: str = None): + """ + 测试已部署的模型 + + Args: + model_id: 模型ID + deploy_url: 部署的URL + s_yaml_file: Serverless Devs 配置文件路径,用于检查启动命令 + """ + # 先调用模型详情接口 + model_detail_url = f"{deploy_url}/model/info" + print(f"正在获取部署后的模型服务详情: {model_detail_url}") + + try: + detail_response = requests.get( + model_detail_url, headers={"Authorization": f"Bearer {token}"} + ) + if detail_response.status_code == 200: + print(f"部署后的模型服务详情: {detail_response.text}") + else: + print(f"获取部署后的模型服务详情失败: {detail_response.status_code}") + except Exception as e: + print(f"获取部署后的模型服务详情时出错: {e}") + + # 检查是否是vLLM模型(通过配置文件中的启动命令) + is_vllm_model = False + if s_yaml_file: + try: + with open(s_yaml_file, "r", encoding="utf-8") as f: + s_config = yaml.safe_load(f) + # 检查启动命令中是否包含vllm + props = ( + s_config.get("resources", {}).get("test_func", {}).get("props", {}) + ) + custom_container_config = props.get("customContainerConfig", {}) + # 检查entrypoint数组中是否包含vllm + entrypoint = custom_container_config.get("entrypoint", []) + if isinstance(entrypoint, list): + entrypoint_str = " ".join(entrypoint) + else: + entrypoint_str = str(entrypoint) + # 检查command字段(为了兼容性) + command = custom_container_config.get("command", "") + if "vllm" in entrypoint_str or "vllm" in command: + is_vllm_model = True + print("检测到vLLM模型,将使用专用测试方法") + except Exception as e: + print(f"检查模型类型时出错: {e}") + + if is_vllm_model: + # 对于vLLM模型,使用专门的测试方法 + print(f"正在测试vLLM模型: {deploy_url}") + test_vllm_model(deploy_url) + return True + else: + # 获取模型信息 + encoded_model_id = urllib.parse.quote(model_id, safe="") + model_registry_url = os.getenv( + "MODEL_REGISTRY_URL", "model-registry-dayly.devsapp.cn" + ) + model_info_url = f"http://{model_registry_url}/api/v1/models/{encoded_model_id}" + + print(f"正在获取模型信息: {model_info_url}") + + response = requests.get( + model_info_url, + headers={"accept": "application/json"}, + ) + + if response.status_code != 200: + raise Exception( + f"获取模型信息失败: {response.status_code} - {response.text}" + ) + + model_info = response.json() + + if not model_info.get("success", False): + raise Exception( + f"获取模型信息失败: {model_info.get('error', 'Unknown error')}" + ) + + model_data = model_info.get("data", {}) + tasks = model_data.get("tasks", []) + + if not tasks: + raise Exception("模型没有定义任务类型") + + task_name = tasks[0].get("name", "") + print(f"模型任务类型: {task_name}") + + # 获取测试payload + payload_url = "https://images.devsapp.cn/modelscope/pipeline_inputs.json" + print(f"正在获取测试payload: {payload_url}") + + payload_response = requests.get(payload_url) + if payload_response.status_code != 200: + raise Exception(f"获取测试payload失败: {payload_response.status_code}") + + # 解析payload数据 + try: + payload_templates = payload_response.json() + payload = payload_templates.get(task_name, {}) + except json.JSONDecodeError: + # 如果不是JSON格式,尝试使用文本 + payload = {"input": payload_response.text} + + print(f"测试payload: {json.dumps(payload, indent=2)}") + + # 发送测试请求 + print(f"正在测试部署的模型: {deploy_url}") + + test_response = requests.post( + deploy_url, + headers={ + "Content-Type": "application/json", + "Authorization": f"Bearer {token}", + }, + data=json.dumps(payload), + ) + + print(f"测试响应状态码: {test_response.status_code}") + print(f"测试响应内容: {test_response.text}") + + if test_response.status_code == 200: + print("模型测试成功!") + return True + else: + print("模型测试失败!") + return False + + +def test_vllm_model(deploy_url: str): + """ + 测试vLLM模型 + + Args: + deploy_url: 部署的URL + """ + # 对于vLLM模型,使用专门的测试方法 + chat_url = f"{deploy_url}/v1/chat/completions" + print(f"正在测试vLLM模型: {chat_url}") + + test_data = { + "messages": [{"role": "user", "content": "Hello! 你是谁?"}], + "stream": False, + } + + try: + response = requests.post( + chat_url, + headers={ + "Content-Type": "application/json", + "Authorization": f"Bearer {token}", + }, + data=json.dumps(test_data), + timeout=300, # 5分钟超时 + ) + + print(f"vLLM测试响应状态码: {response.status_code}") + print(f"vLLM测试响应内容: {response.text}") + + if response.status_code == 200: + print("vLLM模型测试成功!") + return True + else: + print("vLLM模型测试失败!") + return False + except Exception as e: + print(f"测试vLLM模型时出错: {e}") + return False + + +def cleanup_deployment(s_yaml_file: str): + """ + 清除部署的资源 + + Args: + s_yaml_file: Serverless Devs 配置文件路径 + """ + try: + print(f"正在清除部署资源: {s_yaml_file}") + # 清除模型 + subprocess.check_call( + f"echo 123456 | sudo -S s model remove -y -t {s_yaml_file}", shell=True + ) + # 清除函数 + subprocess.check_call( + f"echo 123456 | sudo -S s remove -y -t {s_yaml_file} --skip-push", + shell=True, + ) + print("部署资源清除完成!") + except subprocess.CalledProcessError as e: + print(f"清除部署资源失败: {e}") + except Exception as e: + print(f"清除部署资源时发生错误: {e}") + + +def main(): + """ + 主函数 + """ + import argparse + + parser = argparse.ArgumentParser(description="模型部署和测试脚本") + parser.add_argument("--model-id", help="模型ID") + parser.add_argument("--region", default="cn-shanghai", help="部署区域") + parser.add_argument("--cleanup", help="执行清除操作,提供配置文件路径") + parser.add_argument( + "--auto-cleanup", action="store_true", help="部署和测试完成后自动执行清理操作" + ) + + args = parser.parse_args() + + try: + if args.cleanup: + # 只执行清除操作 + cleanup_deployment(args.cleanup) + return 0 + elif args.model_id: + # 部署和测试模型 + deploy_url, s_yaml_file = deploy_model(args.model_id, args.region) + + # 测试模型 + test_model(args.model_id, deploy_url, s_yaml_file) + + if args.auto_cleanup: + # 自动执行清理操作 + print("自动执行清理操作...") + cleanup_deployment(s_yaml_file) + print("模型部署、测试和清理完成!") + else: + print(f"模型部署和测试完成! 配置文件路径: {s_yaml_file}") + print( + "如需清除资源,请运行: python deploy_and_test_model.py --cleanup {}".format( + s_yaml_file + ) + ) + else: + print("请提供模型ID或使用 --cleanup 参数指定配置文件路径") + return 1 + + except Exception as e: + print(f"错误: {e}") + return 1 + + return 0 + + +if __name__ == "__main__": + exit(main()) diff --git a/__tests__/e2e/model/requirements.txt b/__tests__/e2e/model/requirements.txt new file mode 100644 index 00000000..7f56dad5 --- /dev/null +++ b/__tests__/e2e/model/requirements.txt @@ -0,0 +1,2 @@ +requests==2.32.3 +PyYAML==6.0.1 \ No newline at end of file diff --git a/__tests__/e2e/model/s.yaml b/__tests__/e2e/model/s.yaml new file mode 100644 index 00000000..45907e30 --- /dev/null +++ b/__tests__/e2e/model/s.yaml @@ -0,0 +1,7 @@ +access: quanxi +edition: 3.0.0 +name: hello-world-app +resources: + test_func: + component: ${env('fc_component_version', path('../../../'))} + props: {} \ No newline at end of file diff --git a/__tests__/e2e/nodejs/test-auto-code/index.js b/__tests__/e2e/nodejs/test-auto-code/index.js index a1ab09ba..61436cdf 100644 --- a/__tests__/e2e/nodejs/test-auto-code/index.js +++ b/__tests__/e2e/nodejs/test-auto-code/index.js @@ -11,7 +11,7 @@ module.exports.handler = function (event, context, callback) { const functionName = context.function.name; console.log(`functionName: ${functionName}`); const nasDir = `/mnt/${functionName}`; - const ossDir = `/mnt/oss_${functionName}`; + const ossDir = `/mnt/${functionName}`; const nasFile = `${nasDir}/test.txt`; const ossFile = `${ossDir}/test.txt`; if (fs.existsSync(nasFile)) { diff --git a/__tests__/it/code/index.py b/__tests__/it/code/index.py index 7255be58..1e96fab7 100644 --- a/__tests__/it/code/index.py +++ b/__tests__/it/code/index.py @@ -6,4 +6,4 @@ def handler(event, context): logger = logging.getLogger() logger.info(event) - return event \ No newline at end of file + return event diff --git a/__tests__/ut/commands/deploy/impl/function_test.ts b/__tests__/ut/commands/deploy/impl/function_test.ts index bc5aede6..18c71a04 100644 --- a/__tests__/ut/commands/deploy/impl/function_test.ts +++ b/__tests__/ut/commands/deploy/impl/function_test.ts @@ -755,10 +755,9 @@ describe('Service', () => { expect(service.local.ossMountConfig).toEqual({ mountPoints: [ { - mountDir: `/mnt/oss_test-function`, + mountDir: `/mnt/test-oss-bucket`, bucketName: 'test-oss-bucket', endpoint: 'http://oss-cn-hangzhou-internal.aliyuncs.com', - bucketPath: `/test-function`, readOnly: false, }, ], diff --git a/example/s-pipeline.yaml b/example/s-pipeline.yaml deleted file mode 100644 index 12aa13f4..00000000 --- a/example/s-pipeline.yaml +++ /dev/null @@ -1,58 +0,0 @@ -edition: 3.0.0 -name: ai-model-app -access: quanxi - -resources: - modelDemo: - component: fc3 - props: - region: cn-shanghai - runtime: custom-container - functionName: ${env('fc_component_function_name', 'ai-model-test-qwen-pipeline')} - description: model service from functionai test - logConfig: auto - vpcConfig: auto - nasConfig: auto - instanceConcurrency: 20 - cpu: 8 - memorySize: 65536 - diskSize: 10240 - timeout: 300 - gpuConfig: - gpuMemorySize: 49152 - gpuType: fc.gpu.ada.1 - customContainerConfig: - image: >- - serverless-registry.cn-hangzhou.cr.aliyuncs.com/functionai/modelscope:ubuntu22.04-cuda12.1.0-py311-torch2.3.1-tf2.16.1-1.26.0 - port: 9000 - entrypoint: - - sh - - '-c' - - mkdir -p /.function_model && curl -fL --retry 3 -o /.function_model/server.py https://images.devsapp.cn/modelscope/server.py && exec python3 -u /.function_model/server.py --task "${env('TASK')}" --model-id "${env('MODEL_ID', 'iic/SenseVoiceSmall')}" --model-revision "${env('MODEL_VERSION', 'master')}" --model-path "/mnt/${env('fc_component_function_name', 'ai-model-test-qwen-pipeline')}/${env('MODEL_ID', 'iic/SenseVoiceSmall')}" - triggers: # 默认,用户可能关注的是开启 authType 是 bear token - - triggerConfig: - methods: - - GET - - POST - - PUT - - DELETE - authType: anonymous - disableURLInternet: false - triggerName: httpTrigger - description: '' - qualifier: LATEST - triggerType: http - provisionConfig: - target: 1 - alwaysAllocateCPU: false - alwaysAllocateGPU: false - mode: sync - - annotations: - modelConfig: - source: modelscope - id: ${env('MODEL_ID', 'iic/SenseVoiceSmall')} - # id: Qwen/Qwen3-14B - storage: nas - role: acs:ram::${config('AccountID')}:role/aliyundevsdefaultrole - diff --git a/example/s.yaml b/example/s.yaml deleted file mode 100644 index 4906256f..00000000 --- a/example/s.yaml +++ /dev/null @@ -1,68 +0,0 @@ -edition: 3.0.0 -name: ai-model-app -access: quanxi - -resources: - modelDemo: - component: ${path('../')} - props: - region: cn-shanghai - runtime: custom-container - functionName: ${env('fc_component_function_name', 'ai-model-test-qwen')} - description: model service from functionai test - logConfig: auto - vpcConfig: auto - nasConfig: auto - instanceConcurrency: 20 - cpu: 8 - memorySize: 65536 - diskSize: 10240 - timeout: 300 - gpuConfig: - gpuMemorySize: 49152 - gpuType: fc.gpu.ada.1 - customContainerConfig: - image: >- - serverless-registry.cn-hangzhou.cr.aliyuncs.com/functionai/dms-vllm:openai_v0.10.0 - port: 9000 - entrypoint: - - vllm - - serve - - /mnt/${env('fc_component_function_name', 'ai-model-test-qwen')}/${env('MODEL_ID', 'Qwen/Qwen3-32B-AWQ')} - - --port - - "9000" - - --served-model-name - - ${env('MODEL_ID', 'Qwen/Qwen3-32B-AWQ')} - # - Qwen/Qwen3-14B - - --tensor-parallel-size - - "1" - - --trust-remote-code # 添加这个参数 - - --max-model-len - - "4096" - triggers: # 默认,用户可能关注的是开启 authType 是 bear token - - triggerConfig: - methods: - - GET - - POST - - PUT - - DELETE - authType: anonymous - disableURLInternet: false - triggerName: httpTrigger - description: '' - qualifier: LATEST - triggerType: http - provisionConfig: - target: 1 - alwaysAllocateCPU: false - alwaysAllocateGPU: false - mode: sync - - annotations: - modelConfig: - source: modelscope - id: ${env('MODEL_ID', 'Qwen/Qwen3-32B-AWQ')} - # id: Qwen/Qwen3-14B - storage: nas - role: acs:ram::${config('AccountID')}:role/aliyundevsdefaultrole - diff --git a/example/test_models_VLLM.sh b/example/test_models_VLLM.sh deleted file mode 100755 index eea0cea3..00000000 --- a/example/test_models_VLLM.sh +++ /dev/null @@ -1,125 +0,0 @@ -#!/bin/bash - -# 定义日志文件 -LOG_FILE="test_models.log" - -# 清空或创建日志文件 -> "$LOG_FILE" - -# 日志记录函数 -log() { - echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" | tee -a "$LOG_FILE" -} - -# 定义model_id列表 -MODEL_IDS=("Qwen/Qwen2-Audio-7B-Instruct" "Qwen/Qwen2.5-0.5B-Instruct" "Qwen/Qwen3-32B-AWQ" "Qwen/Qwen3-14B" "Qwen/QwQ-32B-AWQ") - -# 检查yaml文件是否存在 -YAML_FILE="s.yaml" -if [ ! -f "$YAML_FILE" ]; then - log "Error: $YAML_FILE not found!" - exit 1 -fi - -# 遍历每个model_id -for MODEL_ID in "${MODEL_IDS[@]}"; do - log "========================================" - log "Testing model: $MODEL_ID" - log "========================================" - - # 生成随机函数名 - RANDOM_STRING=$(openssl rand -hex 16) - export fc_component_function_name=ai-model-qwen-$RANDOM_STRING - export NEW_MODEL_SERVICE_CLIENT_CONNECT_TIMEOUT=10000 - export MODEL_ID=$MODEL_ID - - # 下载模型 - log "Downloading model..." - DOWNLOAD_OUTPUT=$(s model download 2>&1) - echo "$DOWNLOAD_OUTPUT" >> "$LOG_FILE" - echo "$DOWNLOAD_OUTPUT" - if echo "$DOWNLOAD_OUTPUT" | grep -q "Error"; then - log "Failed to download model: $MODEL_ID" - continue - fi - - # 部署服务 - log "Deploying..." - DEPLOY_OUTPUT=$(s deploy 2>&1) - echo "$DEPLOY_OUTPUT" >> "$LOG_FILE" - echo "$DEPLOY_OUTPUT" - - # 检查部署是否成功 - if echo "$DEPLOY_OUTPUT" | grep -q "state:.*Active"; then - log "Deployment successful for model: $MODEL_ID" - else - log "Deployment failed for model: $MODEL_ID" - # 清理资源 - REMOVE_OUTPUT=$(s model remove -y 2>&1) - echo "$REMOVE_OUTPUT" >> "$LOG_FILE" - echo "$REMOVE_OUTPUT" - REMOVE_OUTPUT=$(s remove -y 2>&1) - echo "$REMOVE_OUTPUT" >> "$LOG_FILE" - echo "$REMOVE_OUTPUT" - continue - fi - - # 提取system_url - SYSTEM_URL=$(echo "$DEPLOY_OUTPUT" | grep "system_url:" | sed 's/.*system_url: *//' | tr -d ' "[:cntrl:]') - if [ -z $SYSTEM_URL ]; then - log "Failed to extract system_url for model: $MODEL_ID" - # 清理资源 - REMOVE_OUTPUT=$(s model remove -y 2>&1) - echo "$REMOVE_OUTPUT" >> "$LOG_FILE" - echo "$REMOVE_OUTPUT" - REMOVE_OUTPUT=$(s remove -y 2>&1) - echo "$REMOVE_OUTPUT" >> "$LOG_FILE" - echo "$REMOVE_OUTPUT" - continue - fi - log "Extracted system_url: $SYSTEM_URL" - - # 发送测试请求 - log "Sending test request..." - CURL_OUTPUT=$(curl --request POST \ - --url "$SYSTEM_URL/v1/chat/completions" \ - -H "Content-Type: application/json" \ - --data '{ - "model": "$MODEL_ID", - "messages": [ - { - "role": "user", - "content": "Hello! 你是谁?" - } - ], - "stream": false - }' 2>&1) - - echo "$CURL_OUTPUT" >> "$LOG_FILE" - echo "$CURL_OUTPUT" - - # 检查curl请求是否成功 - if echo "$CURL_OUTPUT" | grep -q '"object":"chat.completion"'; then - log "Model test successful for: $MODEL_ID" - # 提取并显示模型回复内容 - RESPONSE_CONTENT=$(echo "$CURL_OUTPUT" | sed -n 's/.*"content":"\([^"]*\)".*/\1/p' | sed 's/\\n/\n/g' | sed 's/\\t/\t/g') - log "Model response: $RESPONSE_CONTENT" - else - log "Model test failed for: $MODEL_ID" - fi - - # 清理资源 - log "Removing resources..." - REMOVE_OUTPUT=$(s model remove -y 2>&1) - echo "$REMOVE_OUTPUT" >> "$LOG_FILE" - echo "$REMOVE_OUTPUT" - REMOVE_OUTPUT=$(s remove -y 2>&1) - echo "$REMOVE_OUTPUT" >> "$LOG_FILE" - echo "$REMOVE_OUTPUT" - - log "" - log "Finished testing model: $MODEL_ID" - log "" -done - -log "All models tested." \ No newline at end of file diff --git a/example/test_models_pipeline.sh b/example/test_models_pipeline.sh deleted file mode 100755 index 2a742563..00000000 --- a/example/test_models_pipeline.sh +++ /dev/null @@ -1,120 +0,0 @@ -#!/bin/bash - -# 定义日志文件 -LOG_FILE="test_models.log" - -# 清空或创建日志文件 -> "$LOG_FILE" - -# 日志记录函数 -log() { - echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" | tee -a "$LOG_FILE" -} - -# 定义模型列表 (每个元素包含model_version, model_id, task) -MODEL_LIST=( - '{"model_version": "v2.4.0", "model_id": "iic/cv_convnextTiny_ocr-recognition-general_damo", "task": "ocr-recognition", "input":{"image":"http://modelscope.oss-cn-beijing.aliyuncs.com/demo/images/image_ocr_recognition.jpg"}}' - '{"model_version": "master", "model_id": "iic/SenseVoiceSmall", "task": "auto-speech-recognition", "input": "https://isv-data.oss-cn-hangzhou.aliyuncs.com/ics/MaaS/ASR/test_audio/asr_example_zh.wav"}' -) - -# 检查yaml文件是否存在 -YAML_FILE="s.yaml" -if [ ! -f "$YAML_FILE" ]; then - log "Error: $YAML_FILE not found!" - exit 1 -fi - -# 遍历每个model_id -for MODEL_INFO in "${MODEL_LIST[@]}"; do - log "========================================" - log "Testing model: $MODEL_INFO" - log "========================================" - # 从JSON对象中提取字段 (使用 jq) - export MODEL_VERSION=$(echo "$MODEL_INFO" | grep -o '"model_version": *"[^"]*' | awk -F'"' '{print $4}') - export MODEL_ID=$(echo "$MODEL_INFO" | grep -o '"model_id": *"[^"]*' | awk -F'"' '{print $4}') - export TASK=$(echo "$MODEL_INFO" | grep -o '"task": *"[^"]*' | awk -F'"' '{print $4}') - INPUT=$(echo "$MODEL_INFO" | grep -o '"input": *"[^"]*' | awk -F'"' '{print $4}') - - # 生成随机函数名 - RANDOM_STRING=$(openssl rand -hex 10) - export fc_component_function_name=ai-model-qwen-$RANDOM_STRING - export NEW_MODEL_SERVICE_CLIENT_CONNECT_TIMEOUT=10000 - - # 下载模型 - log "Downloading model..." - DOWNLOAD_OUTPUT=$(s model download -t s-pipeline.yaml 2>&1) - echo "$DOWNLOAD_OUTPUT" >> "$LOG_FILE" - echo "$DOWNLOAD_OUTPUT" - if echo "$DOWNLOAD_OUTPUT" | grep -q "Error"; then - log "Failed to download model: $MODEL_ID" - continue - fi - - # 部署服务 - log "Deploying..." - DEPLOY_OUTPUT=$(s deploy -t s-pipeline.yaml -y 2>&1) - echo "$DEPLOY_OUTPUT" >> "$LOG_FILE" - echo "$DEPLOY_OUTPUT" - - # 检查部署是否成功 - if echo "$DEPLOY_OUTPUT" | grep -q "state:.*Active"; then - log "Deployment successful for model: $MODEL_ID" - else - log "Deployment failed for model: $MODEL_ID" - # 清理资源 - REMOVE_OUTPUT=$(s model remove -t s-pipeline.yaml -y 2>&1) - echo "$REMOVE_OUTPUT" >> "$LOG_FILE" - echo "$REMOVE_OUTPUT" - REMOVE_OUTPUT=$(s remove -t s-pipeline.yaml -y 2>&1) - echo "$REMOVE_OUTPUT" >> "$LOG_FILE" - echo "$REMOVE_OUTPUT" - continue - fi - - # 提取system_url - SYSTEM_URL=$(echo "$DEPLOY_OUTPUT" | grep "system_url:" | sed 's/.*system_url: *//' | tr -d ' "[:cntrl:]') - if [ -z $SYSTEM_URL ]; then - log "Failed to extract system_url for model: $MODEL_ID" - # 清理资源 - REMOVE_OUTPUT=$(s model remove -t s-pipeline.yaml -y 2>&1) - echo "$REMOVE_OUTPUT" >> "$LOG_FILE" - echo "$REMOVE_OUTPUT" - REMOVE_OUTPUT=$(s remove -t s-pipeline.yaml -y 2>&1) - echo "$REMOVE_OUTPUT" >> "$LOG_FILE" - echo "$REMOVE_OUTPUT" - continue - fi - log "Extracted system_url: $SYSTEM_URL" - - # 发送测试请求 - log "Sending test request..." - CURL_OUTPUT=$(curl -v -d '{"input":$INPUT}' $SYSTEM_URL 2>&1) - - echo "$CURL_OUTPUT" >> "$LOG_FILE" - echo "$CURL_OUTPUT" - - # 检查curl请求是否成功 - if echo "$CURL_OUTPUT" | grep -q '"object":"chat.completion"'; then - log "Model test successful for: $MODEL_ID" - # 提取并显示模型回复内容 - RESPONSE_CONTENT=$(echo "$CURL_OUTPUT" | sed -n 's/.*"text":"\([^"]*\)".*/\1/p' | sed 's/\\n/\n/g' | sed 's/\\t/\t/g') - log "Model response: $RESPONSE_CONTENT" - else - log "Model test failed for: $MODEL_ID" - fi - - # 清理资源 - log "Removing resources..." - REMOVE_OUTPUT=$(s model remove -t s-pipeline.yaml -y 2>&1) - echo "$REMOVE_OUTPUT" >> "$LOG_FILE" - echo "$REMOVE_OUTPUT" - REMOVE_OUTPUT=$(s remove -t s-pipeline.yaml -y 2>&1) - echo "$REMOVE_OUTPUT" >> "$LOG_FILE" - echo "$REMOVE_OUTPUT" - - log "" - log "Finished testing model: $MODEL_ID" - log "" -done - -log "All models tested." \ No newline at end of file diff --git a/src/subCommands/deploy/impl/function.ts b/src/subCommands/deploy/impl/function.ts index cac1fcac..845d6976 100644 --- a/src/subCommands/deploy/impl/function.ts +++ b/src/subCommands/deploy/impl/function.ts @@ -1,4 +1,4 @@ -import _, { isEmpty } from 'lodash'; +import _ from 'lodash'; import { diffConvertYaml } from '@serverless-devs/diff'; import inquirer from 'inquirer'; import fs from 'fs'; @@ -335,7 +335,7 @@ export default class Service extends Base { * 生成 auto 资源,非 FC 资源,主要指 vpc、nas、log、role(oss mount 挂载点才有) */ private async _deployAuto() { - const { region, supplement, annotations } = this.inputs.props; + const { region } = this.inputs.props; const { credential } = this.inputs; const { functionName } = this.local; @@ -378,20 +378,18 @@ logConfig: yellow(`Created oss resource succeeded, please replace ossMountConfig: auto in yaml with: ossMountConfig: mountPoints: - - mountDir: /mnt/oss_${functionName} + - mountDir: /mnt/${ossBucket} bucketName: ${ossBucket} endpoint: http://oss-${region}-internal.aliyuncs.com - bucketPath: /${functionName} readOnly: false\n`), ); this.createResource.oss = { ossBucket }; _.set(this.local, 'ossMountConfig', { mountPoints: [ { - mountDir: `/mnt/oss_${functionName}`, + mountDir: `/mnt/${ossBucket}`, bucketName: ossBucket, endpoint: `http://oss-${region}-internal.aliyuncs.com`, - bucketPath: `/${functionName}`, readOnly: false, }, ], @@ -438,10 +436,7 @@ vpcConfig: _.set(this.local, 'vpcConfig', vpcConfig); } if (nasAuto) { - const modelConfig = supplement?.modelConfig || annotations?.modelConfig; - let serverAddr = `${mountTargetDomain}:/${functionName}${ - isEmpty(modelConfig) ? '' : `/${modelConfig.id}` - }`; + let serverAddr = `${mountTargetDomain}:/${functionName}`; if (serverAddr.length > 128) { serverAddr = serverAddr.substring(0, 128); } diff --git a/src/subCommands/model/index.ts b/src/subCommands/model/index.ts index 434e2d42..56bc9b11 100644 --- a/src/subCommands/model/index.ts +++ b/src/subCommands/model/index.ts @@ -104,7 +104,7 @@ vpcConfig: logger.info('[nasAuto] vpcAuto finished.'); } if (nasAuto) { - let serverAddr = `${mountTargetDomain}:/${functionName}/${modelConfig.id}`; + let serverAddr = `${mountTargetDomain}:/${functionName}`; if (serverAddr.length > 128) { serverAddr = serverAddr.substring(0, 128); }