HASHIRU / scripts /opt_http_client_patch.py
mulambo's picture
Initial commit
fea1bd1
# C:\Users\marco\agente_gemini\HASHIRU_6_1\scripts\opt_http_client_patch.py
from __future__ import annotations
import re, ast, sys, pathlib
ROOT = pathlib.Path(__file__).resolve().parents[1]
TARGET = ROOT / "main_agent.py"
def read_text(p: pathlib.Path) -> str:
return p.read_text(encoding="utf-8")
def write_text(p: pathlib.Path, s: str) -> None:
p.write_text(s, encoding="utf-8")
def ensure_http_client_init(src: str) -> tuple[str, bool]:
"""
Insere em __init__:
self.http: Optional[httpx.AsyncClient] = httpx.AsyncClient()
logo após:
self.reinforcement_data: List[Dict[str, Any]] = []
Se já existir, não duplica.
"""
if "self.http: Optional[httpx.AsyncClient] = httpx.AsyncClient()" in src:
return src, False
anchor = r"(self\.reinforcement_data:\s*List\[Dict\[str,\s*Any\]\]\s*=\s*\[\]\s*)"
if re.search(anchor, src):
patched = re.sub(
anchor,
r"\1\n # Reutiliza um único cliente HTTP (melhor performance)\n"
r" self.http: Optional[httpx.AsyncClient] = httpx.AsyncClient()",
src, count=1, flags=re.M
)
return patched, True
# Fallback: tenta inserir dentro de __init__ depois do último self.*
m_init = re.search(r"class\s+AutonomousAgent\s*:\s*[\s\S]*?def\s+__init__\(", src)
if m_init:
# insere após primeira linha de __init__
patched = re.sub(
r"(\s*def\s+__init__\([^\)]*\):\s*\n)",
r"\1 self.http: Optional[httpx.AsyncClient] = httpx.AsyncClient()\n",
src, count=1
)
return patched, True
return src, False
def replace_post_ollama(src: str) -> tuple[str, bool]:
"""
Substitui o corpo de _post_ollama para usar self.http compartilhado,
mantendo timeout por chamada e fallback com AsyncClient temporário.
"""
if "client = getattr(self, \"http\", None)" in src and "temp_client.post(" in src:
return src, False # já está no formato novo
new_post = (
"async def _post_ollama(self, base_url: str, payload: Dict[str, Any], timeout: float) -> str:\n"
" \"\"\"\n"
" Usa o cliente HTTP compartilhado (self.http) para reduzir overhead de conexões.\n"
" Mantém compatibilidade passando timeout por requisição.\n"
" \"\"\"\n"
" client = getattr(self, \"http\", None)\n"
" if client is not None:\n"
" r = await client.post(f\"{base_url}/api/generate\", json=payload, timeout=timeout)\n"
" r.raise_for_status()\n"
" data = r.json()\n"
" return data.get(\"response\", \"\") or data.get(\"message\", \"\")\n"
" # Fallback defensivo (se por algum motivo self.http não existir)\n"
" import httpx as _httpx\n"
" async with _httpx.AsyncClient() as temp_client:\n"
" r = await temp_client.post(f\"{base_url}/api/generate\", json=payload, timeout=timeout)\n"
" r.raise_for_status()\n"
" data = r.json()\n"
" return data.get(\"response\", \"\") or data.get(\"message\", \"\")\n"
)
# Delimitar bloco do método dentro da classe (indent de 4 espaços) até o próximo método da classe
# Começa no "async def _post_ollama(" com 4 espaços de indent
m_start = re.search(r"^\s{4}async\s+def\s+_post_ollama\([^\n]*\):", src, flags=re.M)
if not m_start:
return src, False
# Próximo método da classe (4 espaços + async def ) para fechar o bloco
m_next = re.search(r"^\s{4}async\s+def\s+\w+\(", src[m_start.end():], flags=re.M)
if m_next:
start = m_start.start()
end = m_start.end() + m_next.start()
else:
# até o fim do arquivo
start = m_start.start()
end = len(src)
# Mantém indentação de 4 espaços
new_block = " " + new_post.replace("\n", "\n ")
patched = src[:start] + new_block + src[end:]
return patched, True
def ensure_on_chat_end(src: str) -> tuple[str, bool]:
"""
Adiciona hook @cl.on_chat_end que fecha agent.http se existir.
"""
if "@cl.on_chat_end" in src:
return src, False
hook = (
"@cl.on_chat_end\n"
"async def on_chat_end():\n"
" try:\n"
" if hasattr(agent, \"http\") and agent.http is not None:\n"
" await agent.http.aclose()\n"
" except Exception:\n"
" # Evita quebrar o encerramento da sessão por erros de fechamento\n"
" pass\n"
)
if not src.endswith("\n"):
src += "\n"
src += "\n" + hook
return src, True
def ensure_typing_optional(src: str) -> tuple[str, bool]:
"""
Garante que Optional esteja importado (já costuma estar, mas checamos).
"""
if re.search(r"from\s+typing\s+import\s+[^\\n]*Optional", src):
return src, False
# tenta acrescentar Optional na linha existente de typing
src2, n = re.subn(
r"(from\s+typing\s+import\s+)([^\n]+)",
lambda m: m.group(1) + m.group(2).rstrip() + ", Optional",
src, count=1
)
return (src2, n > 0)
def main():
print(f"[patch] ROOT = {ROOT}")
print(f"[patch] FILE = {TARGET}")
src = read_text(TARGET)
changed = False
src, c1 = ensure_http_client_init(src); changed |= c1
src, c2 = replace_post_ollama(src); changed |= c2
src, c3 = ensure_on_chat_end(src); changed |= c3
src, c4 = ensure_typing_optional(src); changed |= c4
if changed:
# valida sintaxe
try:
ast.parse(src)
except SyntaxError as e:
print("[patch] ERRO de sintaxe após patch:", e)
sys.exit(2)
write_text(TARGET, src)
print(f"PATCH_APPLIED={changed}")
# quick checks
for needle in [
"self.http: Optional[httpx.AsyncClient] = httpx.AsyncClient()",
"async def _post_ollama(",
"@cl.on_chat_end",
]:
print("[check] contains:", needle, "=>", needle in src)
if __name__ == "__main__":
main()