88 lines
4.4 KiB
Python
88 lines
4.4 KiB
Python
#!/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()
|