文字幅補正・整列版
This commit is contained in:
parent
a6a107bd6f
commit
5debb30fe9
1 changed files with 24 additions and 19 deletions
43
oproxy.py
43
oproxy.py
|
|
@ -6,6 +6,7 @@ import os
|
||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
import threading
|
import threading
|
||||||
|
import unicodedata
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
import httpx
|
import httpx
|
||||||
|
|
@ -26,7 +27,6 @@ C_GRAY, C_CYAN, C_GREEN, C_YELLOW, C_RED, C_RESET = (
|
||||||
)
|
)
|
||||||
DEFAULT_REMOTE_PORT, DEFAULT_LOCAL_PORT = 11430, 11434
|
DEFAULT_REMOTE_PORT, DEFAULT_LOCAL_PORT = 11430, 11434
|
||||||
MEM_LIMIT = 16.8
|
MEM_LIMIT = 16.8
|
||||||
|
|
||||||
CONFIG = {
|
CONFIG = {
|
||||||
"url": f"http://127.0.0.1:{DEFAULT_REMOTE_PORT}",
|
"url": f"http://127.0.0.1:{DEFAULT_REMOTE_PORT}",
|
||||||
"timeout": httpx.Timeout(None),
|
"timeout": httpx.Timeout(None),
|
||||||
|
|
@ -37,13 +37,21 @@ def get_ts():
|
||||||
return f"{C_GRAY}[{datetime.now().strftime('%H:%M:%S.%f')[:-3]}]{C_RESET}"
|
return f"{C_GRAY}[{datetime.now().strftime('%H:%M:%S.%f')[:-3]}]{C_RESET}"
|
||||||
|
|
||||||
|
|
||||||
|
def get_visual_width(text):
|
||||||
|
"""全角を2、半角を1として計算する"""
|
||||||
|
return sum(2 if unicodedata.east_asian_width(c) in "WF" else 1 for c in text)
|
||||||
|
|
||||||
|
|
||||||
|
def pad_right(text, width):
|
||||||
|
"""見た目の幅を揃えるためのパディング"""
|
||||||
|
return text + " " * (width - get_visual_width(text))
|
||||||
|
|
||||||
|
|
||||||
async def check_tool_support(client, model_name):
|
async def check_tool_support(client, model_name):
|
||||||
"""モデルがツール(Function Calling)をサポートしているか検証"""
|
|
||||||
try:
|
try:
|
||||||
res = await client.post(f"{CONFIG['url']}/api/show", json={"name": model_name})
|
res = await client.post(f"{CONFIG['url']}/api/show", json={"name": model_name})
|
||||||
if res.status_code == 200:
|
if res.status_code == 200:
|
||||||
info = res.json()
|
info = res.json()
|
||||||
# テンプレートや詳細情報から 'tool' の記述を探す
|
|
||||||
details = str(info.get("template", "")) + str(info.get("details", ""))
|
details = str(info.get("template", "")) + str(info.get("details", ""))
|
||||||
return "tool" in details.lower() or "functions" in details.lower()
|
return "tool" in details.lower() or "functions" in details.lower()
|
||||||
except:
|
except:
|
||||||
|
|
@ -78,16 +86,13 @@ async def sticky_proxy(path: str, request: Request):
|
||||||
if response.status_code != 200:
|
if response.status_code != 200:
|
||||||
err_data = await response.aread()
|
err_data = await response.aread()
|
||||||
err_msg = err_data.decode(errors="ignore")
|
err_msg = err_data.decode(errors="ignore")
|
||||||
|
|
||||||
# レポート生成
|
|
||||||
report = f"### ❌ Ollama Error ({response.status_code})\n\n> {err_msg}\n\n"
|
|
||||||
if "tools" in err_msg.lower():
|
|
||||||
report += "#### 💡 原因: ツール非対応\nこのモデルは Cline の自動操作に必要な `Tools` 機能を持っていません。Llama 3.1 等への変更を推奨します。"
|
|
||||||
|
|
||||||
print(f" {C_RED}{err_msg}{C_RESET}")
|
print(f" {C_RED}{err_msg}{C_RESET}")
|
||||||
yield json.dumps(
|
yield json.dumps(
|
||||||
{
|
{
|
||||||
"message": {"role": "assistant", "content": report},
|
"message": {
|
||||||
|
"role": "assistant",
|
||||||
|
"content": f"### Error\n{err_msg}",
|
||||||
|
},
|
||||||
"done": True,
|
"done": True,
|
||||||
}
|
}
|
||||||
).encode()
|
).encode()
|
||||||
|
|
@ -112,18 +117,16 @@ async def check_connection():
|
||||||
res = await client.get(f"{url}/api/tags")
|
res = await client.get(f"{url}/api/tags")
|
||||||
if res.status_code == 200:
|
if res.status_code == 200:
|
||||||
models = res.json().get("models", [])
|
models = res.json().get("models", [])
|
||||||
print(
|
header = (
|
||||||
f"{get_ts()} {C_GREEN}--- リモートモデル戦力分析 (基準: {MEM_LIMIT}GiB + Tool対応) ---{C_RESET}"
|
f"--- リモートモデル戦力分析 (基準: {MEM_LIMIT}GiB + Tool対応) ---"
|
||||||
)
|
)
|
||||||
|
print(f"{get_ts()} {C_GREEN}{header}{C_RESET}")
|
||||||
|
|
||||||
for m in models:
|
for m in models:
|
||||||
name = m["name"]
|
name = m["name"]
|
||||||
size_gb = m["size"] / (1024**3)
|
size_gb = m["size"] / (1024**3)
|
||||||
|
|
||||||
# ツール対応を非同期でチェック
|
|
||||||
has_tool = await check_tool_support(client, name)
|
has_tool = await check_tool_support(client, name)
|
||||||
|
|
||||||
# 判定ロジック
|
|
||||||
reasons = []
|
reasons = []
|
||||||
if size_gb > MEM_LIMIT:
|
if size_gb > MEM_LIMIT:
|
||||||
reasons.append("MEM_OVER")
|
reasons.append("MEM_OVER")
|
||||||
|
|
@ -135,19 +138,22 @@ async def check_connection():
|
||||||
elif "MEM_OVER" in reasons:
|
elif "MEM_OVER" in reasons:
|
||||||
color, status = C_RED, "❌ MEM "
|
color, status = C_RED, "❌ MEM "
|
||||||
else:
|
else:
|
||||||
color, status = C_YELLOW, "⚠️ TOOL" # メモリはOKだがツールがない
|
color, status = C_YELLOW, "⚠️ TOOL"
|
||||||
|
|
||||||
tool_mark = (
|
tool_mark = (
|
||||||
f"{C_CYAN}[TOOL]{C_RESET}"
|
f"{C_CYAN}[TOOL]{C_RESET}"
|
||||||
if has_tool
|
if has_tool
|
||||||
else f"{C_GRAY}[----]{C_RESET}"
|
else f"{C_GRAY}[----]{C_RESET}"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# 名前の表示幅を40に固定(全角対応)
|
||||||
|
display_name = pad_right(name, 45)
|
||||||
print(
|
print(
|
||||||
f"{get_ts()} {color}{status:<8}{C_RESET} {tool_mark} {name:<35} {size_gb:>5.1f} GiB"
|
f"{get_ts()} {color}{status:<8}{C_RESET} {tool_mark} {display_name} {size_gb:>5.1f} GiB"
|
||||||
)
|
)
|
||||||
|
|
||||||
print(
|
print(
|
||||||
f"{get_ts()} {C_GREEN}------------------------------------------------------------{C_RESET}\n"
|
f"{get_ts()} {C_GREEN}{'-' * get_visual_width(header)}{C_RESET}\n"
|
||||||
)
|
)
|
||||||
return True
|
return True
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|
@ -155,7 +161,6 @@ async def check_connection():
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
# (main, wait_for_quit は変更なし)
|
|
||||||
def wait_for_quit():
|
def wait_for_quit():
|
||||||
while True:
|
while True:
|
||||||
line = sys.stdin.readline()
|
line = sys.stdin.readline()
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue