dp-zp-agent/scripts/build_tool_packages.py
2026-10-26 23:43:10 +01:00

88 lines
4.4 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
"""Build standalone OpenWebUI Tools archives; generated files stay in data/."""
from __future__ import annotations
import argparse
import ast
import hashlib
import importlib.util
import json
from pathlib import Path
import sys
import zipfile
ROOT = Path(__file__).resolve().parents[1]
ENGINES = ('rag', 'graphrag')
def json_bytes(value) -> bytes:
return (json.dumps(value, ensure_ascii=False, indent=2, sort_keys=True) + '\n').encode()
def build_package(engine: str, output: Path) -> dict:
if engine not in ENGINES:
raise ValueError('Unknown tool package')
source = ROOT / 'packages/openwebui' / engine / 'tool.py'
content = source.read_bytes()
header = ast.get_docstring(ast.parse(content.decode()))
metadata = dict(line.strip().split(': ', 1) for line in header.splitlines() if ': ' in line)
name = 'zpwiki_package_' + engine
spec = importlib.util.spec_from_file_location(name, source)
module = importlib.util.module_from_spec(spec)
sys.modules[name] = module
try:
spec.loader.exec_module(module)
config = module.Tools.Valves().model_dump()
schema = module.Tools.Valves.model_json_schema()
finally:
sys.modules.pop(name, None)
instructions = (
f'ZP Wiki {engine.upper()} – OpenWebUI Tool\n\n'
'ZIP najprv rozbal. Vo Workspace > Tools > Import vyber openwebui-tool.json.\n'
'Alebo cez Create vytvor nástroj a vlož celý obsah tool.py do editora.\n'
'Pri importe cez URL použi raw URL súboru tool.py, nie ZIP ani stránku repozitára.\n'
'Vo Valves nastav BACKEND_URL a BACKEND_API_KEY = SEARCH_API_KEY backendu.\n'
'Adresa musí byť dostupná zo servera OpenWebUI; neobsahuje koncový endpoint.\n'
'Backend aj oba nástroje používajú rovnaký dátový kontrakt.\n'
'Výstup sú vyhľadané podklady, nie schválený finálny text.\n'
'Pre kontrolované doručenie používaj existujúci Pipe a /answer.\n'
'Žiadny ďalší nástroj ani lokálny Python balík zp-agent nie je potrebný.\n'
'Podrobný postup, testy a obmedzenia sú v hlavnom README projektu.\n')
import_form = [{'id': module.TOOL_ID, 'name': metadata['title'], 'content': content.decode(),
'meta': {'description': metadata['description'], 'manifest': metadata},
'access_grants': []}]
files = {'tool.py': content, 'openwebui-tool.json': json_bytes(import_form), 'config.example.json': json_bytes(config),
'valves.schema.json': json_bytes(schema), 'INSTALL.txt': instructions.encode()}
manifest = {'schema_version': 1, 'kind': 'openwebui_tool', 'package_id': module.TOOL_ID,
'version': metadata['version'], 'minimum_openwebui_version': metadata['required_open_webui_version'],
'backend_endpoint': module.ENDPOINT, 'entrypoint': 'tool.py', 'output_kind': 'retrieval_context',
'files': {name: hashlib.sha256(data).hexdigest() for name, data in files.items()}}
files['manifest.json'] = json_bytes(manifest)
output.mkdir(parents=True, exist_ok=True)
destination = output / (module.TOOL_ID + '.zip')
import_file = output / (module.TOOL_ID + '.json')
import_file.write_bytes(files['openwebui-tool.json'])
# Fixed timestamps and sorted entries make a rebuild byte-for-byte identical.
with zipfile.ZipFile(destination, 'w', compression=zipfile.ZIP_DEFLATED) as archive:
for name, data in sorted(files.items()):
info = zipfile.ZipInfo(name, date_time=(1980, 1, 1, 0, 0, 0))
info.compress_type = zipfile.ZIP_DEFLATED
info.create_system = 3
info.external_attr = 0o100644 << 16
archive.writestr(info, data)
return {'package_id': module.TOOL_ID, 'archive': str(destination), 'import_file': str(import_file),
'sha256': hashlib.sha256(destination.read_bytes()).hexdigest(), 'manifest': manifest}
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('--output', type=Path, default=ROOT / 'data/tool_packages')
args = parser.parse_args()
built = [build_package(engine, args.output) for engine in ENGINES]
(args.output / 'packages.json').write_bytes(json_bytes(built))
print(json.dumps([{key: row[key] for key in ['package_id', 'archive', 'import_file', 'sha256']} for row in built], ensure_ascii=False))
if __name__ == '__main__':
main()