fix: enrich image failure audit metadata

This commit is contained in:
kdletters
2026-05-28 14:50:13 +08:00
parent c8b36cf799
commit 771b0411a3
18 changed files with 234 additions and 20 deletions

View File

@@ -1204,7 +1204,10 @@ async fn generate_and_persist_bark_battle_image_asset(
prompt: &str,
size: &str,
) -> Result<BarkBattleGeneratedImageAsset, AppError> {
let settings = require_openai_image_settings(state)?;
let settings = require_openai_image_settings(state)?.with_external_api_audit_context(
Some(owner_user_id.to_string()),
Some(draft_id.unwrap_or(asset_id).to_string()),
);
let http_client = build_openai_image_http_client(&settings)?;
let generated = create_openai_image_generation(
&http_client,

View File

@@ -94,7 +94,9 @@ pub async fn generate_character_visual(
.map_err(|error| character_visual_error_response(&request_context, error))?;
let result = async {
let settings = require_openai_image_settings(&state)?;
let settings = require_openai_image_settings(&state)?
.with_external_api_audit_context(Some(owner_user_id.clone()), Some(character_id.clone()))
.with_external_api_audit_request_id(Some(request_context.request_id().to_string()));
let http_client = build_openai_image_http_client(&settings)?;
state
@@ -318,7 +320,10 @@ pub(crate) async fn generate_character_primary_visual_for_profile(
&model,
&prompt,
)?;
let settings = require_openai_image_settings(state)?;
let settings = require_openai_image_settings(state)?.with_external_api_audit_context(
Some(owner_user_id.to_string()),
Some(character_id.clone()),
);
let http_client = build_openai_image_http_client(&settings)?;
state
.ai_task_service()

View File

@@ -553,7 +553,12 @@ pub async fn generate_custom_world_scene_image(
"scene_image",
asset_id.as_str(),
async {
let settings = require_openai_image_settings(&state)?;
let settings = require_openai_image_settings(&state)?
.with_external_api_audit_context(
Some(owner_user_id.to_string()),
normalized.profile_id.clone(),
)
.with_external_api_audit_request_id(Some(request_context.request_id().to_string()));
let http_client = build_openai_image_http_client(&settings)?;
let reference_image =
if let Some(reference_image_src) = normalized.reference_image_src.as_deref() {
@@ -675,7 +680,10 @@ pub(crate) async fn generate_custom_world_scene_image_for_profile(
}),
};
let normalized = normalize_scene_image_request(payload)?;
let settings = require_openai_image_settings(state)?;
let settings = require_openai_image_settings(state)?.with_external_api_audit_context(
Some(owner_user_id.to_string()),
normalized.profile_id.clone(),
);
let http_client = build_openai_image_http_client(&settings)?;
let generated = create_openai_image_generation(
&http_client,
@@ -1011,7 +1019,12 @@ pub async fn generate_custom_world_opening_cg(
opening_cg_id.as_str(),
OPENING_CG_POINTS_COST,
async {
let image_settings = require_openai_image_settings(&state)?;
let image_settings = require_openai_image_settings(&state)?
.with_external_api_audit_context(
Some(owner_user_id.clone()),
normalized.profile_id.clone(),
)
.with_external_api_audit_request_id(Some(request_context.request_id().to_string()));
let image_http_client = build_openai_image_http_client(&image_settings)?;
let video_settings = require_ark_video_settings(&state)?;
let video_http_client = build_upstream_http_client(video_settings.request_timeout_ms)?;

View File

@@ -8,9 +8,13 @@ pub(super) async fn generate_opening_cg_storyboard(
normalized: &NormalizedOpeningCgRequest,
reference_images: &[String],
) -> Result<GeneratedOpeningCgStoryboard, AppError> {
let audit_settings = settings.clone().with_external_api_audit_context(
Some(owner_user_id.to_string()),
normalized.profile_id.clone(),
);
let generated = create_openai_image_generation(
http_client,
settings,
&audit_settings,
normalized.storyboard_prompt.as_str(),
None,
OPENING_CG_STORYBOARD_IMAGE_SIZE,

View File

@@ -1050,6 +1050,8 @@ mod tests {
api_key: "secret".to_string(),
request_timeout_ms: 180_000,
external_api_audit_state: None,
external_api_audit_user_id: None,
external_api_audit_profile_id: None,
});
assert_eq!(

View File

@@ -27,6 +27,9 @@ pub(crate) struct ExternalApiFailureDraft {
pub(crate) prompt_chars: Option<usize>,
pub(crate) reference_image_count: Option<usize>,
pub(crate) image_model: Option<&'static str>,
pub(crate) user_id: Option<String>,
pub(crate) profile_id: Option<String>,
pub(crate) request_id: Option<String>,
}
impl ExternalApiFailureDraft {
@@ -53,6 +56,9 @@ impl ExternalApiFailureDraft {
prompt_chars: None,
reference_image_count: None,
image_model: None,
user_id: None,
profile_id: None,
request_id: None,
}
}
@@ -108,6 +114,21 @@ impl ExternalApiFailureDraft {
self.image_model = image_model;
self
}
pub(crate) fn with_user_id(mut self, user_id: Option<String>) -> Self {
self.user_id = user_id;
self
}
pub(crate) fn with_profile_id(mut self, profile_id: Option<String>) -> Self {
self.profile_id = profile_id;
self
}
pub(crate) fn with_request_id(mut self, request_id: Option<String>) -> Self {
self.request_id = request_id;
self
}
}
pub(crate) fn build_external_api_failure_draft_from_platform_image_audit(
@@ -130,6 +151,9 @@ pub(crate) fn build_external_api_failure_draft_from_platform_image_audit(
.with_prompt_chars(audit.prompt_chars)
.with_reference_image_count(audit.reference_image_count)
.with_image_model(audit.image_model)
.with_user_id(None)
.with_profile_id(None)
.with_request_id(None)
}
/// 中文注释下载图片、OSS 读写等非标准 HTTP 状态统一显式归类,避免 OTLP 低基数 label 误落到 `transport`。
@@ -203,6 +227,9 @@ pub(crate) fn build_external_api_failure_tracking_draft(
);
draft.scope_kind = RuntimeTrackingScopeKind::Module;
draft.scope_id = failure.provider.to_string();
draft.user_id = failure.user_id.clone();
draft.owner_user_id = failure.user_id.clone();
draft.profile_id = failure.profile_id.clone();
draft.metadata = build_external_api_failure_metadata(failure);
draft
}
@@ -233,6 +260,15 @@ fn build_external_api_failure_metadata(failure: &ExternalApiFailureDraft) -> Val
if let Some(image_model) = failure.image_model {
metadata["imageModel"] = json!(image_model);
}
if let Some(user_id) = failure.user_id.as_deref().map(str::trim).filter(|value| !value.is_empty()) {
metadata["userId"] = json!(truncate_field(user_id, 1_000));
}
if let Some(profile_id) = failure.profile_id.as_deref().map(str::trim).filter(|value| !value.is_empty()) {
metadata["profileId"] = json!(truncate_field(profile_id, 1_000));
}
if let Some(request_id) = failure.request_id.as_deref().map(str::trim).filter(|value| !value.is_empty()) {
metadata["requestId"] = json!(truncate_field(request_id, 1_000));
}
if let Some(source) = failure
.error_source
.as_deref()

View File

@@ -412,7 +412,16 @@ async fn maybe_generate_jump_hop_assets(
.unwrap_or_else(|| build_prefixed_uuid_id("jump-hop-profile-"));
payload.profile_id = Some(profile_id.clone());
let settings = require_openai_image_settings(state).map_err(|error| {
let settings = require_openai_image_settings(state)
.map(|settings| {
settings
.with_external_api_audit_context(
Some(owner_user_id.to_string()),
Some(profile_id.clone()),
)
.with_external_api_audit_request_id(Some(request_context.request_id().to_string()))
})
.map_err(|error| {
jump_hop_error_response(request_context, JUMP_HOP_CREATION_PROVIDER, error)
})?;
let http_client = build_openai_image_http_client(&settings).map_err(|error| {

View File

@@ -753,7 +753,10 @@ async fn generate_match3d_material_sheet_from_level_scene(
config: &Match3DConfigJson,
background_asset: Option<&Match3DGeneratedBackgroundAsset>,
) -> Result<Match3DMaterialSheet, AppError> {
let settings = require_openai_image_settings(state)?;
let settings = require_openai_image_settings(state)?.with_external_api_audit_context(
Some(owner_user_id.to_string()),
Some(profile_id.to_string()),
);
let http_client = build_openai_image_http_client(&settings)?;
let prompt = build_match3d_item_spritesheet_prompt();
let reference = load_match3d_level_scene_reference_image(state, background_asset).await?;

View File

@@ -301,7 +301,10 @@ pub(super) async fn generate_match3d_cover_image_asset(
reference_image_srcs: Vec<String>,
) -> Result<Match3DAssetUpload, AppError> {
require_match3d_oss_client(state)?;
let settings = require_openai_image_settings(state)?;
let settings = require_openai_image_settings(state)?.with_external_api_audit_context(
Some(owner_user_id.to_string()),
Some(profile_id.to_string()),
);
let http_client = build_openai_image_http_client(&settings)?;
let cover_prompt = build_match3d_cover_generation_prompt(config, prompt);
let generated = if let Some(uploaded_image) = resolve_match3d_reference_image_for_edit(
@@ -448,7 +451,10 @@ pub(super) async fn generate_match3d_level_asset_bundle(
prompt: &str,
) -> Result<Match3DGeneratedBackgroundAsset, AppError> {
require_match3d_oss_client(state)?;
let settings = require_openai_image_settings(state)?;
let settings = require_openai_image_settings(state)?.with_external_api_audit_context(
Some(owner_user_id.to_string()),
Some(profile_id.to_string()),
);
let http_client = build_openai_image_http_client(&settings)?;
let level_scene_prompt = build_match3d_level_scene_generation_prompt(config);
@@ -590,7 +596,10 @@ pub(super) async fn generate_match3d_container_image(
prompt: &str,
) -> Result<Match3DGeneratedBackgroundAsset, AppError> {
require_match3d_oss_client(state)?;
let settings = require_openai_image_settings(state)?;
let settings = require_openai_image_settings(state)?.with_external_api_audit_context(
Some(owner_user_id.to_string()),
Some(profile_id.to_string()),
);
let http_client = build_openai_image_http_client(&settings)?;
let reference_image = load_match3d_container_reference_image()?;
let container_prompt = build_match3d_container_generation_prompt(config, prompt);

View File

@@ -34,6 +34,9 @@ pub(crate) struct OpenAiImageSettings {
pub api_key: String,
pub request_timeout_ms: u64,
pub external_api_audit_state: Option<AppState>,
pub external_api_audit_user_id: Option<String>,
pub external_api_audit_profile_id: Option<String>,
pub external_api_audit_request_id: Option<String>,
}
impl std::fmt::Debug for OpenAiImageSettings {
@@ -47,6 +50,18 @@ impl std::fmt::Debug for OpenAiImageSettings {
"external_api_audit_enabled",
&self.external_api_audit_state.is_some(),
)
.field(
"external_api_audit_user_id",
&self.external_api_audit_user_id,
)
.field(
"external_api_audit_profile_id",
&self.external_api_audit_profile_id,
)
.field(
"external_api_audit_request_id",
&self.external_api_audit_request_id,
)
.finish()
}
}
@@ -87,6 +102,9 @@ pub(crate) fn require_openai_image_settings(
api_key: api_key.to_string(),
request_timeout_ms: state.config.vector_engine_image_request_timeout_ms.max(1),
external_api_audit_state: Some(state.clone()),
external_api_audit_user_id: None,
external_api_audit_profile_id: None,
external_api_audit_request_id: None,
})
}
@@ -240,6 +258,21 @@ pub(crate) fn build_openai_image_request_body(
}
impl OpenAiImageSettings {
pub(crate) fn with_external_api_audit_context(
mut self,
user_id: Option<String>,
profile_id: Option<String>,
) -> Self {
self.external_api_audit_user_id = user_id;
self.external_api_audit_profile_id = profile_id;
self
}
pub(crate) fn with_external_api_audit_request_id(mut self, request_id: Option<String>) -> Self {
self.external_api_audit_request_id = request_id;
self
}
fn provider_settings(&self) -> VectorEngineImageSettings {
VectorEngineImageSettings {
base_url: self.base_url.clone(),
@@ -310,6 +343,10 @@ pub(crate) async fn record_openai_image_failure_if_configured(
let Some(draft) = build_openai_image_failure_audit_draft(error) else {
return;
};
let draft = draft
.with_user_id(settings.external_api_audit_user_id.clone())
.with_profile_id(settings.external_api_audit_profile_id.clone())
.with_request_id(settings.external_api_audit_request_id.clone());
record_external_api_failure(state, draft).await;
}
@@ -422,12 +459,18 @@ mod tests {
api_key: "test-key".to_string(),
request_timeout_ms: 1_000_000,
external_api_audit_state: None,
external_api_audit_user_id: None,
external_api_audit_profile_id: None,
external_api_audit_request_id: None,
};
let v1_settings = OpenAiImageSettings {
base_url: "https://vector.example/v1".to_string(),
api_key: "test-key".to_string(),
request_timeout_ms: 1_000_000,
external_api_audit_state: None,
external_api_audit_user_id: None,
external_api_audit_profile_id: None,
external_api_audit_request_id: None,
};
assert_eq!(
@@ -447,12 +490,18 @@ mod tests {
api_key: "test-key".to_string(),
request_timeout_ms: 1_000_000,
external_api_audit_state: None,
external_api_audit_user_id: None,
external_api_audit_profile_id: None,
external_api_audit_request_id: None,
};
let v1_settings = OpenAiImageSettings {
base_url: "https://vector.example/v1".to_string(),
api_key: "test-key".to_string(),
request_timeout_ms: 1_000_000,
external_api_audit_state: None,
external_api_audit_user_id: None,
external_api_audit_profile_id: None,
external_api_audit_request_id: None,
};
assert_eq!(
@@ -472,6 +521,9 @@ mod tests {
api_key: "test-key".to_string(),
request_timeout_ms: 1_000_000,
external_api_audit_state: None,
external_api_audit_user_id: None,
external_api_audit_profile_id: None,
external_api_audit_request_id: None,
};
let http_client = reqwest::Client::new();

View File

@@ -1085,6 +1085,7 @@ pub(crate) fn attach_puzzle_level_asset_bundle(
pub(crate) async fn generate_puzzle_initial_ui_background_required(
state: &PuzzleApiState,
request_context: &RequestContext,
owner_user_id: &str,
session_id: &str,
draft: &PuzzleResultDraftRecord,
@@ -1093,6 +1094,7 @@ pub(crate) async fn generate_puzzle_initial_ui_background_required(
let prompt = resolve_puzzle_initial_ui_background_prompt(draft, target_level);
let generated = generate_puzzle_ui_background_image(
state,
request_context,
owner_user_id,
session_id,
target_level.level_name.as_str(),
@@ -1104,6 +1106,7 @@ pub(crate) async fn generate_puzzle_initial_ui_background_required(
pub(crate) async fn generate_puzzle_level_asset_bundle_required(
state: &PuzzleApiState,
request_context: &RequestContext,
owner_user_id: &str,
session_id: &str,
target_level: &PuzzleDraftLevelRecord,
@@ -1111,6 +1114,7 @@ pub(crate) async fn generate_puzzle_level_asset_bundle_required(
) -> Result<GeneratedPuzzleLevelAssetBundle, AppError> {
generate_puzzle_level_asset_bundle(
state,
request_context,
owner_user_id,
session_id,
target_level.level_name.as_str(),
@@ -1175,6 +1179,7 @@ pub(crate) fn find_puzzle_level_for_initial_asset_check<'a>(
pub(crate) async fn compile_puzzle_draft_with_initial_cover(
state: &PuzzleApiState,
request_context: &RequestContext,
session_id: String,
owner_user_id: String,
prompt_text: Option<&str>,
@@ -1195,6 +1200,7 @@ pub(crate) async fn compile_puzzle_draft_with_initial_cover(
})?;
let mut target_level = select_puzzle_level_for_api(&draft, None)?;
let fallback_level_name = target_level.level_name.clone();
let (_, profile_id) = build_stable_puzzle_work_ids(&compiled_session.session_id);
let image_prompt = resolve_puzzle_draft_cover_prompt(
prompt_text,
&target_level.picture_description,
@@ -1209,6 +1215,7 @@ pub(crate) async fn compile_puzzle_draft_with_initial_cover(
let mut candidates = generate_puzzle_image_candidates(
state,
owner_user_id.as_str(),
Some(profile_id.as_str()),
&compiled_session.session_id,
&target_level.level_name,
&image_prompt,
@@ -1262,6 +1269,7 @@ pub(crate) async fn compile_puzzle_draft_with_initial_cover(
{
let asset_bundle = generate_puzzle_level_asset_bundle_required(
state,
request_context,
owner_user_id.as_str(),
compiled_session.session_id.as_str(),
&target_level,
@@ -1369,7 +1377,6 @@ pub(crate) async fn compile_puzzle_draft_with_initial_cover(
Err(error)
}
})?;
let (_, profile_id) = build_stable_puzzle_work_ids(&compiled_session.session_id);
match state
.spacetime_client()
.update_puzzle_work(PuzzleWorkUpsertRecordInput {
@@ -1441,6 +1448,7 @@ pub(crate) async fn compile_puzzle_draft_with_initial_cover(
pub(crate) async fn compile_puzzle_draft_with_uploaded_cover(
state: &PuzzleApiState,
request_context: &RequestContext,
session_id: String,
owner_user_id: String,
prompt_text: Option<&str>,
@@ -1544,6 +1552,7 @@ pub(crate) async fn compile_puzzle_draft_with_uploaded_cover(
.await?;
let asset_bundle = generate_puzzle_level_asset_bundle_required(
state,
request_context,
owner_user_id.as_str(),
compiled_session.session_id.as_str(),
&target_level,

View File

@@ -78,6 +78,7 @@ pub(crate) async fn create_uploaded_puzzle_image_candidate(
pub(crate) async fn generate_puzzle_image_candidates(
state: &PuzzleApiState,
owner_user_id: &str,
profile_id: Option<&str>,
session_id: &str,
level_name: &str,
prompt: &str,
@@ -150,6 +151,11 @@ pub(crate) async fn generate_puzzle_image_candidates(
// 中文注释SpacetimeDB reducer 不能做外部 I/O参考图读取与外部生图都必须停留在 api-server。
// 中文注释:拼图作品资产统一按 1:1 正方形生成,前端运行时也按正方形棋盘切块承载。
let settings = require_puzzle_vector_engine_settings(state)?;
let settings = PuzzleVectorEngineSettings {
external_api_audit_user_id: Some(owner_user_id.to_string()),
external_api_audit_profile_id: profile_id.map(ToOwned::to_owned),
..settings
};
let vector_engine_started_at = Instant::now();
let generated = if should_use_reference_image_generation {
let reference_image = reference_image.as_ref().ok_or_else(|| {
@@ -255,12 +261,18 @@ pub(crate) async fn generate_puzzle_image_candidates(
pub(crate) async fn generate_puzzle_ui_background_image(
state: &PuzzleApiState,
request_context: &RequestContext,
owner_user_id: &str,
session_id: &str,
level_name: &str,
prompt: &str,
) -> Result<GeneratedPuzzleUiBackgroundResponse, AppError> {
let settings = require_openai_image_settings(state.root_state())?;
let settings = require_openai_image_settings(state.root_state())?
.with_external_api_audit_context(
Some(owner_user_id.to_string()),
Some(session_id.to_string()),
)
.with_external_api_audit_request_id(Some(request_context.request_id().to_string()));
let http_client = build_openai_image_http_client(&settings)?;
let generated = create_openai_image_generation(
&http_client,
@@ -292,12 +304,14 @@ pub(crate) async fn generate_puzzle_ui_background_image(
pub(crate) async fn generate_puzzle_level_asset_bundle(
state: &PuzzleApiState,
request_context: &RequestContext,
owner_user_id: &str,
session_id: &str,
level_name: &str,
puzzle_image: &PuzzleDownloadedImage,
) -> Result<GeneratedPuzzleLevelAssetBundle, AppError> {
let settings = require_puzzle_vector_engine_settings(state)?;
let settings = require_puzzle_vector_engine_settings(state)?
.with_external_api_audit_request_id(Some(request_context.request_id().to_string()));
let http_client = build_puzzle_image_http_client(state, PuzzleImageModel::GptImage2)?;
let puzzle_reference = build_puzzle_downloaded_image_reference(puzzle_image);
let scene_generated = create_puzzle_vector_engine_image_generation(

View File

@@ -71,12 +71,14 @@ pub async fn generate_puzzle_onboarding_work(
let now = current_utc_micros();
let session_id = build_prefixed_uuid_id("puzzle-onboarding-");
let onboarding_profile_id = format!("onboarding-profile-{now}");
let naming = generate_puzzle_first_level_name(&state, prompt_text.as_str()).await;
let tags =
generate_puzzle_work_tags(&state, naming.level_name.as_str(), prompt_text.as_str()).await;
let candidates = generate_puzzle_image_candidates(
&state,
"onboarding-guest",
Some(onboarding_profile_id.as_str()),
session_id.as_str(),
naming.level_name.as_str(),
prompt_text.as_str(),
@@ -132,7 +134,7 @@ pub async fn generate_puzzle_onboarding_work(
));
let item = PuzzleWorkProfileRecord {
work_id: format!("onboarding-work-{now}"),
profile_id: format!("onboarding-profile-{now}"),
profile_id: onboarding_profile_id,
owner_user_id: "onboarding-guest".to_string(),
source_session_id: None,
author_display_name: "陶泥儿主".to_string(),
@@ -675,6 +677,7 @@ pub async fn execute_puzzle_agent_action(
async {
compile_puzzle_draft_with_initial_cover(
&state,
&request_context,
compile_session_id.clone(),
owner_user_id.clone(),
prompt_text,
@@ -689,6 +692,7 @@ pub async fn execute_puzzle_agent_action(
} else {
compile_puzzle_draft_with_uploaded_cover(
&state,
&request_context,
compile_session_id.clone(),
owner_user_id.clone(),
prompt_text,
@@ -861,9 +865,11 @@ pub async fn execute_puzzle_agent_action(
.await?,
]
} else {
let (_, profile_id) = build_stable_puzzle_work_ids(&session.session_id);
generate_puzzle_image_candidates(
&state,
owner_user_id.as_str(),
Some(profile_id.as_str()),
&session.session_id,
&target_level.level_name,
&prompt,
@@ -920,6 +926,7 @@ pub async fn execute_puzzle_agent_action(
})?;
let asset_bundle = generate_puzzle_level_asset_bundle_required(
&state,
&request_context,
owner_user_id.as_str(),
&session.session_id,
&target_level,
@@ -1079,6 +1086,7 @@ pub async fn execute_puzzle_agent_action(
);
let generated = generate_puzzle_ui_background_image(
&state,
&request_context,
owner_user_id.as_str(),
&session.session_id,
&target_level.level_name,

View File

@@ -31,6 +31,9 @@ pub(crate) struct PuzzleVectorEngineSettings {
pub(crate) api_key: String,
pub(crate) request_timeout_ms: u64,
pub(crate) external_api_audit_state: Option<AppState>,
pub(crate) external_api_audit_user_id: Option<String>,
pub(crate) external_api_audit_profile_id: Option<String>,
pub(crate) external_api_audit_request_id: Option<String>,
}
pub(crate) struct PuzzleGeneratedImages {
@@ -100,8 +103,19 @@ impl PuzzleVectorEngineSettings {
api_key: self.api_key.clone(),
request_timeout_ms: self.request_timeout_ms,
external_api_audit_state: self.external_api_audit_state.clone(),
external_api_audit_user_id: self.external_api_audit_user_id.clone(),
external_api_audit_profile_id: self.external_api_audit_profile_id.clone(),
external_api_audit_request_id: self.external_api_audit_request_id.clone(),
}
}
pub(crate) fn with_external_api_audit_request_id(
mut self,
request_id: Option<String>,
) -> Self {
self.external_api_audit_request_id = request_id;
self
}
}
pub(crate) struct ParsedPuzzleImageDataUrl {
@@ -177,6 +191,9 @@ pub(crate) fn require_puzzle_vector_engine_settings(
api_key: api_key.to_string(),
request_timeout_ms: state.vector_engine_image_request_timeout_ms().max(1),
external_api_audit_state: Some(state.root_state().clone()),
external_api_audit_user_id: None,
external_api_audit_profile_id: None,
external_api_audit_request_id: None,
})
}

View File

@@ -389,7 +389,10 @@ async fn generate_square_hole_image_data_url(
size: &str,
failure_context: &str,
) -> Result<String, AppError> {
let settings = require_openai_image_settings(state)?;
let settings = require_openai_image_settings(state)?.with_external_api_audit_context(
Some(owner_user_id.to_string()),
Some(profile_id.to_string()),
);
let http_client = build_openai_image_http_client(&settings)?;
let generated = create_openai_image_generation(
&http_client,