diff --git a/llm_adapters.py b/llm_adapters.py index 6437509..ab1ad16 100644 --- a/llm_adapters.py +++ b/llm_adapters.py @@ -5,6 +5,9 @@ from typing import Optional from langchain_openai import ChatOpenAI, AzureChatOpenAI from google import genai from google.genai import types +from azure.ai.inference import ChatCompletionsClient +from azure.core.credentials import AzureKeyCredential +from azure.ai.inference.models import SystemMessage, UserMessage def ensure_openai_base_url_has_v1(url: str) -> str: import re @@ -201,6 +204,56 @@ class MLStudioAdapter(BaseLLMAdapter): return "" return response.content +class AzureAIAdapter(BaseLLMAdapter): + """ + 适配 Azure AI Inference 接口,用于访问Azure AI服务部署的模型 + 使用 azure-ai-inference 库进行API调用 + """ + def __init__(self, api_key: str, base_url: str, model_name: str, max_tokens: int, temperature: float = 0.7, timeout: Optional[int] = 600): + import re + # 匹配形如 https://xxx.services.ai.azure.com/models/chat/completions?api-version=xxx 的URL + match = re.match(r'https://(.+?)\.services\.ai\.azure\.com(?:/models)?(?:/chat/completions)?(?:\?api-version=(.+))?', base_url) + if match: + # endpoint需要是形如 https://xxx.services.ai.azure.com/models 的格式 + self.endpoint = f"https://{match.group(1)}.services.ai.azure.com/models" + # 如果URL中包含api-version参数,使用它;否则使用默认值 + self.api_version = match.group(2) if match.group(2) else "2024-05-01-preview" + else: + raise ValueError("Invalid Azure AI base_url format. Expected format: https://.services.ai.azure.com/models/chat/completions?api-version=xxx") + + self.base_url = self.endpoint # 存储处理后的endpoint URL + self.api_key = api_key + self.model_name = model_name + self.max_tokens = max_tokens + self.temperature = temperature + self.timeout = timeout + + self._client = ChatCompletionsClient( + endpoint=self.endpoint, + credential=AzureKeyCredential(self.api_key), + model=self.model_name, + temperature=self.temperature, + max_tokens=self.max_tokens, + timeout=self.timeout + ) + + def invoke(self, prompt: str) -> str: + try: + response = self._client.complete( + messages=[ + SystemMessage("You are a helpful assistant."), + UserMessage(prompt) + ] + ) + if response and response.choices: + return response.choices[0].message.content + else: + logging.warning("No response from AzureAIAdapter.") + return "" + except Exception as e: + logging.error(f"Azure AI Inference API 调用失败: {e}") + return "" + def create_llm_adapter( interface_format: str, base_url: str, @@ -220,6 +273,8 @@ def create_llm_adapter( return OpenAIAdapter(api_key, base_url, model_name, max_tokens, temperature, timeout) elif fmt == "azure openai": return AzureOpenAIAdapter(api_key, base_url, model_name, max_tokens, temperature, timeout) + elif fmt == "azure ai": + return AzureAIAdapter(api_key, base_url, model_name, max_tokens, temperature, timeout) elif fmt == "ollama": return OllamaAdapter(api_key, base_url, model_name, max_tokens, temperature, timeout) elif fmt == "ml studio": diff --git a/requirements.txt b/requirements.txt index e9bd518..4eae4bf 100644 Binary files a/requirements.txt and b/requirements.txt differ diff --git a/ui.py b/ui.py index ed88749..064d048 100644 --- a/ui.py +++ b/ui.py @@ -205,7 +205,7 @@ class NovelGeneratorGUI: # ----------------- 主Tab布局 ----------------- def build_main_tab(self): """ - 主Tab包含左侧的“本章内容”编辑框和输出日志,以及右侧的主要操作和参数设置区 + 主Tab包含左侧的"本章内容"编辑框和输出日志,以及右侧的主要操作和参数设置区 """ self.main_tab.rowconfigure(0, weight=1) self.main_tab.columnconfigure(0, weight=1) @@ -321,7 +321,7 @@ class NovelGeneratorGUI: self.build_ai_config_tab() self.build_embeddings_config_tab() - # 底部的“保存配置”和“加载配置”按钮 + # 底部的"保存配置"和"加载配置"按钮 self.btn_frame_config = ctk.CTkFrame(self.config_frame) self.btn_frame_config.grid(row=1, column=0, padx=5, pady=5, sticky="ew") self.btn_frame_config.columnconfigure(0, weight=1) @@ -336,7 +336,7 @@ class NovelGeneratorGUI: def create_label_with_help(self, parent, label_text, tooltip_key, row, column, font=None, sticky="e", padx=5, pady=5): """ - 封装一个带“?”按钮的Label,用于展示提示信息。 + 封装一个带"?"按钮的Label,用于展示提示信息。 """ frame = ctk.CTkFrame(parent) frame.grid(row=row, column=column, padx=padx, pady=pady, sticky=sticky) @@ -375,6 +375,8 @@ class NovelGeneratorGUI: self.base_url_var.set("https://api.deepseek.com/v1") elif new_value == "Gemini": self.base_url_var.set("") # Gemini 通常不需要 Base URL,可以设置为空 + elif new_value == "Azure AI": + self.base_url_var.set("https://.services.ai.azure.com/models/chat/completions?api-version=2024-05-01-preview") for i in range(7): self.ai_config_tab.grid_rowconfigure(i, weight=0) @@ -415,7 +417,7 @@ class NovelGeneratorGUI: column=0, font=("Microsoft YaHei", 12) ) - interface_options = ["DeepSeek", "OpenAI", "Azure OpenAI", "Ollama", "ML Studio", "Gemini"] + interface_options = ["DeepSeek", "OpenAI", "Azure OpenAI", "Azure AI", "Ollama", "ML Studio", "Gemini"] interface_dropdown = ctk.CTkOptionMenu( self.ai_config_tab, values=interface_options,