diff --git a/ltx_director.py b/ltx_director.py index 8cf2eee..f861426 100644 --- a/ltx_director.py +++ b/ltx_director.py @@ -437,11 +437,37 @@ _PROVIDER_DEFAULTS = { "custom": {"url": "", "model": ""}, } -_ANALYZE_PROMPT = ( - "Describe the character's physical appearance in two concise sentences. " - "Specify their hair color/style, face details, and their clothing type/color. " - "Keep the entire response very brief." -) +_ANALYZE_PROMPT = ( + "Describe the character's physical appearance in two concise sentences. " + "Specify their hair color/style, face details, and their clothing type/color. " + "Keep the entire response very brief." +) + + +def _extract_ollama_generated_text(resp_json: dict) -> str: + """Handle Ollama generate/chat variants and reasoning-capable models.""" + if not isinstance(resp_json, dict): + return "" + + candidates = [] + candidates.append(resp_json.get("response")) + candidates.append(resp_json.get("thinking")) + candidates.append(resp_json.get("content")) + + message = resp_json.get("message") + if isinstance(message, dict): + candidates.append(message.get("content")) + candidates.append(message.get("reasoning_content")) + candidates.append(message.get("thinking")) + + for candidate in candidates: + if isinstance(candidate, str) and candidate.strip(): + text = candidate.strip() + if "" in text: + text = text.split("")[-1].strip() + if text: + return text + return "" def _resolve_provider(data): @@ -487,18 +513,36 @@ async def analyze_character_endpoint(request): try: async with aiohttp.ClientSession() as session: - if provider == "ollama": - payload = { - "model": model_name, "prompt": _ANALYZE_PROMPT, - "images": cleaned_b64_list, "stream": False, "keep_alive": 0, - } - async with session.post(f"{base_url}/api/generate", json=payload, timeout=120) as response: - if response.status != 200: - err_txt = await response.text() - return web.json_response({"status": "error", "message": f"Ollama HTTP {response.status}: {err_txt}"}) - resp_json = await response.json() - generated_text = (resp_json.get("response") or "").strip() - else: + if provider == "ollama": + payload = { + "model": model_name, "prompt": _ANALYZE_PROMPT, + "images": cleaned_b64_list, "stream": False, "keep_alive": 0, + } + async with session.post(f"{base_url}/api/generate", json=payload, timeout=300) as response: + if response.status != 200: + err_txt = await response.text() + return web.json_response({"status": "error", "message": f"Ollama HTTP {response.status}: {err_txt}"}) + resp_json = await response.json() + generated_text = _extract_ollama_generated_text(resp_json) + + # Some Ollama model variants return an empty `response` from `/api/generate` + # even though the same request succeeds via the chat endpoint. + if not generated_text: + chat_payload = { + "model": model_name, + "messages": [{ + "role": "user", + "content": _ANALYZE_PROMPT, + "images": cleaned_b64_list, + }], + "stream": False, + "keep_alive": 0, + } + async with session.post(f"{base_url}/api/chat", json=chat_payload, timeout=300) as response: + if response.status == 200: + resp_json = await response.json() + generated_text = _extract_ollama_generated_text(resp_json) + else: # OpenAI-compatible vision chat (LM Studio / Custom). content = [{"type": "text", "text": _ANALYZE_PROMPT}] for b64 in cleaned_b64_list: @@ -528,11 +572,16 @@ async def analyze_character_endpoint(request): "message": f"Could not connect to {provider} at {base_url}. Make sure the server is running and reachable.", }) - if "" in generated_text: - generated_text = generated_text.split("")[-1].strip() - - log.info("[LTXDirector] Analysis complete: %s", generated_text) - return web.json_response({"status": "success", "description": generated_text}) + if "" in generated_text: + generated_text = generated_text.split("")[-1].strip() + if not generated_text: + return web.json_response({ + "status": "error", + "message": f"{provider} returned an empty analysis response.", + }) + + log.info("[LTXDirector] Analysis complete: %s", generated_text) + return web.json_response({"status": "success", "description": generated_text}) except Exception as e: log.error(f"[LTXDirector] Failed to analyze character: {e}")