@@ -0,0 +1,18 @@ +# Generated by Django 5.0.14 on 2025-10-10 15:46 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('ml_model', '0059_alter_deployment_runner_import_path_and_more'), + ] + + operations = [ + migrations.AlterField( + model_name='paymentrule', + name='cost', + field=models.DecimalField(decimal_places=7, help_text='In RUB, per specified strategy', max_digits=10, verbose_name='Cost'), + ), + ] @@ -1,4 +1,4 @@ -from ml_model.runners.dummy import DummyImageRunner, DummyTextRunner +from ml_model.runners.dummy import DummyImageRunner, DummyTextRunner, DummyVideoRunner from ml_model.runners.falai import FalAIRunner from ml_model.runners.openai import OpenAIGPTRunner, OpenAIResponseRunner, GPTImageRunner from ml_model.runners.openrouter import OpenrouterRunner @@ -21,4 +21,5 @@ __all__ = [ 'FalAIRunner', 'DummyTextRunner', 'DummyImageRunner', + 'DummyVideoRunner', ] @@ -69,10 +69,13 @@ class DummyImageRunner(BaseRunner): ) -> Generator[str, Any, None]: url = parameters['url'] with httpx.Client() as client: - content = client.get(url).text + content = str(client.get(url).text.encode('utf-8')) rates = parameters['ttpr'] while content: rate = rates[random.randint(0, len(rates) - 1)] chunk, content = content[:rate], content[rate:] yield chunk + + +class DummyVideoRunner(DummyImageRunner): ... \ No newline at end of file @@ -106,6 +106,7 @@ class ReplicateTextRunner(ReplicateBaseRunner): if file and isinstance(file, BytesIO): kind = filetype.guess(file.read(20)) + file.seek(0) format = 'jpeg' if kind.extension == 'jpg' else kind.extension if format in ('jpeg', 'png'): mime = kind.mime if kind else 'application/octet-stream' @@ -145,11 +146,11 @@ class ReplicateImageRunner(ReplicateBaseRunner): payload = {prompt_key: content, **parameters} if file: kind = filetype.guess(file.read(20)) + file.seek(0) format = 'jpeg' if kind.extension == 'jpg' else kind.extension if format in ('jpeg', 'png'): mime = kind.mime if kind else 'application/octet-stream' payload[file_key] = f'data:{mime};base64,{base64.b64encode(file.read()).decode("utf-8")}' - return payload @classmethod @@ -159,9 +160,51 @@ class ReplicateImageRunner(ReplicateBaseRunner): return chunk -class ReplicateVideoRunner(BaseRunner): +class ReplicateVideoRunner(ReplicateImageRunner): @classmethod - def generate(cls, content=None, file=None, parameters={}, history=[], scrape_results=[]): ... + def generate(cls, content=None, file=None, parameters={}, history=[], scrape_results=[]): + model_key = parameters.pop('model_key', 'model') + model = parameters.pop(model_key) + model_owner = parameters.pop('model_owner') + official = model and model_owner + + payload = {} + if not official: + payload.update({'version': model}) + + payload['input'] = cls.compile_input( + content=content, + file=file, + parameters=parameters, + history=history, + scrape_results=scrape_results, + ) + + with httpx.Client( + base_url='https://api.replicate.com/v1', + headers={ + 'Authorization': f'Bearer {settings.REPLICATE_API_KEY}', + 'Content-Type': 'application/json', + }, + timeout=None, + ) as client: + resp = client.post( + f'/models/{model_owner}/{model}/predictions' if official else '/predictions', + json=payload + ) + data = resp.json() + while True: + resp = client.get(f"/predictions/{data['id']}") + result = resp.json() + logger.info(result) + if result['status'] in ('succeeded', 'failed'): + break + if result['status'] == 'failed': + raise Exception('Model failed generation') + stream_url = result['output'] + with client.stream('GET', stream_url) as stream: + for chunk in stream.iter_bytes(512 * 1024): + yield base64.b64encode(chunk).decode('utf-8') class ReplicateAudioRunner(BaseRunner): @@ -259,7 +259,7 @@ class PaymentRule(models.Model): cost = models.DecimalField( max_digits=10, - decimal_places=8, + decimal_places=7, verbose_name=_('Cost'), help_text=_('In RUB, per specified strategy'), )