From 5debb30fe9ac5ba84326305fd4758b5c9916c82d Mon Sep 17 00:00:00 2001 From: user Date: Sun, 8 Feb 2026 01:12:41 +0900 Subject: [PATCH] =?UTF-8?q?=E6=96=87=E5=AD=97=E5=B9=85=E8=A3=9C=E6=AD=A3?= =?UTF-8?q?=E3=83=BB=E6=95=B4=E5=88=97=E7=89=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- oproxy.py | 43 ++++++++++++++++++++++++------------------- 1 file changed, 24 insertions(+), 19 deletions(-) diff --git a/oproxy.py b/oproxy.py index edda2af..0af9291 100755 --- a/oproxy.py +++ b/oproxy.py @@ -6,6 +6,7 @@ import os import re import sys import threading +import unicodedata from datetime import datetime 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 MEM_LIMIT = 16.8 - CONFIG = { "url": f"http://127.0.0.1:{DEFAULT_REMOTE_PORT}", "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}" +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): - """モデルがツール(Function Calling)をサポートしているか検証""" try: res = await client.post(f"{CONFIG['url']}/api/show", json={"name": model_name}) if res.status_code == 200: info = res.json() - # テンプレートや詳細情報から 'tool' の記述を探す details = str(info.get("template", "")) + str(info.get("details", "")) return "tool" in details.lower() or "functions" in details.lower() except: @@ -78,16 +86,13 @@ async def sticky_proxy(path: str, request: Request): if response.status_code != 200: err_data = await response.aread() 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}") yield json.dumps( { - "message": {"role": "assistant", "content": report}, + "message": { + "role": "assistant", + "content": f"### Error\n{err_msg}", + }, "done": True, } ).encode() @@ -112,18 +117,16 @@ async def check_connection(): res = await client.get(f"{url}/api/tags") if res.status_code == 200: models = res.json().get("models", []) - print( - f"{get_ts()} {C_GREEN}--- リモートモデル戦力分析 (基準: {MEM_LIMIT}GiB + Tool対応) ---{C_RESET}" + header = ( + f"--- リモートモデル戦力分析 (基準: {MEM_LIMIT}GiB + Tool対応) ---" ) + print(f"{get_ts()} {C_GREEN}{header}{C_RESET}") for m in models: name = m["name"] size_gb = m["size"] / (1024**3) - - # ツール対応を非同期でチェック has_tool = await check_tool_support(client, name) - # 判定ロジック reasons = [] if size_gb > MEM_LIMIT: reasons.append("MEM_OVER") @@ -135,19 +138,22 @@ async def check_connection(): elif "MEM_OVER" in reasons: color, status = C_RED, "❌ MEM " else: - color, status = C_YELLOW, "⚠️ TOOL" # メモリはOKだがツールがない + color, status = C_YELLOW, "⚠️ TOOL" tool_mark = ( f"{C_CYAN}[TOOL]{C_RESET}" if has_tool else f"{C_GRAY}[----]{C_RESET}" ) + + # 名前の表示幅を40に固定(全角対応) + display_name = pad_right(name, 45) 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( - f"{get_ts()} {C_GREEN}------------------------------------------------------------{C_RESET}\n" + f"{get_ts()} {C_GREEN}{'-' * get_visual_width(header)}{C_RESET}\n" ) return True except Exception as e: @@ -155,7 +161,6 @@ async def check_connection(): return False -# (main, wait_for_quit は変更なし) def wait_for_quit(): while True: line = sys.stdin.readline()