Download app/tests/test_endurance45.py from OpenTARS/openTARS: direct link, hf CLI and curl.
- Browser
- Download file 20.1 kB
-
https://huggingface.co/OpenTARS/openTARS/resolve/main/app/tests/test_endurance45.py
- Command line
-
hf download hf://OpenTARS/openTARS/app/tests/test_endurance45.py
-
curl -L -o test_endurance45.py https://huggingface.co/OpenTARS/openTARS/resolve/main/app/tests/test_endurance45.py
20.1 kB
| """4.6 Endurance: o Cooper (visão da tela), WSL e Linux sem interface gráfica. | |
| Tudo numa pasta pessoal falsa; nenhum modelo de verdade (o Ollama é simulado).""" | |
| import base64 | |
| import builtins | |
| import io | |
| import json | |
| import os | |
| import sys | |
| import tempfile | |
| from pathlib import Path | |
| CASA = tempfile.mkdtemp(prefix="opentars-casa-") | |
| os.environ["HOME"] = CASA | |
| for var, sub in (("XDG_CONFIG_HOME", ".config"), ("XDG_DATA_HOME", ".local/share"), ("XDG_CACHE_HOME", ".cache")): | |
| os.environ[var] = os.path.join(CASA, sub) | |
| os.environ["TARS_ARQUIVO_SESSOES"] = os.path.join(CASA, "sessoes.json") | |
| from _util import carregar_tars # noqa: E402 | |
| tars = carregar_tars() | |
| amb, coo = tars.ambiente, tars.visao_lib | |
| from PIL import Image # noqa: E402 | |
| PASTA = Path(tempfile.mkdtemp(prefix="opentars-anexos-")) | |
| # ------------------------------------------------------------ | |
| # 1. Versão | |
| # ------------------------------------------------------------ | |
| assert tars.VERSAO == "5.2" and tars.CODINOME == "Ranger" and tars.VERSAO_EXIBIDA == "5.2 Ranger" | |
| assert tars.REVISAO >= 2026100401 | |
| print("OK: versão 5.2 Ranger") | |
| # ------------------------------------------------------------ | |
| # 2. Ambiente: WSL, sem interface, caminhos do Windows | |
| # ------------------------------------------------------------ | |
| def ler_falso(conteudos): | |
| return lambda caminho: conteudos.get(str(caminho), "") | |
| wsl2 = ler_falso({"/proc/sys/kernel/osrelease": "5.15.153.1-microsoft-standard-WSL2", "/proc/version": "Linux ... microsoft"}) | |
| wsl1 = ler_falso({"/proc/sys/kernel/osrelease": "4.4.0-19041-Microsoft", "/proc/version": "Linux ... Microsoft"}) | |
| puro = ler_falso({"/proc/sys/kernel/osrelease": "6.8.0-45-generic", "/proc/version": "Linux version 6.8.0 (buildd@ubuntu)"}) | |
| assert amb.versao_wsl({}, wsl2) == 2 and amb.versao_wsl({}, wsl1) == 1 and amb.versao_wsl({}, puro) is None | |
| assert amb.versao_wsl({"WSL_DISTRO_NAME": "Ubuntu"}, puro) == 1 # a variável basta | |
| assert amb.e_wsl({"WSL_INTEROP": "/run/WSL/1_interop"}, puro) and not amb.e_wsl({}, puro) | |
| d = amb.detectar({"DISPLAY": ":0"}, puro) | |
| assert d["tela"] and not d["sem_interface"] and d["wsl"] is None and d["rotulo"] == "desktop" | |
| d = amb.detectar({}, puro) | |
| assert d["sem_interface"] and not d["tela"] and "no graphical session" in d["rotulo"] | |
| d = amb.detectar({"SSH_CONNECTION": "1 2 3 4"}, puro) | |
| assert d["sem_interface"] and d["ssh"] and "SSH" in d["rotulo"] | |
| d = amb.detectar({}, wsl2) | |
| assert d["sem_interface"] and d["wsl"] == 2 and d["rotulo"].startswith("WSL2") | |
| d = amb.detectar({"DISPLAY": ":0", "WAYLAND_DISPLAY": "wayland-0"}, wsl2) | |
| assert d["wslg"] and not d["sem_interface"] and d["rotulo"] == "WSL2 with WSLg", d | |
| d = amb.detectar({"DISPLAY": ":0", "TARS_SEM_INTERFACE": "1"}, puro) # forçado | |
| assert d["sem_interface"] and amb.forcado_sem_interface({"TARS_SEM_INTERFACE": "1"}) | |
| assert amb.caminho_do_windows("C:\\Users\\ana\\Meus Docs\\a b.txt") == "/mnt/c/Users/ana/Meus Docs/a b.txt" | |
| assert amb.caminho_do_windows("d:/dados") == "/mnt/d/dados" and amb.caminho_do_windows("D:\\") == "/mnt/d" | |
| assert amb.caminho_do_windows("\\\\wsl$\\Ubuntu\\home\\ana\\x.txt") == "/home/ana/x.txt" | |
| assert amb.caminho_do_windows("\\\\wsl.localhost\\Ubuntu\\tmp") == "/tmp" | |
| assert amb.caminho_do_windows("/home/ana/x") == "/home/ana/x" and not amb.parece_caminho_windows("/home/ana/x") | |
| assert amb.parece_caminho_windows("C:\\x") | |
| route = ("Iface\tDestination\tGateway\tFlags\nrmnet0\t00000000\t0120A8C0\t0003\nrmnet0\t0020A8C0\t00000000\t0001\n") | |
| assert amb.gateway_padrao(route) == "192.168.32.1", amb.gateway_padrao(route) | |
| assert amb.servidor_dns("# x\nnameserver 192.168.32.1\nnameserver 8.8.8.8\n") == "192.168.32.1" | |
| assert amb.servidor_dns("nameserver 127.0.0.53\n") == "127.0.0.53" | |
| cands = amb.candidatos_ollama_wsl(ler=ler_falso({"/etc/resolv.conf": "nameserver 172.20.0.1\n", "/proc/net/route": route})) | |
| assert cands == ["http://172.20.0.1:11434", "http://192.168.32.1:11434"], cands | |
| assert amb.candidatos_ollama_wsl(ler=ler_falso({"/etc/resolv.conf": "nameserver 127.0.0.53\n"})) == [] | |
| assert amb.abridor_de_links_do_windows(existe=lambda p: p == "explorer.exe") == ["explorer.exe"] | |
| assert amb.abridor_de_links_do_windows(existe=lambda p: True) == ["wslview"] | |
| assert amb.abridor_de_links_do_windows(existe=lambda p: False) is None | |
| print("OK: ambiente: WSL1/2, WSLg, sem interface, SSH, caminhos do Windows e IP do Ollama do Windows") | |
| # ------------------------------------------------------------ | |
| # 3. Cooper: nome, perguntas, respostas, milésimos -> pixels | |
| # ------------------------------------------------------------ | |
| assert coo.MODELO_COOPER == "ProjectOpenTARS/cooper:1.2" # só para reconhecer um Cooper que já esteja instalado | |
| assert coo.COMANDO_INSTALAR == "ollama pull ProjectOpenTARS/Gargantua-2", "18/10: quem olha a tela é o Gargantua 2" | |
| assert coo.achar_instalado(["qwen3:4b", "ProjectOpenTARS/cooper:1.2", "ProjectOpenTARS/Gargantua-2:lite", "ProjectOpenTARS/Gargantua-2:latest"]) == "ProjectOpenTARS/Gargantua-2:latest" | |
| assert coo.achar_instalado(["ProjectOpenTARS/cooper:1.2", "ProjectOpenTARS/Gargantua-2:lite"]) == "ProjectOpenTARS/Gargantua-2:lite", "o Lite novo (com telas) vence o Cooper antigo" | |
| assert coo.achar_instalado(["ProjectOpenTARS/Gargantua-2:lite", "ProjectOpenTARS/Gargantua-2:latest"]) == "ProjectOpenTARS/Gargantua-2:latest", "o 2B vem antes do Lite" | |
| assert coo.achar_instalado(["ProjectOpenTARS/Gargantua:2.0-lite"]) is None, "o Lite 2.0 antigo não foi treinado com telas" | |
| assert coo.achar_instalado(["ProjectOpenTARS/cooper:1.2", "ProjectOpenTARS/Gargantua:2.0-lite"]) == "ProjectOpenTARS/cooper:1.2" | |
| os.environ["TARS_VISAO_NO_LITE"] = "1" | |
| assert coo.achar_instalado(["ProjectOpenTARS/Gargantua:2.0-lite"]) == "ProjectOpenTARS/Gargantua:2.0-lite", "liberado à mão" | |
| os.environ.pop("TARS_VISAO_NO_LITE") | |
| assert coo.achar_instalado(["ProjectOpenTARS/Gargantua:1.2-lite", "ProjectOpenTARS/Gargantua:latest"]) is None, "o Gargantua 1.x não enxerga" | |
| for tag in ("ProjectOpenTARS/cooper:latest", "cooper", "cooper-1.0:latest", "projectopentars/Cooper"): | |
| assert coo.e_cooper(tag), tag | |
| for tag in ("gargantua:latest", "qwen3-vl:2b", "ProjectOpenTARS/Gargantua", "", None): | |
| assert not coo.e_cooper(tag), tag | |
| assert coo.achar_instalado(["qwen3:4b", "cooper-1.0:latest", "ProjectOpenTARS/cooper:latest"]) == "ProjectOpenTARS/cooper:latest" | |
| assert coo.achar_instalado(["ProjectOpenTARS/cooper:latest", "ProjectOpenTARS/cooper:1.2"]) == "ProjectOpenTARS/cooper:1.2", "o 1.2 vence o antigo" | |
| assert coo.achar_instalado(["cooper-1.0:latest"]) == "cooper-1.0:latest" and coo.achar_instalado(["qwen3:4b"]) is None | |
| assert coo.ligado({}) and not coo.ligado({"TARS_COOPER": "0"}) and not coo.ligado({"TARS_COOPER": "off"}) | |
| assert coo.tipo_do_alvo("the Save button") == ("button", "Save") | |
| assert coo.tipo_do_alvo("botão Salvar") == ("button", "Salvar") | |
| assert coo.tipo_do_alvo("Dark mode checkbox") == ("checkbox", "Dark mode") | |
| assert coo.tipo_do_alvo("Address bar field") == ("field", "Address bar") | |
| assert coo.tipo_do_alvo("campo Nome") == ("field", "Nome") | |
| assert coo.tipo_do_alvo("close icon") == ("icon", "close") | |
| assert coo.tipo_do_alvo("Settings") == ("button", "Settings") # sem pista: botão | |
| assert coo.tipo_do_alvo("menu Arquivo") == ("menu", "Arquivo") | |
| assert coo.pergunta_localizar("the Save button") == ( | |
| 'Where is the button "Save" on the screen? Answer with its center as JSON {"x": 0-1000, "y": 0-1000} ' | |
| '(thousandths of the image width and height). If it is not on the screen, answer {"found": false}.') | |
| assert ' in the window "Notes"' in coo.pergunta_localizar("Save", janela="Notes") | |
| assert coo.pergunta_descrever("use the Minimize icon").startswith( | |
| 'Describe this screenshot for an assistant that must operate the computer to do: "use the Minimize icon".') | |
| assert coo.ler_ponto('{"x": 512, "y": 340}') == ("achou", 512, 340) | |
| assert coo.ler_ponto('```json\n{"x": 0, "y": 1000}\n```') == ("achou", 0, 1000) | |
| assert coo.ler_ponto('Here: {"x": 12.5, "y": 99}') == ("achou", 12.5, 99) | |
| assert coo.ler_ponto('{"found": false}') == ("ausente", None, None) | |
| assert coo.ler_ponto("it is at (512, 340)") == ("achou", 512, 340) | |
| for ruim in ("", None, "não sei", '{"x": 1200, "y": 5}', '{"x": "a", "y": 2}', "(5000, 40)", '{"y": 5}'): | |
| assert coo.ler_ponto(ruim) is None, ruim | |
| assert coo.para_pixels(500, 500, 1920, 1080) == (960, 540) | |
| assert coo.para_pixels(1000, 1000, 1920, 1080) == (1919, 1079) and coo.para_pixels(0, 0, 800, 600) == (0, 0) | |
| assert coo.para_pixels(333, 666, 1280, 720) == (426, 480) | |
| desc = 'Window "Notes". Button "Save" at (500, 500). Field "Name" at (250, 100). Odd (2000, 5).' | |
| assert coo.descricao_em_pixels(desc, 1000, 500) == 'Window "Notes". Button "Save" at (500, 250). Field "Name" at (250, 50). Odd (2000, 5).' | |
| assert "(960, 540)" in coo.descricao_em_pixels('Save {"x": 500, "y": 500}', 1920, 1080) | |
| print("OK: Cooper: perguntas no formato do treino, leitura das respostas e milésimos -> pixels") | |
| # ------------------------------------------------------------ | |
| # 4. Cooper no núcleo: clique, descrição, e nunca como IA de conversa | |
| # ------------------------------------------------------------ | |
| COOPER = "ProjectOpenTARS/cooper:latest" | |
| CATALOGO = { | |
| "qwen3:4b": {"tag": "qwen3:4b", "familia": "qwen3", "categoria": "geral", "parametros_b": 4.0, "capacidades": {"tools"}, | |
| "capacidades_conhecidas": True, "vram_estimado_gb": 3.0}, | |
| COOPER: {"tag": COOPER, "familia": "qwen3vl", "categoria": "visao", "parametros_b": 2.0, "capacidades": {"vision", "completion"}, | |
| "capacidades_conhecidas": True, "vram_estimado_gb": 2.0}, | |
| "gemma3:4b": {"tag": "gemma3:4b", "familia": "gemma3", "categoria": "visao", "parametros_b": 4.0, | |
| "capacidades": {"vision", "completion"}, "capacidades_conhecidas": True, "vram_estimado_gb": 3.0}, | |
| } | |
| tars.catalogar_modelos = lambda forcar=False: CATALOGO | |
| assert tars.visao_instalada() == COOPER | |
| assert COOPER in tars.MODELOS_AJUDANTES and "cooper" in tars.MODELOS_AJUDANTES and "qwen3:4b" not in tars.MODELOS_AJUDANTES | |
| assert "qwen2.5:0.5b" in tars.MODELOS_AJUDANTES | |
| assert COOPER not in tars.construir_cadeia_fallback("acao"), "o Cooper não conversa" | |
| os.environ["TARS_COOPER"] = "0" | |
| assert tars.visao_instalada() is None | |
| del os.environ["TARS_COOPER"] | |
| perguntas = [] | |
| respostas = {"valor": '{"x": 250, "y": 500}'} | |
| def chat_falso(modelo, prompt, **kw): | |
| perguntas.append((modelo, prompt, kw)) | |
| return respostas["valor"] | |
| tars._chat_unico = chat_falso | |
| img = Image.new("RGB", (1200, 800), "white") | |
| ponto, tag = tars.localizar_com_visao("the Save button", img) | |
| assert ponto == (300, 400) and tag == COOPER, (ponto, tag) | |
| modelo, prompt, kw = perguntas[-1] | |
| assert modelo == COOPER and prompt.startswith('Where is the button "Save"') and kw["temperatura"] == 0.0 and len(kw["imagens"]) == 1 | |
| assert Image.open(io.BytesIO(base64.b64decode(kw["imagens"][0]))).size == (1200, 800) | |
| respostas["valor"] = '{"found": false}' | |
| assert tars.localizar_com_visao("Save", img) == ("ausente", COOPER) | |
| respostas["valor"] = "não entendi" | |
| assert tars.localizar_com_visao("Save", img) == (None, None) | |
| respostas["valor"] = None | |
| assert tars.localizar_com_visao("Save", img) == (None, None) | |
| respostas["valor"] = '{"x": 250, "y": 500}' | |
| assert tars.mirar_com_visao("Save", img) == (300, 400, "located 'Save' by looking at the screen") | |
| assert tars.mirar_com_visao("Save", img, perto_de=(310, 410)) is not None | |
| assert tars.mirar_com_visao("Save", img, perto_de=(900, 700)) is None, "longe do x/y da IA: a grade confere" | |
| # descrição da tela: posições do Cooper já em pixels da imagem enviada | |
| buffer = io.BytesIO() | |
| Image.new("RGB", (1000, 500), "white").save(buffer, format="PNG") | |
| b64 = base64.b64encode(buffer.getvalue()).decode() | |
| respostas["valor"] = 'Window "Notes". Button "Save" at (500, 500).' | |
| texto, quem = tars._descrever_print(b64, "salvar", "qwen3:4b") | |
| assert quem == COOPER and "(500, 250)" in texto and "1000x500" in texto, texto | |
| assert perguntas[-1][1].startswith("Describe this screenshot for an assistant") | |
| respostas["valor"] = "A calm window with some buttons." # sem posição: ainda vale, avisando | |
| texto, quem = tars._descrever_print(b64, "salvar", "qwen3:4b") | |
| assert quem == COOPER and "without positions" in texto | |
| respostas["valor"] = None # Cooper sem resposta: nada de modelo de reserva | |
| tars._visao_estado.update(pausado_ate=0.0, quente=False, erro="") | |
| perguntas.clear() | |
| assert tars._descrever_print(b64, "salvar", "qwen3:4b") == (None, None) and {p[0] for p in perguntas} == {COOPER}, "só o Cooper é consultado" | |
| print("OK: Cooper acha e descreve; sem resposta, só o motivo (nenhum outro modelo de visão); nunca conversa") | |
| # ------------------------------------------------------------ | |
| # 5. Sem interface gráfica | |
| # ------------------------------------------------------------ | |
| tars.SEM_INTERFACE = True | |
| tars.AMBIENTE.update(sem_interface=True, tela=False) | |
| nomes = {f["function"]["name"] for f in tars.ferramentas_do_pedido("acao", "abra o firefox e clique em entrar")} | |
| assert nomes and not (nomes & tars.FERRAMENTAS_SO_COM_TELA), nomes & tars.FERRAMENTAS_SO_COM_TELA | |
| nomes_tec = {f["function"]["name"] for f in tars.ferramentas_do_pedido("tecnico", "quanta memória livre eu tenho")} | |
| assert "execute_terminal" in nomes_tec and "take_screenshot" not in nomes_tec | |
| for ferramenta in ("take_screenshot", "click_mouse", "open_application", "list_windows", "type_text", "hotkey"): | |
| r = tars.executar_ferramenta(ferramenta, {"app": "calculadora", "text": "x", "keys": ["ctrl", "c"]}) | |
| assert not r["sucesso"] and "no graphical session" in r["mensagem"] and ferramenta in r["mensagem"], (ferramenta, r) | |
| r = tars.executar_ferramenta("execute_terminal", {"command": "echo ola"}) | |
| assert r["sucesso"] and "ola" in json.dumps(r), r | |
| r = tars.executar_ferramenta("list_files", {"path": str(PASTA)}) | |
| assert r["sucesso"], r | |
| r = tars.abrir_site("https://example.com") | |
| assert not r["sucesso"] and "https://example.com" in r["mensagem"] and "link" in r["mensagem"], r | |
| r = tars.pesquisar_web("gatos") | |
| assert not r["sucesso"] and "google.com/search?q=gatos" in r["mensagem"], r | |
| # no WSL sem tela, o link abre no navegador do Windows | |
| chamadas = [] | |
| tars.EM_WSL = True | |
| tars.ambiente.abridor_de_links_do_windows = lambda existe=None: ["wslview"] | |
| tars.subprocess.Popen = lambda cmd, **kw: chamadas.append(cmd) | |
| r = tars.abrir_site("https://example.com") | |
| assert r["sucesso"] and chamadas == [["wslview", "https://example.com"]], (r, chamadas) | |
| tars.EM_WSL = False | |
| avisos = tars.avisos_de_ambiente() | |
| assert any("tery" not in a and ("Sem sessão gráfica" in a or "Sem sess" in a) for a in avisos), avisos | |
| assert not any("mouse" in a.lower() for a in avisos), avisos | |
| tars._contexto_sistema_cache.clear() | |
| assert "NO graphical session" in tars.contexto_sistema() | |
| tars._contexto_sistema_cache.clear() | |
| tars.EM_WSL = True | |
| assert "Running inside WSL" in tars.contexto_sistema() | |
| tars.EM_WSL = False | |
| tars._contexto_sistema_cache.clear() | |
| # com tela, tudo volta | |
| tars.SEM_INTERFACE = False | |
| tars.AMBIENTE.update(sem_interface=False, tela=True) | |
| nomes = {f["function"]["name"] for f in tars.ferramentas_do_pedido("busca", "pesquise gatos")} | |
| assert "take_screenshot" in nomes or "click_element" in nomes, nomes | |
| assert "NO graphical session" not in tars.contexto_sistema() | |
| print("OK: sem interface: ferramentas de tela somem e, se chamadas, explicam; links vão pro navegador do Windows no WSL") | |
| # ------------------------------------------------------------ | |
| # 8. Conversa no terminal | |
| # ------------------------------------------------------------ | |
| pedidos = [] | |
| tars.processar_mensagem = lambda texto: pedidos.append(texto) or "ok" | |
| tars.iniciar_conversa = lambda: 0 | |
| tars.encerrar_todas_ias = lambda *a, **k: None | |
| tars.avisos_de_ambiente = lambda: ["aviso de teste"] | |
| entradas = [] | |
| builtins_input = builtins.input | |
| builtins.input = lambda prompt="": entradas.pop(0) if entradas else (_ for _ in ()).throw(EOFError()) | |
| saida = io.StringIO() | |
| real_stdout = sys.stdout | |
| try: | |
| sys.stdout = saida | |
| entradas[:] = ["/ajuda", "resuma isto", "/sair", "isto não deveria ser lido"] | |
| assert tars.modo_chat_terminal() == 0 | |
| finally: | |
| sys.stdout = real_stdout | |
| builtins.input = builtins_input | |
| assert pedidos == ["resuma isto"], pedidos | |
| assert "aviso de teste" in saida.getvalue() | |
| assert entradas == ["isto não deveria ser lido"], "o /sair encerra o laço" | |
| entradas[:] = ["pergunta pelo pipe"] | |
| pedidos.clear() | |
| builtins.input = lambda prompt="": entradas.pop(0) if entradas else (_ for _ in ()).throw(EOFError()) | |
| try: | |
| sys.stdout = io.StringIO() | |
| assert tars.modo_chat_terminal() == 0 | |
| finally: | |
| sys.stdout = real_stdout | |
| builtins.input = builtins_input | |
| assert pedidos == ["pergunta pelo pipe"] | |
| print("OK: opentars --chat: pedido, /ajuda, /sair e entrada por pipe") | |
| # --cooper (descontinuado em 18/10: só avisa e diz como instalar o Gargantua 2; não baixa nada) | |
| rodou = [] | |
| tars.subprocess.run = lambda cmd, **kw: rodou.append(cmd) or type("R", (), {"returncode": 0})() | |
| saida = io.StringIO() | |
| try: | |
| sys.stdout = saida | |
| assert tars.comando_cooper() == 0 | |
| finally: | |
| sys.stdout = real_stdout | |
| assert not rodou and "ollama pull ProjectOpenTARS/Gargantua-2" in saida.getvalue() and "descontinuado" in saida.getvalue(), saida.getvalue() | |
| print("OK: opentars --cooper avisa que o Cooper foi descontinuado e aponta o Gargantua 2") | |
| # ------------------------------------------------------------ | |
| # 9. Textos nos dois idiomas | |
| # ------------------------------------------------------------ | |
| base = Path(tars.__file__).parent / "idiomas" | |
| en, pt = json.loads((base / "en.json").read_text()), json.loads((base / "pt_BR.json").read_text()) | |
| assert set(en) == set(pt), sorted(set(en) ^ set(pt)) | |
| for chave in ("chat.ajuda", "visao.nao_instalado", "cooper.descontinuado", "diag.wsl_sem_ollama", | |
| "term.onde_conversar_sem_interface", "aviso.modo_sem_interface"): | |
| assert en[chave] and pt[chave], chave | |
| assert "--chat" in en["term.ajuda"] and "--cooper" not in pt["term.ajuda"] | |
| print("OK: textos novos nos dois idiomas") | |
| print("\nTUDO OK.") | |
| # 4.5.1: Cooper exclusivo (nada de Bonsai/LM Studio de reserva) | |
| os.environ.pop("TARS_COOPER", None) | |
| tars.visao_instalada = lambda: COOPER | |
| _envio = [] | |
| tars._chat_unico = lambda modelo, prompt, **kw: (_envio.append(modelo), "A window with a Save button")[1] | |
| img2 = Image.new("RGB", (1200, 800), "white") | |
| buf = io.BytesIO(); img2.save(buf, format="PNG") | |
| import base64 as _b64 | |
| b64 = _b64.b64encode(buf.getvalue()).decode() | |
| texto, tag = tars.descrever_com_visao(b64, "salvar") | |
| assert tag == COOPER and "Save button" in texto and set(_envio) == {COOPER}, "descrição sem posições também vale (exclusivo)" | |
| print("OK: Cooper exclusivo: nenhum outro modelo de visão é chamado") | |
| # 4.5.1: Cooper lento/fora do ar não trava os passos seguintes | |
| _chamadas = [] | |
| def _lento(modelo, prompt, **kw): | |
| _chamadas.append(kw["timeout"]) | |
| tars._ultimo_erro["texto"] = "Read timed out" | |
| return None | |
| tars._chat_unico = _lento | |
| tars.visao_instalada = lambda: COOPER | |
| tars._visao_estado.update(pausado_ate=0.0, quente=False, erro="") | |
| assert tars.perguntar_visao(COOPER, "q", imagem_b64=b64) is None | |
| assert _chamadas == [tars.TIMEOUT_VISAO_FRIO], "prazo estourado: sem segunda tentativa" | |
| assert tars.visao_disponivel() is None, "depois de estourar o prazo o Cooper descansa" | |
| assert tars.motivo_da_falha_da_visao() == "Read timed out" | |
| assert tars.descrever_com_visao(b64, "x") == (None, None) and len(_chamadas) == 1, "em pausa: nem pergunta" | |
| tars._visao_estado.update(pausado_ate=0.0) | |
| _chamadas.clear() | |
| def _erro_rapido(modelo, prompt, **kw): | |
| _chamadas.append(kw["timeout"]) | |
| tars._ultimo_erro["texto"] = "HTTP 500 model failed to load" | |
| return None | |
| tars._chat_unico = _erro_rapido | |
| assert tars.perguntar_visao(COOPER, "q", imagem_b64=b64) is None and len(_chamadas) == 2, "erro rápido: tenta de novo uma vez" | |
| assert tars.visao_disponivel() == COOPER, "erro rápido não põe em pausa" | |
| tars._visao_estado.update(pausado_ate=0.0, quente=False, erro="") | |
| print("OK: Cooper lento ganha pausa, erro rápido tenta duas vezes, e o motivo fica registrado") | |