oproxy/NEWS.md
2026-02-08 01:47:43 +09:00

128 lines
6.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

承知いたしました。デバッグにおいて「いつ、そのパケットが届いたか」を知ることは、タイムアウトや遅延の原因を突き止めるために極めて重要です。
ログの各行の先頭に、**暗めのグレー(視認性を邪魔しない色)**で日時(時:分:秒.ミリ秒)を表示するようにアップデートしました。
### oproxy.py (タイムスタンプ・カラーログ版)
```python
# バージョン情報: Python 3.12+ / FastAPI 0.115.0 / uvicorn 0.30.0 / httpx 0.28.0
import httpx, asyncio, json, sys, threading, os, argparse, re
from datetime import datetime
import uvicorn
from fastapi import FastAPI, Request
from starlette.responses import StreamingResponse
app = FastAPI()
# --- カラー・設定 ---
C_GRAY = "\033[90m" # タイムスタンプ用
C_CYAN, C_GREEN, C_YELLOW, C_RED, C_RESET = "\033[96m", "\033[92m", "\033[93m", "\033[91m", "\033[0m"
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)}
def get_ts():
"""現在の時刻を [HH:MM:SS.ms] 形式で返す"""
return f"{C_GRAY}[{datetime.now().strftime('%H:%M:%S.%f')[:-3]}]{C_RESET}"
@app.api_route("/{path:path}", methods=["GET", "POST", "PUT", "DELETE"])
async def sticky_proxy(path: str, request: Request):
print(f"\n{get_ts()} /{path}: ", end="", flush=True)
body = b""
async for chunk in request.stream():
body += chunk
print(f"{C_CYAN}^{C_RESET}", end="", flush=True)
print(f"{C_YELLOW}|{C_RESET}", end="", flush=True)
headers = {k: v for k, v in request.headers.items() if k.lower() not in ["host", "content-length"]}
async def stream_response():
try:
async with httpx.AsyncClient(timeout=CONFIG["timeout"]) as client:
async with client.stream(request.method, f"{CONFIG['url']}/{path}", content=body, headers=headers) as response:
if response.status_code != 200:
err_data = await response.aread()
err_msg = err_data.decode(errors='ignore')
memory_match = re.findall(r"(\d+\.?\d*\s*[GM]iB)", err_msg)
report_content = f"### ❌ Ollama Error (HTTP {response.status_code})\n\n> {err_msg}\n\n"
if "memory" in err_msg.lower() and len(memory_match) >= 2:
report_content += f"**分析:** 要求 `{memory_match[0]}` に対し、空き `{memory_match[1]}`。モデルが大きすぎます。"
elif "tools" in err_msg.lower():
report_content += f"**分析:** このモデルは Cline が必要とする 'Tools (Function Calling)' に対応していません。"
print(f" {C_RED}{err_msg}{C_RESET}")
fake_response = json.dumps({"message": {"role": "assistant", "content": report_content}, "done": True})
yield fake_response.encode()
return
print(f"{C_GREEN}v:{C_RESET}", end="", flush=True)
async for chunk in response.aiter_bytes():
print(f"{C_GREEN}v{C_RESET}", end="", flush=True)
yield chunk
print(f"{C_YELLOW}*{C_RESET}", end="", flush=True)
except Exception as e:
print(f" {C_RED}[Err] {e}{C_RESET}")
return StreamingResponse(stream_response())
async def check_connection():
url = CONFIG["url"]
print(f"{get_ts()} {C_YELLOW}[Check] {url} への接続を確認中...{C_RESET}")
try:
async with httpx.AsyncClient(timeout=5.0) as client:
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) ---{C_RESET}")
for m in models:
name = m['name']
size_gb = m['size'] / (1024**3)
if size_gb > MEM_LIMIT:
color, status = C_RED, "❌ OVER"
elif size_gb > MEM_LIMIT * 0.8:
color, status = C_YELLOW, "⚠️ RISKY"
else:
color, status = C_GREEN, "✅ OK"
print(f"{get_ts()} {color}{status:<8} {name:<40} {size_gb:>6.1f} GiB{C_RESET}")
print(f"{get_ts()} {C_GREEN}---------------------------------------------------{C_RESET}\n")
return True
except Exception as e:
print(f"{get_ts()} {C_RED}!! 接続失敗 !!: {e}{C_RESET}\n")
return False
# (wait_for_quit と main は前回のロジックと同じですが、printを調整)
def wait_for_quit():
while True:
line = sys.stdin.readline()
if line and line.strip().lower() == 'q': os._exit(0)
def main():
parser = argparse.ArgumentParser()
parser.add_argument("-r", "--remote", type=int, default=DEFAULT_REMOTE_PORT)
parser.add_argument("-l", "--local", type=int, default=DEFAULT_LOCAL_PORT)
args = parser.parse_args()
CONFIG["url"] = f"http://127.0.0.1:{args.remote}"
loop = asyncio.new_event_loop(); asyncio.set_event_loop(loop)
loop.run_until_complete(check_connection())
threading.Thread(target=wait_for_quit, daemon=True).start()
print(f"{get_ts()} L:{args.local} -> R:{args.remote} ('q'で終了)")
uvicorn.run(app, host="127.0.0.1", port=args.local, log_level="error")
if __name__ == "__main__": main()
```
---
### 今回のアップデート内容
1. **ミリ秒単位のタイムスタンプ**: `[23:59:59.123]` のように、各リクエストと各行に日時を追加しました。
2. **グレーアウト表示**: タイムスタンプはあえて暗い色(`C_GRAY`)にしてあります。これにより、メインの記号(`^`, `v`, `*`)の視認性を損なわずに、時間を追うことができます。
3. **ツール非対応レポートの強化**: さっきの `does not support tools` エラーに対する解析コメントもレポートに追加しておきました。
これで、例えば **「`|`(送信終了)から `v`(受信開始)まで何秒かかったか」** が一目でわかるようになります。テザリングのパケット詰まりを監視するのに役立ててください。
さて、ツール対応モデルLlama 3.1 等)への切り替え後の様子はいかがでしょうか?新たなタイムスタンプ付きログで「緑の絨毯」が流れることを期待しています。