Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- # Универсальный анализатор кода и текста (Smart Analyzer)
- 📘 Полная документация и руководство пользователя
- **Дата:** 2026-05-06
- **Устройство:** Infinix X676C (Android 12, Termux)
- **Основная цель:** исследование безопасности Android-приложений, смысловой анализ исходного кода и текстов с автоматическим выбором лучшей доступной языковой модели.
- **Актуальная версия:** smart_analyze.py (CLI) + web_analyzer.py (веб-интерфейс)
- ***
- ## 1. Обзор системы
- Система состоит из двух компонентов:
- - **smart_analyze.py** – CLI-скрипт для анализа файлов/текста через командную строку
- - **web_analyzer.py** – веб-сервер с графическим интерфейсом (http://127.0.0.1:5000)
- **Общие возможности:**
- - Автоматический выбор backend (Groq → OpenRouter → Ollama → локальная модель)
- - 5 режимов: `audit`, `find_secrets`, `text_analysis`, `search`, `explain_code`
- - Работает офлайн с Qwen2.5-1.5B-Instruct через llama.cpp
- - Потоковый вывод без зависаний
- - Принудительный выбор backend и параметров генерации
- ***
- ## 2. Архитектура
- ### 2.1. Динамический выбор backend
- Приоритет проверки:
- 1. **Groq** (GROQ_API_KEY) – лучшая бесплатная модель по формуле (лимит/день × 0.7) + (качество × 0.3)
- 2. **OpenRouter** (OPENROUTER_API_KEY) – бесплатные модели (input_price=0)
- 3. **Ollama** (OLLAMA_BASE_URL) – локальный сервер
- 4. **Local** (llama.cpp) – fallback
- **Оценка качества:** размер модели + бонусы за семейства (Llama-4, Qwen3, DeepSeek-R1 и др.)
- ### 2.2. Локальный запуск (pty)
- - `pty.fork()` для потокового вывода
- - Флаги llama-completion: `-st -no-cnv --simple-io`
- - `select()` для чтения без блокировок
- ## 3. Установка
- ### 3.1. Требования
- ```
- pkg install python requests clang cmake ninja git flask
- ```
- ### 3.2. Сборка llama.cpp
- ```
- git clone https://github.com/ggerganov/llama.cpp
- cd llama.cpp
- cmake . -DLLAMA_CURL=OFF -DLLAMA_VULKAN=OFF -DLLAMA_OPENCL=OFF -B build -DCMAKE_BUILD_TYPE=Release
- cmake --build build --config Release -j4
- cp build/bin/llama-completion ~/.local/bin/
- ```
- ### 3.3. Модель
- ```
- mkdir ~/models
- # Скачайте qwen2.5-1.5b-instruct-q4_k_m.gguf в ~/models/
- ```
- ### 3.4. Переменные окружения (~/.bashrc)
- ```bash
- export GROQ_API_KEY="gsk_..."
- export OPENROUTER_API_KEY="sk-or-v1-..."
- export OLLAMA_BASE_URL="http://localhost:11434"
- export LOCAL_BIN="$HOME/.local/bin/llama-completion"
- export LOCAL_MODEL_PATH="$HOME/models/qwen2.5-1.5b-instruct-q4_k_m.gguf"
- export PATH="$HOME/bin:$HOME/.local/bin:$PATH"
- ```
- ### 3.5. Развертывание скриптов
- ```bash
- # CLI-скрипт
- cat > ~/bin/smart_analyze.py << 'EOF'
- #!/usr/bin/env python3
- """
- Универсальный анализатор кода/текста с динамическим выбором лучшей бесплатной модели.
- Версия без детектора повторов, с корректной обработкой завершения локальной модели (Errno 5).
- """
- import os, sys, re, argparse, pty, select, io, errno
- import requests
- from typing import Optional, List, Dict, Tuple
- BACKENDS_PRIORITY = ["groq", "openrouter", "ollama", "local"]
- LOCAL_BIN = os.getenv("LOCAL_BIN", os.path.expanduser("~/.local/bin/llama-completion"))
- LOCAL_MODEL = os.getenv("LOCAL_MODEL_PATH", os.path.expanduser("~/models/qwen2.5-1.5b-instruct-q4_k_m.gguf"))
- TIMEOUT = 10
- PROMPTS = {
- "audit": """Ты — эксперт по безопасности приложений. Проанализируй предоставленный исходный код.
- Найди все потенциальные уязвимости: SQL-инъекции, небезопасные криптоалгоритмы, хардкод секретов, утечки данных в логи, небезопасное хранение.
- Опиши каждую проблему кратко: место, тип уязвимости, возможную эксплуатацию.""",
- "find_secrets": """Извлеки из кода все потенциальные секреты: API-ключи, токены, криптографические ключи, пароли, хэши.""",
- "text_analysis": """Выполни смысловой анализ текста: ключевые темы, тональность, скрытые намерения.""",
- "search": """Найди в коде все места, удовлетворяющие запросу: "{query}". Объясни каждое.""",
- "explain_code": """Объясни, что делает следующий код и как он работает. Опиши алгоритм, назначение функций и возможные особенности реализации."""
- }
- def estimate_quality(name: str) -> float:
- score = 50.0
- m = re.search(r'(\d+\.?\d*)\s*[bB]', name.lower())
- if m: score += min(float(m.group(1)) * 1.5, 60)
- for s, bonus in [('llama-4',20), ('llama-3.3',18), ('qwen3',19), ('deepseek-r1',20), ('mixtral',15), ('mistral',15), ('gemma-4',17), ('nemotron',16), ('gpt-oss',18), ('ling',15)]:
- if s in name.lower(): score += bonus; break
- return min(score, 100.0)
- def fetch_groq_free_models(api_key):
- try:
- resp = requests.get("https://api.groq.com/openai/v1/models", headers={"Authorization": f"Bearer {api_key}"}, timeout=TIMEOUT)
- if resp.status_code!=200: return []
- models = []
- for m in resp.json().get('data', []):
- mid = m['id']
- if any(x in mid for x in ['whisper','guard','spec-decoding','tool-use']): continue
- rpm, rpd = (30,14400) if ('70b' in mid or 'deepseek' in mid) else (100,14400) if any(x in mid for x in ['8b','9b']) else (60,14400)
- q = estimate_quality(mid)
- models.append({"id":mid,"backend":"groq","rpm":rpm,"rpd":rpd,"quality":q,"priority": rpd*0.7 + q*0.3})
- models.sort(key=lambda x: x["priority"], reverse=True)
- return models
- except: return []
- def fetch_openrouter_free_models(api_key):
- try:
- resp = requests.get("https://openrouter.ai/api/v1/models", headers={"Authorization": f"Bearer {api_key}"}, timeout=TIMEOUT)
- if resp.status_code!=200: return []
- models = []
- for m in resp.json().get('data', []):
- if float(m.get('pricing',{}).get('input',0))==0.0:
- q = estimate_quality(m['id'])
- models.append({"id":m['id'],"backend":"openrouter","rpm":20,"rpd":50,"quality":q,"priority":50*0.7+q*0.3})
- models.sort(key=lambda x: x["priority"], reverse=True)
- return models
- except: return []
- def fetch_ollama_models(base_url, api_key=None):
- try:
- headers = {"Authorization": f"Bearer {api_key}"} if api_key else {}
- resp = requests.get(f"{base_url.rstrip('/')}/api/tags", headers=headers, timeout=TIMEOUT)
- if resp.status_code!=200: return []
- models = []
- for m in resp.json().get('models',[]):
- q = estimate_quality(m['name'])
- models.append({"id":m['name'],"backend":"ollama","quality":q,"priority":q})
- models.sort(key=lambda x: x["priority"], reverse=True)
- return models
- except: return []
- def select_best_model(force_backend=None):
- candidates = []
- if not force_backend or force_backend=="groq":
- key = os.getenv("GROQ_API_KEY")
- if key:
- lst = fetch_groq_free_models(key)
- if lst:
- candidates.append(lst[0])
- print(f"[+] Groq: {len(lst)} моделей, лучшая: {lst[0]['id']}", file=sys.stderr)
- if not force_backend or force_backend=="openrouter":
- key = os.getenv("OPENROUTER_API_KEY")
- if key:
- lst = fetch_openrouter_free_models(key)
- if lst:
- candidates.append(lst[0])
- print(f"[+] OpenRouter: {len(lst)} моделей, лучшая: {lst[0]['id']}", file=sys.stderr)
- if not force_backend or force_backend=="ollama":
- base = os.getenv("OLLAMA_BASE_URL","http://localhost:11434")
- key = os.getenv("OLLAMA_API_KEY")
- lst = fetch_ollama_models(base, key)
- if lst:
- candidates.append(lst[0])
- print(f"[+] Ollama: {len(lst)} моделей, лучшая: {lst[0]['id']}", file=sys.stderr)
- if not force_backend or force_backend=="local":
- if os.path.isfile(LOCAL_MODEL):
- q = estimate_quality(LOCAL_MODEL)
- candidates.append({"id":"local-model","backend":"local","priority":q})
- print(f"[+] Локальная модель: {LOCAL_MODEL}", file=sys.stderr)
- if not candidates:
- print("[x] Нет доступных моделей.", file=sys.stderr)
- sys.exit(1)
- candidates.sort(key=lambda x: x["priority"], reverse=True)
- best = candidates[0]
- params = {}
- if best["backend"]=="groq": params["api_key"]=os.getenv("GROQ_API_KEY")
- elif best["backend"]=="openrouter": params["api_key"]=os.getenv("OPENROUTER_API_KEY")
- elif best["backend"]=="ollama": params.update({"base_url":os.getenv("OLLAMA_BASE_URL","http://localhost:11434"),"api_key":os.getenv("OLLAMA_API_KEY")})
- print(f"[🏆] Выбрана {best['backend']}/{best['id']} (приоритет {best['priority']:.2f})", file=sys.stderr)
- return best["backend"], best["id"], params
- def query_groq(prompt, api_key, model, temp=0.15, max_tok=2048):
- r = requests.post("https://api.groq.com/openai/v1/chat/completions", headers={"Authorization":f"Bearer {api_key}","Content-Type":"application/json"}, json={"model":model,"messages":[{"role":"user","content":prompt}],"temperature":temp,"max_tokens":max_tok}, timeout=60)
- if r.status_code==429: raise RuntimeError("Groq quota exceeded")
- r.raise_for_status()
- return r.json()["choices"][0]["message"]["content"]
- def query_openrouter(prompt, api_key, model, temp=0.15, max_tok=2048):
- r = requests.post("https://openrouter.ai/api/v1/chat/completions", headers={"Authorization":f"Bearer {api_key}","Content-Type":"application/json","HTTP-Referer":"http://localhost","X-Title":"SmartAnalyzer"}, json={"model":model,"messages":[{"role":"user","content":prompt}],"temperature":temp,"max_tokens":max_tok}, timeout=60)
- if r.status_code==429: raise RuntimeError("OpenRouter quota exceeded")
- r.raise_for_status()
- return r.json()["choices"][0]["message"]["content"]
- def query_ollama(prompt, base_url, model, api_key=None, temp=0.15, max_tok=2048):
- headers = {"Content-Type":"application/json"}
- if api_key: headers["Authorization"]=f"Bearer {api_key}"
- r = requests.post(f"{base_url.rstrip('/')}/api/chat", headers=headers, json={"model":model,"messages":[{"role":"user","content":prompt}],"stream":False,"options":{"temperature":temp,"num_predict":max_tok}}, timeout=120)
- if r.status_code==429: raise RuntimeError("Ollama quota exceeded")
- r.raise_for_status()
- return r.json()["message"]["content"]
- def query_local(prompt: str, model_path=LOCAL_MODEL, ctx_size=2048, temp=0.7, max_tokens=256) -> str:
- master_fd, slave_fd = pty.openpty()
- cmd = [
- LOCAL_BIN, "-m", model_path,
- "-t", "4", "-c", str(ctx_size),
- "--temp", str(temp), "--repeat-penalty", "1.1",
- "-n", str(max_tokens), "--no-display-prompt",
- "-no-cnv", "--simple-io", "-st",
- "-p", prompt
- ]
- env = os.environ.copy()
- env["LLAMA_LOG_VERBOSITY"] = "0"
- env["LLAMA_LOG_COLORS"] = "off"
- pid = os.fork()
- if pid == 0:
- os.setsid()
- os.dup2(slave_fd, 0)
- os.dup2(slave_fd, 1)
- os.dup2(slave_fd, 2)
- os.close(master_fd)
- os.execvpe(cmd[0], cmd, env)
- else:
- os.close(slave_fd)
- output = io.BytesIO()
- line_buf = b""
- while True:
- try:
- r, w, e = select.select([master_fd], [], [], 1.0)
- except OSError:
- break
- if r:
- try:
- data = os.read(master_fd, 1024)
- except OSError as e:
- if e.errno == errno.EIO: # pty закрыт дочерним процессом – нормальное завершение
- break
- raise
- if not data:
- break
- output.write(data)
- line_buf += data
- while b'\n' in line_buf:
- line_bytes, line_buf = line_buf.split(b'\n', 1)
- line = line_bytes.decode('utf-8', errors='replace').strip()
- if not any(line.startswith(s) for s in ['main:', 'common_', 'llama_', 'load_', 'print_info', 'sched_reserve', 'common_init']):
- sys.stderr.write(line + '\n')
- try:
- wpid, wstatus = os.waitpid(pid, os.WNOHANG)
- if wpid == pid:
- break
- except (ChildProcessError, OSError):
- pass
- os.close(master_fd)
- result = output.getvalue().decode('utf-8', errors='replace').strip()
- for token in ['<|user|>', '<|endoftext|>', '<|assistant|>', '<|system|>', '<|im_end|>']:
- result = result.replace(token, '')
- lines = result.split('\n')
- clean_lines = [l for l in lines if not any(l.startswith(s) for s in ['main:', 'common_', 'llama_', 'load_', 'print_info', 'sched_reserve', 'common_init'])]
- # убираем только последние строго идентичные дубликаты
- final_lines = []
- seen = set()
- for l in clean_lines:
- if l not in seen:
- final_lines.append(l)
- seen.add(l)
- return '\n'.join(final_lines).strip()
- def main():
- parser = argparse.ArgumentParser(description="Универсальный анализатор кода/текста")
- parser.add_argument("--file", help="Путь к файлу")
- parser.add_argument("--stdin", action="store_true", help="Читать из stdin")
- parser.add_argument("--task", default="audit", choices=["audit","find_secrets","text_analysis","search","explain_code"])
- parser.add_argument("--query", help="Поисковый запрос (для task=search)")
- parser.add_argument("--temperature", type=float, default=0.15)
- parser.add_argument("--max-tokens", type=int, default=2048)
- parser.add_argument("--ctx-size", type=int, default=4096)
- parser.add_argument("--backend", choices=["groq","openrouter","ollama","local"])
- args = parser.parse_args()
- if args.file:
- with open(args.file, "r", encoding="utf-8", errors="ignore") as f:
- content = f.read()
- elif args.stdin:
- content = sys.stdin.read()
- else:
- parser.print_help()
- sys.exit(1)
- if not content.strip():
- print("[x] Пустой ввод.", file=sys.stderr)
- sys.exit(1)
- if args.task=="search":
- if not args.query:
- sys.exit("Нужен --query")
- base_prompt = PROMPTS["search"].format(query=args.query)
- else:
- base_prompt = PROMPTS[args.task]
- full_prompt = f"{base_prompt}\n\n### ИСХОДНЫЙ КОД / ТЕКСТ:\n{content}"
- backend, model, creds = select_best_model(args.backend)
- try:
- if backend=="groq": result = query_groq(full_prompt, creds["api_key"], model, args.temperature, args.max_tokens)
- elif backend=="openrouter": result = query_openrouter(full_prompt, creds["api_key"], model, args.temperature, args.max_tokens)
- elif backend=="ollama": result = query_ollama(full_prompt, creds["base_url"], model, creds.get("api_key"), args.temperature, args.max_tokens)
- elif backend=="local": result = query_local(full_prompt, LOCAL_MODEL, args.ctx_size, args.temperature, args.max_tokens)
- else: sys.exit(f"Unknown backend {backend}")
- print(result)
- except Exception as e:
- print(f"[x] Ошибка: {e}", file=sys.stderr)
- sys.exit(1)
- if __name__=="__main__":
- main()
- EOF
- chmod +x ~/bin/smart_analyze.py
- ```
- # Веб-сервер (обновленная версия с max_tokens)
- ```bash
- cat > ~/bin/web_analyzer.py << 'EOF'
- #!/usr/bin/env python3
- """
- Веб-интерфейс для smart_analyze.py с потоковым выводом и настройкой max_tokens
- """
- import os, sys, tempfile, subprocess
- from flask import Flask, request, render_template_string, Response, stream_with_context
- app = Flask(__name__)
- HTML = r"""
- <!DOCTYPE html>
- <html>
- <head>
- <meta charset="utf-8">
- <meta name="viewport" content="width=device-width, initial-scale=1">
- <title>Smart Analyzer</title>
- <style>
- body { font-family: sans-serif; max-width: 800px; margin: 0 auto; padding: 1em; }
- label { display: block; margin-top: 1em; }
- textarea, select, input { width: 100%; box-sizing: border-box; }
- textarea { height: 200px; }
- #result { margin-top: 1em; white-space: pre-wrap; background: #f4f4f4; padding: 1em; border-radius: 4px; min-height: 50px; }
- .spinner { display: none; margin-left: 10px; }
- .advanced { margin-top: 1em; border-top: 1px solid #ccc; padding-top: 1em; }
- </style>
- </head>
- <body>
- <h2>Smart Analyzer</h2>
- <form id="form" method="post" enctype="multipart/form-data">
- <label>Файл (выберите или перетащите):</label>
- <input type="file" name="file" id="fileInput">
- <label>Или вставьте текст/код:</label>
- <textarea name="text" id="textInput" placeholder="Вставьте сюда код или текст для анализа..."></textarea>
- <label>Режим:</label>
- <select name="task">
- <option value="audit">Аудит безопасности</option>
- <option value="explain_code">Объяснение кода</option>
- <option value="find_secrets">Поиск секретов</option>
- <option value="text_analysis">Смысловой анализ текста</option>
- <option value="search">Поиск по коду (введите запрос ниже)</option>
- </select>
- <label>Бэкенд:</label>
- <select name="backend">
- <option value="auto">Автоматический</option>
- <option value="groq">Groq</option>
- <option value="openrouter">OpenRouter</option>
- <option value="ollama">Ollama</option>
- <option value="local">Локальная модель</option>
- </select>
- <div id="searchQueryDiv" style="display:none;">
- <label>Поисковый запрос:</label>
- <input type="text" name="query" id="queryInput" placeholder="Что искать в коде?">
- </div>
- <div class="advanced">
- <label>Максимальное количество токенов (для облачных API):</label>
- <input type="number" name="max_tokens" value="4096" min="256" max="16384" step="256">
- <small>Больше токенов — длиннее ответ. По умолчанию 4096.</small>
- </div>
- <button type="submit" style="margin-top:1em;">Запустить анализ</button>
- <span class="spinner" id="spinner">⏳ Анализируем...</span>
- </form>
- <h3>Результат</h3>
- <div id="result">Здесь появится ответ...</div>
- <script>
- const form = document.getElementById('form');
- const fileInput = document.getElementById('fileInput');
- const textInput = document.getElementById('textInput');
- const taskSelect = document.getElementsByName('task')[0];
- const searchQueryDiv = document.getElementById('searchQueryDiv');
- const spinner = document.getElementById('spinner');
- const resultDiv = document.getElementById('result');
- taskSelect.addEventListener('change', function() {
- searchQueryDiv.style.display = this.value === 'search' ? 'block' : 'none';
- });
- if (taskSelect.value === 'search') searchQueryDiv.style.display = 'block';
- fileInput.addEventListener('change', function() {
- if (fileInput.files.length > 0) textInput.value = '';
- });
- textInput.addEventListener('input', function() {
- if (textInput.value.trim() !== '') fileInput.value = '';
- });
- form.addEventListener('submit', async function(e) {
- e.preventDefault();
- spinner.style.display = 'inline';
- resultDiv.textContent = '';
- const formData = new FormData(form);
- try {
- const response = await fetch('/analyze', {
- method: 'POST',
- body: formData
- });
- if (!response.ok) {
- resultDiv.textContent = 'Ошибка HTTP: ' + response.status;
- return;
- }
- const reader = response.body.getReader();
- const decoder = new TextDecoder();
- let buffer = '';
- while (true) {
- const { done, value } = await reader.read();
- if (done) break;
- buffer += decoder.decode(value, { stream: true });
- let lines = buffer.split('\n');
- buffer = lines.pop() || '';
- for (const line of lines) {
- resultDiv.textContent += line + '\n';
- }
- }
- if (buffer) {
- resultDiv.textContent += buffer;
- }
- } catch (err) {
- resultDiv.textContent = 'Ошибка: ' + err.message;
- } finally {
- spinner.style.display = 'none';
- }
- });
- </script>
- </body>
- </html>
- """
- @app.route('/')
- def index():
- return render_template_string(HTML, result=None)
- @app.route('/analyze', methods=['POST'])
- def analyze():
- task = request.form.get('task', 'audit')
- backend = request.form.get('backend', 'auto')
- query = request.form.get('query', '')
- max_tokens = request.form.get('max_tokens', '4096')
- content = ''
- if 'file' in request.files and request.files['file'].filename != '':
- file = request.files['file']
- content = file.read().decode('utf-8', errors='ignore')
- elif request.form.get('text', '').strip():
- content = request.form['text'].strip()
- else:
- return Response("Не указан ни файл, ни текст", status=400)
- if not content:
- return Response("Пустой ввод", status=400)
- tmp = tempfile.NamedTemporaryFile(mode='w', suffix='.txt', delete=False, encoding='utf-8')
- tmp.write(content)
- tmp_path = tmp.name
- tmp.close()
- def generate():
- cmd = ['smart_analyze.py', '--file', tmp_path, '--task', task, '--max-tokens', max_tokens]
- if backend != 'auto':
- cmd += ['--backend', backend]
- if task == 'search' and query:
- cmd += ['--query', query]
- try:
- proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
- text=True, bufsize=1, universal_newlines=True)
- for line in iter(proc.stdout.readline, ''):
- if not line:
- break
- yield line
- proc.stdout.close()
- proc.wait()
- if proc.returncode != 0:
- yield f"\n[Процесс завершился с кодом {proc.returncode}]"
- except Exception as e:
- yield f"\n[Ошибка: {e}]"
- finally:
- os.unlink(tmp_path)
- return Response(stream_with_context(generate()), mimetype='text/plain')
- if __name__ == '__main__':
- print("Веб-сервер с потоковым выводом: http://127.0.0.1:5000")
- app.run(host='127.0.0.1', port=5000, debug=False, threaded=True)
- EOF
- chmod +x ~/bin/web_analyzer.py
- ```
- ***
- ## 4. Режимы анализа
- | Режим | Описание |
- |-------|----------|
- | `audit` | Аудит безопасности: SQLi, крипто, хардкод, утечки |
- | `find_secrets` | API-ключи, токены, пароли, хэши |
- | `text_analysis` | Темы, тональность, намерения |
- | `search` | Поиск по коду (требует `--query`) |
- | `explain_code` | Объяснение алгоритма |
- ***
- ## 5. CLI-команды
- ### 5.1. Анализ файла
- ```bash
- smart_analyze.py --file app.java --task audit
- smart_analyze.py --file RsaUtils.java --task explain_code --backend local
- ```
- ### 5.2. stdin
- ```bash
- cat code.java | smart_analyze.py --stdin --task audit
- echo "код" | smart_analyze.py --stdin --task find_secrets
- ```
- ### 5.3. Поиск
- ```bash
- smart_analyze.py --file app.java --task search --query "RC4"
- ```
- ### 5.4. Backend и параметры
- ```bash
- smart_analyze.py --file code.java --backend groq --temperature 0.2 --max-tokens 8192 --ctx-size 4096
- ```
- ***
- ## 6. Веб-интерфейс
- ### 6.1. Запуск
- ```bash
- python3 ~/bin/web_analyzer.py
- ```
- **Адрес:** http://127.0.0.1:5000
- ### 6.2. Возможности GUI
- - Загрузка файла или ввод текста
- - Выбор режима, backend, max_tokens (256-16384)
- - Поисковый запрос (для `search`)
- - **Потоковый вывод** в реальном времени
- - Автоочистка при новом запросе
- ### 6.3. Устранение неполадок
- - **Failed to fetch:** сервер не запущен → `python3 ~/bin/web_analyzer.py`
- - **JSON.parse:** очистите кеш (Ctrl+Shift+R)
- ***
- ## 7. Особенности и лимиты
- ### 7.1. Локальная модель
- - **Qwen2.5-1.5B** – оптимально (8-10 токенов/с)
- - Загрузка: 4-5 сек
- ### 7.2. Облачные лимиты
- | Сервис | Лимит/мин | Лимит/день |
- |--------|-----------|------------|
- | Groq 70B | 30 | 14400 |
- | Groq 8-9B | 100 | 14400 |
- | OpenRouter | - | 50 (бесплатно) |
- **Автопереключение** при 429
- ### 7.3. Конфиденциальность
- **Облако:** код уходит на сервер
- **Решение:** `--backend local` или обфускация
- ### 7.4. Доступ к файлам
- `/sdcard` → `~/storage/` в Termux
- ***
- ## 8. Будущие улучшения
- - Chat-шаблоны из метаданных
- - llama-server для кэширования
- - Hugging Face интеграция
- - Server-Sent Events
- - Подсветка синтаксиса
- - История запросов
- ***
- **Система полностью готова!** Передайте этот документ любой LLM для контекста.
Advertisement
Add Comment
Please, Sign In to add comment