lg / tests /control /test_cloud.py
RDS777's picture
Distinguish cloud inference errors and retry truncated Qwen responses safely
4ec19d6 verified
Raw History Blame Contribute Delete
7.08 kB
import json
import os
import tempfile
import threading
import unittest
from types import SimpleNamespace
from unittest.mock import patch
from fastapi.testclient import TestClient
from lg_control.cloud import cloud_completion
from lg_control.local import run_agent
from lg_control.server import Settings, create_app
from lg_control.tools import Workspace
class FakeModel:
def __init__(self, parsed):
self.parsed = parsed
self.calls = []
def create_chat_completion(self, **kwargs):
self.calls.append(kwargs)
return {'choices': [{'finish_reason': 'stop', 'message': {'content': json.dumps(self.parsed)}}]}
class CloudTests(unittest.TestCase):
def settings(self):
return SimpleNamespace(origin='https://lg.example', worker_token='w'*32,
client_id='', client_secret='', password='', redirects=set(), oauth_enabled=False)
def test_qwen_structured_proposal(self):
model = FakeModel({'answer': 'Je liste les fichiers.', 'tool': 'list_files', 'arguments': {}})
brain = SimpleNamespace(llm=model, _lock=threading.Lock())
result = cloud_completion([{'role': 'user', 'content': 'Liste les fichiers'}], brain)
self.assertEqual(result['message']['tool_calls'][0]['function']['name'], 'list_files')
self.assertEqual(model.calls[0]['response_format']['type'], 'json_object')
self.assertTrue(brain._lock.acquire(blocking=False))
brain._lock.release()
def test_qwen_unknown_tool_rejected(self):
model = FakeModel({'answer': '', 'tool': 'delete_entire_disk', 'arguments': {}})
brain = SimpleNamespace(llm=model, _lock=threading.Lock())
with self.assertRaises(ValueError):
cloud_completion([], brain)
def test_cloud_loop_and_local_tools(self):
observed = []
def model(messages):
observed.append(messages)
if len(observed) == 1:
return {'message': {'role': 'assistant', 'content': '', 'tool_calls': [{'function': {'name': 'computer_status', 'arguments': {}}}]}}
self.assertEqual(messages[-1]['role'], 'tool')
self.assertIn('workspace', json.loads(messages[-1]['content']))
return {'message': {'role': 'assistant', 'content': 'PC accessible.'}}
with tempfile.TemporaryDirectory() as directory, patch.dict(os.environ, {'LG_ASSISTANT_ENABLED': '1'}), TestClient(create_app(self.settings(), model)) as client:
self.assertEqual(client.post('/control/chat', json={'messages': []}).status_code, 401)
headers = {'Authorization': 'Bearer '+'w'*32}
def chat(payload):
r = client.post('/control/chat', headers=headers, json={'messages': payload['messages']})
self.assertEqual(r.status_code, 200)
return r.json()
self.assertEqual(run_agent(Workspace(directory), 'Statut du PC', chat=chat), 'PC accessible.')
self.assertEqual(client.get('/authorize').status_code, 503)
self.assertEqual(len(observed), 2)
def test_cloud_disabled_by_default(self):
with patch.dict(os.environ, {'LG_ASSISTANT_ENABLED': '0'}), TestClient(create_app(self.settings())) as client:
response = client.post('/control/chat', headers={'Authorization': 'Bearer '+'w'*32}, json={'messages': [{'role': 'user', 'content': 'bonjour'}]})
self.assertEqual(response.status_code, 503)
def test_oauth_optional_for_cloud_mode(self):
with patch.dict(os.environ, {'LG_PUBLIC_URL': 'https://lg.example', 'LG_WORKER_TOKEN': 'w'*32}, clear=True):
cfg = Settings()
self.assertFalse(cfg.oauth_enabled)
def test_concurrent_inference_rejected(self):
release, entered = threading.Event(), threading.Event()
def model(messages):
entered.set()
release.wait(5)
return {'message': {'role': 'assistant', 'content': 'OK'}}
with patch.dict(os.environ, {'LG_ASSISTANT_ENABLED': '1'}), TestClient(create_app(self.settings(), model)) as client:
headers = {'Authorization': 'Bearer '+'w'*32}
body = {'messages': [{'role': 'user', 'content': 'bonjour'}]}
results = []
thread = threading.Thread(target=lambda: results.append(client.post('/control/chat', headers=headers, json=body)))
thread.start()
try:
self.assertTrue(entered.wait(3))
self.assertEqual(client.post('/control/chat', headers=headers, json=body).status_code, 409)
finally:
release.set()
thread.join(5)
self.assertEqual(results[0].status_code, 200)
class CloudFailureTests(unittest.TestCase):
def test_truncated_response_regenerates_before_any_tool(self):
class Model:
count = 0
def create_chat_completion(self, **kwargs):
self.count += 1
if self.count == 1:
return {'choices': [{'finish_reason': 'length', 'message': {'content': '{"tool":"write_file"'}}]}
return {'choices': [{'finish_reason': 'stop', 'message': {'content': json.dumps({'answer': 'Structure expliquee.', 'tool': '', 'arguments': {}})}}]}
model = Model()
result = cloud_completion([], SimpleNamespace(llm=model, _lock=threading.Lock()))
self.assertEqual(model.count, 2)
self.assertNotIn('tool_calls', result['message'])
def test_context_and_truncation_have_distinct_errors(self):
from lg_control.cloud import CloudCompletionError
class Model:
def create_chat_completion(self, **kwargs):
raise ValueError('Requested tokens exceed context window')
with self.assertRaises(CloudCompletionError) as caught:
cloud_completion([], SimpleNamespace(llm=Model(), _lock=threading.Lock()))
self.assertEqual(caught.exception.code, 'context_exceeded')
class Truncated:
def create_chat_completion(self, **kwargs):
return {'choices': [{'finish_reason': 'length'}]}
with self.assertRaises(CloudCompletionError) as caught:
cloud_completion([], SimpleNamespace(llm=Truncated(), _lock=threading.Lock()))
self.assertEqual(caught.exception.code, 'response_truncated')
def test_http_preserves_failure_code(self):
from lg_control.cloud import CloudCompletionError
def backend(messages):
raise CloudCompletionError('response_truncated', 'Truncated')
cfg = CloudTests().settings()
with patch.dict(os.environ, {'LG_ASSISTANT_ENABLED': '1'}), TestClient(create_app(cfg, backend)) as client:
response = client.post('/control/chat', headers={'Authorization': 'Bearer '+'w'*32}, json={'messages': [{'role': 'user', 'content': 'test'}]})
self.assertEqual(response.status_code, 422)
self.assertEqual(response.json()['detail']['code'], 'response_truncated')
if __name__ == '__main__':
unittest.main()