from __future__ import annotations import json import typing import ollama from .. import operator, entities, errors @operator.operator_class( name='ollama', help='ollama平台操作', usage='!ollama\n!ollama show <模型名>\n!ollama pull <模型名>\n!ollama del <模型名>', ) class OllamaOperator(operator.CommandOperator): async def execute( self, context: entities.ExecuteContext ) -> typing.AsyncGenerator[entities.CommandReturn, None]: try: content: str = '模型列表:\n' model_list: list = ollama.list().get('models', []) for model in model_list: content += f'名称: {model["name"]}\n' content += f'修改时间: {model["modified_at"]}\n' content += f'大小: {bytes_to_mb(model["size"])}MB\n\n' yield entities.CommandReturn(text=f'{content.strip()}') except ollama.ResponseError: yield entities.CommandReturn( error=errors.CommandError('无法获取模型列表,请确认 Ollama 服务正常') ) def bytes_to_mb(num_bytes): mb: float = num_bytes / 1024 / 1024 return format(mb, '.2f') @operator.operator_class( name='show', help='ollama模型详情', privilege=2, parent_class=OllamaOperator ) class OllamaShowOperator(operator.CommandOperator): async def execute( self, context: entities.ExecuteContext ) -> typing.AsyncGenerator[entities.CommandReturn, None]: content: str = '模型详情:\n' try: show: dict = ollama.show(model=context.crt_params[0]) model_info: dict = show.get('model_info', {}) ignore_show: str = 'too long to show...' for key in ['license', 'modelfile']: show[key] = ignore_show for key in [ 'tokenizer.chat_template.rag', 'tokenizer.chat_template.tool_use', ]: model_info[key] = ignore_show content += json.dumps(show, indent=4) yield entities.CommandReturn(text=content.strip()) except ollama.ResponseError: yield entities.CommandReturn( error=errors.CommandError('无法获取模型详情,请确认 Ollama 服务正常') ) @operator.operator_class( name='pull', help='ollama模型拉取', privilege=2, parent_class=OllamaOperator ) class OllamaPullOperator(operator.CommandOperator): async def execute( self, context: entities.ExecuteContext ) -> typing.AsyncGenerator[entities.CommandReturn, None]: try: model_list: list = ollama.list().get('models', []) if context.crt_params[0] in [model['name'] for model in model_list]: yield entities.CommandReturn(text='模型已存在') return except ollama.ResponseError: yield entities.CommandReturn( error=errors.CommandError('无法获取模型列表,请确认 Ollama 服务正常') ) return on_progress: bool = False progress_count: int = 0 try: for resp in ollama.pull(model=context.crt_params[0], stream=True): total: typing.Any = resp.get('total') if not on_progress: if total is not None: on_progress = True yield entities.CommandReturn(text=resp.get('status')) else: if total is None: on_progress = False completed: typing.Any = resp.get('completed') if isinstance(completed, int) and isinstance(total, int): percentage_completed = (completed / total) * 100 if percentage_completed > progress_count: progress_count += 10 yield entities.CommandReturn( text=f'下载进度: {completed}/{total} ({percentage_completed:.2f}%)' ) except ollama.ResponseError as e: yield entities.CommandReturn(text=f'拉取失败: {e.error}') @operator.operator_class( name='del', help='ollama模型删除', privilege=2, parent_class=OllamaOperator ) class OllamaDelOperator(operator.CommandOperator): async def execute( self, context: entities.ExecuteContext ) -> typing.AsyncGenerator[entities.CommandReturn, None]: try: ret: str = ollama.delete(model=context.crt_params[0])['status'] except ollama.ResponseError as e: ret = f'{e.error}' yield entities.CommandReturn(text=ret)