diff --git a/src/pygpt_net/controller/chat/common.py b/src/pygpt_net/controller/chat/common.py index 7297ad147..8fb5b9b2d 100644 --- a/src/pygpt_net/controller/chat/common.py +++ b/src/pygpt_net/controller/chat/common.py @@ -343,6 +343,9 @@ def check_api_key( """ if model is None: return True + # Ollama and other local providers do not require an API key + if model.is_ollama(): + return True config = self.window.core.config provider_keys = { diff --git a/src/pygpt_net/controller/model/model.py b/src/pygpt_net/controller/model/model.py index da8a8b652..eb40daf03 100755 --- a/src/pygpt_net/controller/model/model.py +++ b/src/pygpt_net/controller/model/model.py @@ -58,7 +58,7 @@ def handle(self, event: BaseEvent): mode == MODE_CHAT and not model_data.is_openai_supported() and model_data.is_ollama() ) ): - model_id = model_data.get_ollama_model() + model_id = (model_data.get_ollama_model() or "").strip() # load ENV vars first if ('env' in model_data.llama_index @@ -76,8 +76,9 @@ def handle(self, event: BaseEvent): return if not is_model: event.data["stop"] = True # stop flow + display_name = model_id or model_data.id or model or "" self.window.ui.dialogs.alert( - trans("dialog.ollama.model_not_found").replace("{model}", model_id)) + trans("dialog.ollama.model_not_found").replace("{model}", display_name)) return # on input before diff --git a/src/pygpt_net/core/idx/response.py b/src/pygpt_net/core/idx/response.py index 91f400f93..24189dbd6 100644 --- a/src/pygpt_net/core/idx/response.py +++ b/src/pygpt_net/core/idx/response.py @@ -125,13 +125,14 @@ def from_llm( :param llm: LLM instance :param response: Response data """ - output = response.message.content + msg = getattr(response, "message", None) + output = (getattr(msg, "content", None) if msg else None) or "" + if isinstance(output, str): + output = output.strip() or output tool_calls = llm.get_tool_calls_from_response( response, error_on_no_tool_call=False, ) - if output is None: - output = "" ctx.set_output(output, "") ctx.tool_calls = self.window.core.command.unpack_tool_calls_from_llama(tool_calls) diff --git a/src/pygpt_net/core/models/ollama.py b/src/pygpt_net/core/models/ollama.py index 27ef589ed..5f929a84e 100644 --- a/src/pygpt_net/core/models/ollama.py +++ b/src/pygpt_net/core/models/ollama.py @@ -73,6 +73,9 @@ def check_model(self, model: str) -> dict: 'is_installed': False, 'is_model': False, } + if not model: + return result + model = model.strip() if model in self.available_models: return { 'is_installed': True, @@ -84,7 +87,8 @@ def check_model(self, model: str) -> dict: if "models" not in status: return result for item in status.get('models', []): - model_id = item.get('name') + raw_name = item.get('name') or '' + model_id = raw_name.strip() if model_id not in self.available_models: self.available_models.append(model_id) if model_id == model: @@ -92,10 +96,11 @@ def check_model(self, model: str) -> dict: 'is_installed': True, 'is_model': True, } - model_id = item.get('name').replace(":latest", "") # handle latest tag - if model_id not in self.available_models: - self.available_models.append(model_id) - if model_id == model: + # handle :latest tag (compare without it) + model_id_no_latest = model_id.replace(":latest", "").strip() + if model_id_no_latest not in self.available_models: + self.available_models.append(model_id_no_latest) + if model_id_no_latest == model or model_id == model: return { 'is_installed': True, 'is_model': True, diff --git a/src/pygpt_net/item/model.py b/src/pygpt_net/item/model.py index c25d0c7b1..03b72b51a 100755 --- a/src/pygpt_net/item/model.py +++ b/src/pygpt_net/item/model.py @@ -231,15 +231,15 @@ def get_provider(self) -> str: def get_ollama_model(self) -> str: """ - Get Ollama model ID + Get Ollama model ID (name used for ollama pull / API). - :return: model ID + :return: model ID, or self.id when no "model" arg is set (e.g. custom/non-default list) """ if "args" in self.llama_index: for arg in self.llama_index["args"]: - if arg["name"] == "model": - return arg["value"] - return "" + if arg["name"] == "model" and arg.get("value"): + return (arg["value"] or "").strip() + return (self.id or "").strip() def has_mode(self, mode: str) -> bool: """ diff --git a/src/pygpt_net/provider/api/openai/__init__.py b/src/pygpt_net/provider/api/openai/__init__.py index 2261e068d..bbcfe4ffa 100755 --- a/src/pygpt_net/provider/api/openai/__init__.py +++ b/src/pygpt_net/provider/api/openai/__init__.py @@ -332,10 +332,15 @@ def quick_call( if len(tools) > 0 and "disable_tools" not in extra: additional_kwargs["tools"] = tools + model_id = (model.get_ollama_model() or model.id or "").strip() if model.is_ollama() else (model.id or "") + if not model_id: + self.locked = False + return None + try: response = client.chat.completions.create( messages=messages, - model=model.id, + model=model_id, temperature=temperature, top_p=1.0, frequency_penalty=0.0, diff --git a/src/pygpt_net/provider/api/openai/chat.py b/src/pygpt_net/provider/api/openai/chat.py index c16614f23..aabd35fd9 100755 --- a/src/pygpt_net/provider/api/openai/chat.py +++ b/src/pygpt_net/provider/api/openai/chat.py @@ -146,9 +146,14 @@ def send( response_kwargs['stream_options'] = {"include_usage": True} # OpenRouter: add web search remote tool (if enabled) - model_id = model.id + # Ollama: use actual Ollama model name (get_ollama_model), not just list key + model_id = model.id or "" if model.provider == "open_router": model_id = self.window.core.models.get_openrouter_model(model) + elif model.is_ollama(): + model_id = (model.get_ollama_model() or model.id or "").strip() + if not model_id: + raise ValueError("Model name is required for the API request. Check that the selected model has a valid model ID.") response = client.chat.completions.create( messages=messages, diff --git a/src/pygpt_net/provider/api/openai/responses.py b/src/pygpt_net/provider/api/openai/responses.py index b432634e2..f826fc648 100644 --- a/src/pygpt_net/provider/api/openai/responses.py +++ b/src/pygpt_net/provider/api/openai/responses.py @@ -169,9 +169,12 @@ def send( "summary": "concise", } + model_id = (model.get_ollama_model() or model.id or "").strip() if model.is_ollama() else (model.id or "") + if not model_id: + raise ValueError("Model name is required for the API request.") response = client.responses.create( input=messages, - model=model.id, + model=model_id, stream=stream, **response_kwargs, ) diff --git a/src/pygpt_net/provider/llms/ollama.py b/src/pygpt_net/provider/llms/ollama.py index 1416269da..c149a8d1c 100755 --- a/src/pygpt_net/provider/llms/ollama.py +++ b/src/pygpt_net/provider/llms/ollama.py @@ -92,9 +92,24 @@ def llama( if "base_url" not in args: args["base_url"] = os.environ['OLLAMA_API_BASE'] if "model" not in args: - args["model"] = model.id + args["model"] = (model.id or "").strip() + else: + args["model"] = (args["model"] or "").strip() if "request_timeout" not in args: args["request_timeout"] = 300 + # Apply context and output token settings from model/config + if window: + ctx_size = window.core.models.get_num_ctx(model.id) if model.id else 0 + if ctx_size <= 0: + ctx_size = window.core.config.get("max_total_tokens") or 0 + if ctx_size > 0: + args["context_window"] = int(ctx_size) + max_out = window.core.models.get_tokens(model.id) if model.id else 0 + if max_out <= 0: + max_out = window.core.config.get("max_output_tokens") or 0 + if max_out > 0: + extra_opts = args.get("additional_kwargs") or {} + args["additional_kwargs"] = {**extra_opts, "num_predict": int(max_out)} return Ollama(**args) def get_embeddings_model(