fix llm
This commit is contained in:
Binary file not shown.
Binary file not shown.
@@ -5,7 +5,6 @@ import json
|
||||
import logging
|
||||
import re
|
||||
from typing import Optional, Dict, Any, List
|
||||
|
||||
import requests
|
||||
from pydantic import BaseModel, Field
|
||||
from pydantic.json_schema import models_json_schema
|
||||
@@ -59,7 +58,7 @@ class LLMService:
|
||||
self,
|
||||
messages: List[Dict[str, str]],
|
||||
max_tokens: int = 1024,
|
||||
temperature: float = 0.7,
|
||||
temperature: float = 0.1,
|
||||
# TODO: Consider adding a parameter like response_format_type: Optional[str] = None
|
||||
# if the LLM API supports forcing JSON output (e.g., { "type": "json_object" })
|
||||
) -> Optional[str]:
|
||||
@@ -114,7 +113,7 @@ class LLMService:
|
||||
pydantic_model_class: type[BaseModel],
|
||||
prompt_instruction: Optional[str] = None,
|
||||
max_tokens: int = 1024,
|
||||
temperature: float = 0.7
|
||||
temperature: float = 0.1
|
||||
) -> Optional[Dict[str, Any]]:
|
||||
"""
|
||||
根据给定的Pydantic模型类生成JSON Schema,并调用LLM生成符合该Schema的参数字典。
|
||||
@@ -178,6 +177,65 @@ class LLMService:
|
||||
|
||||
return None
|
||||
|
||||
def generate_data_from_schema(
|
||||
self,
|
||||
schema_dict: dict,
|
||||
prompt_instruction: Optional[str] = None,
|
||||
max_tokens: int = 1024,
|
||||
temperature: float = 0.1
|
||||
) -> Optional[Dict[str, Any]]:
|
||||
"""
|
||||
根据给定的JSON Schema字典,调用LLM生成符合该Schema的数据对象。
|
||||
"""
|
||||
try:
|
||||
schema_str = json.dumps(schema_dict, indent=2, ensure_ascii=False)
|
||||
logger.debug(f"LLMService.generate_data_from_schema: 使用的JSON Schema:\n{schema_str}")
|
||||
|
||||
system_prompt = (
|
||||
"你是一个API测试数据生成助手。你的任务是根据用户提供的JSON Schema和额外指令,"
|
||||
"生成一个符合该Schema的JSON对象。请确保你的输出严格是一个JSON对象,"
|
||||
"不包含任何额外的解释、注释或Markdown标记。"
|
||||
)
|
||||
user_prompt_content = f"请为以下JSON Schema生成一个有效的JSON对象实例:\n\n```json\n{schema_str}\n```\n"
|
||||
if prompt_instruction:
|
||||
user_prompt_content += f"\n请遵循以下额外指令:\n{prompt_instruction}"
|
||||
messages = [
|
||||
{"role": "system", "content": system_prompt},
|
||||
{"role": "user", "content": user_prompt_content}
|
||||
]
|
||||
|
||||
assistant_response_content = self._execute_chat_completion_request(
|
||||
messages=messages,
|
||||
max_tokens=max_tokens,
|
||||
temperature=temperature
|
||||
)
|
||||
|
||||
if assistant_response_content:
|
||||
# 尝试从返回内容中提取JSON部分
|
||||
json_match = re.search(r'```json\n(.*?)\n```', assistant_response_content, re.DOTALL)
|
||||
if json_match:
|
||||
json_str = json_match.group(1)
|
||||
else:
|
||||
first_brace = assistant_response_content.find('{')
|
||||
last_brace = assistant_response_content.rfind('}')
|
||||
if first_brace != -1 and last_brace != -1 and last_brace > first_brace:
|
||||
json_str = assistant_response_content[first_brace : last_brace+1]
|
||||
else:
|
||||
json_str = assistant_response_content
|
||||
|
||||
try:
|
||||
generated_data = json.loads(json_str)
|
||||
logger.info("成功从LLM生成并解析了数据。")
|
||||
return generated_data
|
||||
except json.JSONDecodeError as e_json:
|
||||
logger.error(f"无法将LLM响应解析为JSON: {e_json}\n原始响应片段: '{json_str[:500]}'")
|
||||
else:
|
||||
logger.warning("从LLM获取的响应内容为空或请求失败。")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"执行LLM数据生成时发生未知错误: {e}", exc_info=True)
|
||||
return None
|
||||
|
||||
# --- 示例用法 (用于模块内测试) ---
|
||||
if __name__ == '__main__':
|
||||
logging.basicConfig(level=logging.DEBUG,
|
||||
|
||||
@@ -1029,85 +1029,117 @@ class APITestOrchestrator:
|
||||
|
||||
# 1. 处理路径参数
|
||||
path_param_specs = [p for p in parameters if p.get('in') == 'path']
|
||||
for param_spec in path_param_specs:
|
||||
name = param_spec.get('name')
|
||||
if not name: continue
|
||||
|
||||
if path_param_specs:
|
||||
should_use_llm = self._should_use_llm_for_param_type("path_params", test_case_instance)
|
||||
if should_use_llm and self.llm_service:
|
||||
self.logger.info(f"Attempting LLM generation for path parameter '{name}' in '{operation_id}'")
|
||||
# generated_value = self.llm_service.generate_data_for_parameter(param_spec, endpoint_spec, "path")
|
||||
# initial_path_params[name] = generated_value if generated_value is not None else f"llm_placeholder_for_{name}"
|
||||
initial_path_params[name] = f"llm_path_{name}" # Placeholder
|
||||
else:
|
||||
if 'example' in param_spec:
|
||||
initial_path_params[name] = param_spec['example']
|
||||
elif param_spec.get('schema') and 'example' in param_spec['schema']:
|
||||
initial_path_params[name] = param_spec['schema']['example'] # OpenAPI 3.0 `parameter.schema.example`
|
||||
elif 'default' in param_spec.get('schema', {}):
|
||||
initial_path_params[name] = param_spec['schema']['default']
|
||||
elif 'default' in param_spec: # OpenAPI 2.0 `parameter.default`
|
||||
initial_path_params[name] = param_spec['default']
|
||||
self.logger.info(f"Attempting LLM generation for path parameters in '{operation_id}'")
|
||||
path_schema, path_model_name = self._build_object_schema_for_params(path_param_specs, f"{operation_id}_PathParams")
|
||||
if path_schema:
|
||||
llm_path_params = self.llm_service.generate_data_from_schema(
|
||||
path_schema,
|
||||
prompt_instruction=None,
|
||||
max_tokens=256,
|
||||
temperature=0.1
|
||||
)
|
||||
if llm_path_params:
|
||||
initial_path_params = llm_path_params
|
||||
else:
|
||||
self.logger.warning(f"LLM failed to generate path params for '{operation_id}', fallback to default.")
|
||||
else:
|
||||
schema = param_spec.get('schema', {})
|
||||
param_type = schema.get('type', 'string')
|
||||
if param_type == 'integer': initial_path_params[name] = 123
|
||||
elif param_type == 'number': initial_path_params[name] = 1.23
|
||||
elif param_type == 'boolean': initial_path_params[name] = True
|
||||
elif param_type == 'string' and schema.get('format') == 'uuid': initial_path_params[name] = str(UUID(int=0)) # Example UUID
|
||||
elif param_type == 'string' and schema.get('format') == 'date': initial_path_params[name] = dt.date.today().isoformat()
|
||||
elif param_type == 'string' and schema.get('format') == 'date-time': initial_path_params[name] = dt.datetime.now().isoformat()
|
||||
else: initial_path_params[name] = f"param_{name}"
|
||||
self.logger.debug(f"Initial path param for '{operation_id}': {name} = {initial_path_params.get(name)}")
|
||||
|
||||
self.logger.warning(f"Failed to build schema for path params in '{operation_id}', fallback to default.")
|
||||
if not initial_path_params: # fallback
|
||||
for param_spec in path_param_specs:
|
||||
name = param_spec.get('name')
|
||||
if not name: continue
|
||||
if 'example' in param_spec:
|
||||
initial_path_params[name] = param_spec['example']
|
||||
elif param_spec.get('schema') and 'example' in param_spec['schema']:
|
||||
initial_path_params[name] = param_spec['schema']['example'] # OpenAPI 3.0 `parameter.schema.example`
|
||||
elif 'default' in param_spec.get('schema', {}):
|
||||
initial_path_params[name] = param_spec['schema']['default']
|
||||
elif 'default' in param_spec: # OpenAPI 2.0 `parameter.default`
|
||||
initial_path_params[name] = param_spec['default']
|
||||
else:
|
||||
schema = param_spec.get('schema', {})
|
||||
param_type = schema.get('type', 'string')
|
||||
if param_type == 'integer': initial_path_params[name] = 123
|
||||
elif param_type == 'number': initial_path_params[name] = 1.23
|
||||
elif param_type == 'boolean': initial_path_params[name] = True
|
||||
elif param_type == 'string' and schema.get('format') == 'uuid': initial_path_params[name] = str(UUID(int=0)) # Example UUID
|
||||
elif param_type == 'string' and schema.get('format') == 'date': initial_path_params[name] = dt.date.today().isoformat()
|
||||
elif param_type == 'string' and schema.get('format') == 'date-time': initial_path_params[name] = dt.datetime.now().isoformat()
|
||||
else: initial_path_params[name] = f"param_{name}"
|
||||
self.logger.debug(f"Initial path param for '{operation_id}': {initial_path_params}")
|
||||
|
||||
# 2. 处理查询参数
|
||||
query_param_specs = [p for p in parameters if p.get('in') == 'query']
|
||||
for param_spec in query_param_specs:
|
||||
name = param_spec.get('name')
|
||||
if not name: continue
|
||||
if query_param_specs:
|
||||
should_use_llm = self._should_use_llm_for_param_type("query_params", test_case_instance)
|
||||
if should_use_llm and self.llm_service:
|
||||
self.logger.info(f"Attempting LLM generation for query parameter '{name}' in '{operation_id}'")
|
||||
initial_query_params[name] = f"llm_query_{name}" # Placeholder
|
||||
else:
|
||||
if 'example' in param_spec:
|
||||
initial_query_params[name] = param_spec['example']
|
||||
elif param_spec.get('schema') and 'example' in param_spec['schema']:
|
||||
initial_query_params[name] = param_spec['schema']['example']
|
||||
elif 'default' in param_spec.get('schema', {}):
|
||||
initial_query_params[name] = param_spec['schema']['default']
|
||||
elif 'default' in param_spec:
|
||||
initial_query_params[name] = param_spec['default']
|
||||
else:
|
||||
initial_query_params[name] = f"query_val_{name}" # Simplified default
|
||||
self.logger.debug(f"Initial query param for '{operation_id}': {name} = {initial_query_params.get(name)}")
|
||||
|
||||
self.logger.info(f"Attempting LLM generation for query parameters in '{operation_id}'")
|
||||
query_schema, query_model_name = self._build_object_schema_for_params(query_param_specs, f"{operation_id}_QueryParams")
|
||||
if query_schema:
|
||||
llm_query_params = self.llm_service.generate_data_from_schema(
|
||||
query_schema,
|
||||
prompt_instruction=None,
|
||||
max_tokens=512,
|
||||
temperature=0.1
|
||||
)
|
||||
if llm_query_params:
|
||||
initial_query_params = llm_query_params
|
||||
else:
|
||||
self.logger.warning(f"LLM failed to generate query params for '{operation_id}', fallback to default.")
|
||||
if not initial_query_params: # fallback
|
||||
for param_spec in query_param_specs:
|
||||
name = param_spec.get('name')
|
||||
if not name: continue
|
||||
if 'example' in param_spec:
|
||||
initial_query_params[name] = param_spec['example']
|
||||
elif param_spec.get('schema') and 'example' in param_spec['schema']:
|
||||
initial_query_params[name] = param_spec['schema']['example']
|
||||
elif 'default' in param_spec.get('schema', {}):
|
||||
initial_query_params[name] = param_spec['schema']['default']
|
||||
elif 'default' in param_spec:
|
||||
initial_query_params[name] = param_spec['default']
|
||||
else:
|
||||
initial_query_params[name] = f"query_val_{name}"
|
||||
self.logger.debug(f"Initial query param for '{operation_id}': {initial_query_params}")
|
||||
|
||||
# 3. 处理请求头参数 (包括规范定义的和标准的 Content-Type/Accept)
|
||||
header_param_specs = [p for p in parameters if p.get('in') == 'header']
|
||||
for param_spec in header_param_specs:
|
||||
name = param_spec.get('name')
|
||||
if not name: continue
|
||||
# 标准头 Content-Type 和 Accept 会在后面专门处理
|
||||
if name.lower() in ['content-type', 'accept', 'authorization']:
|
||||
self.logger.debug(f"Skipping standard header '{name}' in parameter processing for '{operation_id}'. It will be handled separately.")
|
||||
continue
|
||||
|
||||
custom_header_param_specs = [p for p in header_param_specs if p.get('name', '').lower() not in ['content-type', 'accept', 'authorization']]
|
||||
if custom_header_param_specs:
|
||||
should_use_llm = self._should_use_llm_for_param_type("headers", test_case_instance)
|
||||
if should_use_llm and self.llm_service:
|
||||
self.logger.info(f"Attempting LLM generation for header '{name}' in '{operation_id}'")
|
||||
initial_headers[name] = f"llm_header_{name}" # Placeholder
|
||||
else:
|
||||
if 'example' in param_spec:
|
||||
initial_headers[name] = str(param_spec['example'])
|
||||
elif param_spec.get('schema') and 'example' in param_spec['schema']:
|
||||
initial_headers[name] = str(param_spec['schema']['example'])
|
||||
elif 'default' in param_spec.get('schema', {}):
|
||||
initial_headers[name] = str(param_spec['schema']['default'])
|
||||
elif 'default' in param_spec:
|
||||
initial_headers[name] = str(param_spec['default'])
|
||||
else:
|
||||
initial_headers[name] = f"header_val_{name}"
|
||||
self.logger.debug(f"Initial custom header param for '{operation_id}': {name} = {initial_headers.get(name)}")
|
||||
self.logger.info(f"Attempting LLM generation for header parameters in '{operation_id}'")
|
||||
header_schema, header_model_name = self._build_object_schema_for_params(custom_header_param_specs, f"{operation_id}_HeaderParams")
|
||||
if header_schema:
|
||||
llm_header_params = self.llm_service.generate_data_from_schema(
|
||||
header_schema,
|
||||
prompt_instruction=None,
|
||||
max_tokens=256,
|
||||
temperature=0.1
|
||||
)
|
||||
if llm_header_params:
|
||||
for k, v in llm_header_params.items():
|
||||
initial_headers[k] = str(v)
|
||||
else:
|
||||
self.logger.warning(f"LLM failed to generate header params for '{operation_id}', fallback to default.")
|
||||
if not any(k for k in initial_headers if k.lower() not in ['content-type', 'accept', 'authorization']): # fallback
|
||||
for param_spec in custom_header_param_specs:
|
||||
name = param_spec.get('name')
|
||||
if not name: continue
|
||||
if 'example' in param_spec:
|
||||
initial_headers[name] = str(param_spec['example'])
|
||||
elif param_spec.get('schema') and 'example' in param_spec['schema']:
|
||||
initial_headers[name] = str(param_spec['schema']['example'])
|
||||
elif 'default' in param_spec.get('schema', {}):
|
||||
initial_headers[name] = str(param_spec['schema']['default'])
|
||||
elif 'default' in param_spec:
|
||||
initial_headers[name] = str(param_spec['default'])
|
||||
else:
|
||||
initial_headers[name] = f"header_val_{name}"
|
||||
self.logger.debug(f"Initial custom header param for '{operation_id}': {initial_headers}")
|
||||
|
||||
# 3.1 设置 Content-Type
|
||||
# 优先从 requestBody.content 获取 (OpenAPI 3.x)
|
||||
@@ -1163,7 +1195,12 @@ class APITestOrchestrator:
|
||||
should_use_llm_for_body = self._should_use_llm_for_param_type("body", test_case_instance)
|
||||
if should_use_llm_for_body and self.llm_service:
|
||||
self.logger.info(f"Attempting LLM generation for request body of '{operation_id}' with schema...")
|
||||
initial_body = self.llm_service.generate_data_from_schema(request_body_schema, endpoint_spec, "requestBody")
|
||||
initial_body = self.llm_service.generate_data_from_schema(
|
||||
request_body_schema,
|
||||
prompt_instruction=None, # 如有自定义指令可替换
|
||||
max_tokens=1024,
|
||||
temperature=0.1
|
||||
)
|
||||
if initial_body is None:
|
||||
self.logger.warning(f"LLM failed to generate request body for '{operation_id}'. Falling back to default schema generator.")
|
||||
initial_body = self._generate_data_from_schema(request_body_schema, context_name=f"{operation_id}_body", operation_id=operation_id)
|
||||
|
||||
Reference in New Issue
Block a user