Phase 0: 專案初始化 - 建立專案結構、環境設定與 LLM 服務模組
This commit is contained in:
150
services/llm_service.py
Normal file
150
services/llm_service.py
Normal file
@@ -0,0 +1,150 @@
|
||||
"""
|
||||
Ollama LLM API 服務模組
|
||||
支援一般請求與串流模式
|
||||
"""
|
||||
|
||||
import requests
|
||||
import json
|
||||
from typing import Generator, Optional
|
||||
from config import Config
|
||||
|
||||
|
||||
class LLMService:
|
||||
"""Ollama API 服務封裝"""
|
||||
|
||||
def __init__(self, api_url: str = None, default_model: str = None):
|
||||
self.api_url = api_url or Config.OLLAMA_API_URL
|
||||
self.default_model = default_model or Config.OLLAMA_DEFAULT_MODEL
|
||||
|
||||
def get_available_models(self) -> list:
|
||||
"""取得可用模型列表"""
|
||||
try:
|
||||
response = requests.get(f"{self.api_url}/v1/models", timeout=10)
|
||||
response.raise_for_status()
|
||||
models = response.json()
|
||||
return [m['id'] for m in models.get('data', [])]
|
||||
except requests.RequestException as e:
|
||||
raise LLMServiceError(f"無法取得模型列表: {e}")
|
||||
|
||||
def chat(
|
||||
self,
|
||||
messages: list,
|
||||
model: str = None,
|
||||
temperature: float = 0.7,
|
||||
system_prompt: str = None
|
||||
) -> str:
|
||||
"""
|
||||
發送聊天請求 (非串流)
|
||||
|
||||
Args:
|
||||
messages: 訊息列表 [{"role": "user", "content": "..."}]
|
||||
model: 模型名稱
|
||||
temperature: 溫度參數 (0-1)
|
||||
system_prompt: 系統提示詞
|
||||
|
||||
Returns:
|
||||
AI 回應內容
|
||||
"""
|
||||
model = model or self.default_model
|
||||
|
||||
# 加入系統提示詞
|
||||
if system_prompt:
|
||||
messages = [{"role": "system", "content": system_prompt}] + messages
|
||||
|
||||
payload = {
|
||||
"model": model,
|
||||
"messages": messages,
|
||||
"temperature": temperature,
|
||||
"stream": False
|
||||
}
|
||||
|
||||
try:
|
||||
response = requests.post(
|
||||
f"{self.api_url}/v1/chat/completions",
|
||||
json=payload,
|
||||
timeout=60
|
||||
)
|
||||
response.raise_for_status()
|
||||
result = response.json()
|
||||
return result['choices'][0]['message']['content']
|
||||
except requests.RequestException as e:
|
||||
raise LLMServiceError(f"聊天請求失敗: {e}")
|
||||
|
||||
def chat_stream(
|
||||
self,
|
||||
messages: list,
|
||||
model: str = None,
|
||||
temperature: float = 0.7,
|
||||
system_prompt: str = None
|
||||
) -> Generator[str, None, None]:
|
||||
"""
|
||||
發送聊天請求 (串流模式)
|
||||
|
||||
Args:
|
||||
messages: 訊息列表
|
||||
model: 模型名稱
|
||||
temperature: 溫度參數
|
||||
system_prompt: 系統提示詞
|
||||
|
||||
Yields:
|
||||
串流回應的每個片段
|
||||
"""
|
||||
model = model or self.default_model
|
||||
|
||||
if system_prompt:
|
||||
messages = [{"role": "system", "content": system_prompt}] + messages
|
||||
|
||||
payload = {
|
||||
"model": model,
|
||||
"messages": messages,
|
||||
"temperature": temperature,
|
||||
"stream": True
|
||||
}
|
||||
|
||||
try:
|
||||
response = requests.post(
|
||||
f"{self.api_url}/v1/chat/completions",
|
||||
json=payload,
|
||||
stream=True,
|
||||
timeout=120
|
||||
)
|
||||
response.raise_for_status()
|
||||
|
||||
for line in response.iter_lines():
|
||||
if line:
|
||||
if line.startswith(b"data: "):
|
||||
data_str = line[6:].decode('utf-8')
|
||||
if data_str.strip() != "[DONE]":
|
||||
try:
|
||||
data = json.loads(data_str)
|
||||
if 'choices' in data:
|
||||
delta = data['choices'][0].get('delta', {})
|
||||
if 'content' in delta:
|
||||
yield delta['content']
|
||||
except json.JSONDecodeError:
|
||||
continue
|
||||
except requests.RequestException as e:
|
||||
raise LLMServiceError(f"串流請求失敗: {e}")
|
||||
|
||||
def simple_query(self, prompt: str, system_prompt: str = None) -> str:
|
||||
"""
|
||||
簡單查詢 (單一問題)
|
||||
|
||||
Args:
|
||||
prompt: 使用者問題
|
||||
system_prompt: 系統提示詞
|
||||
|
||||
Returns:
|
||||
AI 回應
|
||||
"""
|
||||
messages = [{"role": "user", "content": prompt}]
|
||||
return self.chat(messages, system_prompt=system_prompt)
|
||||
|
||||
|
||||
class LLMServiceError(Exception):
|
||||
"""LLM 服務錯誤"""
|
||||
pass
|
||||
|
||||
|
||||
# 全域實例
|
||||
llm_service = LLMService()
|
||||
Reference in New Issue
Block a user