Moin,
sehr erfreulicherweise ist ja nun die DSGVO-konforme KI-Nutzung eines starken Sprachmodells / LLMs innerhalb von tomedo möglich. Vielen Dank nochmal für diese tolle Funktion.
Bisher ist noch ein AppleScript dazu notwendig, um die KI innerhalb von tomedo (abgesehen vom Karteichat) ansprechen zu können. Ich hatte ja schon eine Vorgängerversion mit Nutzung von Ollama für auf dem eigenen Rechner laufende KI-Analysen gezeigt (/d/106811 diese Lösung setzt aber hinreichend starke Rechnerleistung lokal voraus. Da der Thread schon sehr umfangreich geworden ist, starte ich hier nochmal einen neuen, um Anderen den Einstieg zu erleichtern.
Zusammengefasst soll die hier gezeigte Funktion folgendes leisten: Jeder Text, den man innerhalb / außerhalb von tomedo per Auswählen (z.B. CMD-A) und Kopieren (CMD-C) in die Zwischenablage kopiert, kann mit einem zur Analyse passenden, vorformulierten Prompt per KI analysiert werden. Die zuvor bestehende Datenschutz-Problematik bei Nutzung leistungsstarker LLMs auf externen Servern ist durch von Zollsoft selbst betriebenen Servern beseitigt. Der Aufruf der KI funktioniert über einen Textbaustein, der eigentlich nur eine Aktionskette startet. Durch diesen Weg des Aktionskettenaufrufs kann man sich den Textbaustein in allen Karteieinträgen ablegen und dann mit einem Klick den KI-Aufruf starten.
1. Schritt: Anlage der Textbausteine für die Prompts

2. Schritt: Erstellen des AppleScripts (unter Admin > Skriptverwaltung)
Für den individuellen Anwendungsfall kann / muss für die Verwendung eigener Prompts das Skript angepasst werden. Im Zweifelsfall kann man sich hier auch von ChatGPT / Gemini / Claude helfen lassen. In meinem Beispiel heißt das AppleScript "5-LLM-API-v2" (der Name des verwendeten AppleScript muss in der Aktionskette im 3. Schritt richtig ausgewählt werden).
Die URL-Adresse des Servers findet man hier: tomedo > Über tomedo > Server-URL (z.B. http://localhost:8080/tomedo_live/). Den User-Ident findet man mit dieser Statistik (im Feld "SQL/Konfiguration" muss 'MeinKuerzel' durch das eigene Kürzel ersetzt werden) heraus:

------------------------------------------------------------
-- Befüllen der Variable inputString, die den per KI zu analysierenden Text enthalten soll
-- hier wird der Inhalt der Zwischenablage benutzt
set the inputString to the clipboard
------------------------------------------------------------
-- Auswahl des Prompts durch den Benutzer
-- In dieser Liste werden dem Nutzer die verschiedenen Prompts zur Auswahl angezeigt
-- Die Begriffe werden in exakt dieser Form in der folgenden if-Kette geprüft und zur Prompt-Zuordnung benutzt
set userChoice to choose from list {"Arztbericht", "Schlaflabor", "CT", "BGA", "externe Anfrage", "Gutachtenauftrag", "Patientenbrief", "Übersetzung (türkisch)"} with prompt "Bitte eine Auswahl treffen:" default items {"Arztbericht"}
-- Falls der Benutzer abbricht, endet das Skript hier
if userChoice is false then
return
end if
set userChoice to item 1 of userChoice
set prompt to ""
------------------------------------------------------------
-- Zuordnung des Prompt-Textes entsprechend der zuvor vom Benutzer getroffenen Auswahl
-- Die Prompt-Texte werden nicht hier im Skript abgelegt, sondern separat in Textbausteinen
-- Dadurch ist das rasche Ändern eines Promptes, welche auch in verschiedenen Skripten gleichzeitig genutzt werden können, möglich.
-- In Abhängigkeit der Auswahl können hier auch noch Zusatzfragen gestellt werden (wie hier im Beispiel der BGA)
-- Je nach Auswahl Prompt-Text setzen
if userChoice is "Arztbericht" then
set auftrag to "$[_Prompt_Arztbericht]$"
else if userChoice is "Schlaflabor" then
set auftrag to "$[_Prompt_Schlaflabor]$"
else if userChoice is "CT" then
set auftrag to "$[_Prompt_CT-CKE]$"
else if userChoice is "BGA" then
set BGA_Bedingung to choose from list {"Raumluft", "1 l O2/min", "2 l O2/min", "3 l O2/min", "4 l O2/min", "5 l O2/min"} with prompt "Unter welcher Bedingung wurde die BGA abgenommen:" default items {"Raumluft"}
set auftrag to "$[_Prompt_BGA]$" & "BGA unter " & (item 1 of BGA_Bedingung)
else if userChoice is "externe Anfrage" then
set auftrag to "$[_Prompt_Anfrage_extern]$"
else if userChoice is "Gutachtenauftrag" then
set auftrag to "$[_Prompt_Gutachten]$"
else if userChoice is "Patientenbrief" then
set auftrag to "$[_Prompt_Patientenbrief]$"
else if userChoice is "Übersetzung (türkisch)" then
set auftrag to "$[_Prompt_Übersetzung_Türkisch]$"
end if
------------------------------------------------------------
-- Zusammenführen des Prompts (Variable auftrag) und des zu analysierenden Texts (Variable inputString)
set prompt to auftrag & inputString
------------------------------------------------------------
-- Anzeige des gesamten Prompts vor Weiterleitung an die KI
display dialog "Deine Auswahl: " & userChoice & return & "Variable: " & prompt buttons {"OK"} default button 1
------------------------------------------------------------
-- Angabe der URL-Adresse des tomedo-Servers und des Nutzer-Idents
set tomedo_server_url to "http://tomedo.localnet:8080/tomedo_live/"
set user_ident to "4"
-- Zusammensetzen des vollständiger Endpoints für die Ansprache des LLM:
set llm_endpoint to tomedo_server_url & "llmservice/" & user_ident & "/v1/chat/completions"
------------------------------------------------------------
-- Auswahl des für die KI-Analyse zu verwendenden LLM:
-- Hier muss das gewünschte Standardmodell gesetzt werden, z.B. "gemini-2.5-pro" oder "mistral-medium-2508" oder oder
set selectedModel to "gemini-2.5-pro"
------------------------------------------------------------
-- Aufruf des tomedo-LLM-Services
-- Erstellung des Python-Programms (verwendet nur Python-Standardbibliotheken), welches in der Variable pycode abgelegt wird
set pyCode to "
import os, sys, json, urllib.request, urllib.error
TOMEDO_URL = os.environ.get('LLM_ENDPOINT')
MODEL = os.environ.get('USER_MODEL', 'gemini-2.5-flash')
prompt = os.environ.get('USER_PROMPT', '')
payload = {
'model': MODEL,
'messages': [{'role': 'user', 'content': prompt}],
'stream': False
}
req = urllib.request.Request(
TOMEDO_URL,
data=json.dumps(payload).encode('utf-8'),
headers={'Content-Type': 'application/json'},
method='POST'
)
try:
with urllib.request.urlopen(req, timeout=120) as resp:
data = json.loads(resp.read().decode('utf-8'))
# OpenAI-kompatibles Format: choices[0].message.content
content = ''
try:
choices = data.get('choices') or []
if choices:
msg = choices[0].get('message', {}) or {}
content = (msg.get('content') or '').strip()
except Exception:
pass
# Fallback: Ollama-ähnliches Format data['message']['content']
if not content:
content = (data.get('message', {}) or {}).get('content', '').strip()
# Letzter Fallback: direktes 'content'-Feld
if not content:
content = (data.get('content') or '').strip()
sys.stdout.write(content)
except urllib.error.HTTPError as e:
msg = e.read().decode('utf-8', errors='ignore')
sys.stderr.write(f'HTTPError {e.code}: {msg}\\n')
sys.exit(1)
except Exception as e:
sys.stderr.write(f'ERROR: {e}\\n')
sys.exit(1)
"
------------------------------------------------------------
-- Das Python-Programm wird nun mit dem Prompt, dem ausgewählten LLM-Modell und dem tomedo-LLM-Endpoint zusammengeführt
-- Übergabe an den Python-Prozess via Env-Variablen
set shellCmd to "/usr/bin/env /bin/zsh -lc " & quoted form of ( ¬
"export USER_PROMPT=" & quoted form of prompt & "; " & ¬
"export USER_MODEL=" & quoted form of selectedModel & "; " & ¬
"export LLM_ENDPOINT=" & quoted form of llm_endpoint & "; " & ¬
"python3 - <<'PY'\n" & pyCode & "\nPY")
try
set resultText to do shell script shellCmd
on error errmsg number errNum
display dialog ("Fehler beim API-Aufruf:
" & errmsg) buttons {"OK"} with icon stop
return
end try
------------------------------------------------------------
-- Das Resultat der KI-Analyse (Variable resultText) wird zum Abschluss in die Zwischenablage gelegt
set the clipboard to resultText
-- Hinweis-Fenster
display dialog "Fertig: Auswertung befindet sich in der Zwischenablage." buttons {"OK"} default button 1 with icon note
------------------------------------------------------------
-- In Abhängigkeit des gewählten Prompts (Benutzerauswahl) können nun noch geeignete Aktionsketten gestartet werden
if userChoice is "BGA" then
tell application "Finder" to open location "tomedo://aktionskette?1-5-2-BGA-Transfer"
end if
3. Schritt: Aktionskette zum Aufruf des AppleScripts erstellen

4. Schritt: Textbaustein zum KI-Aufruf anlegen

5. Schritt: Textbaustein (heißt bei mir "API") in den passenden Karteieinträgen zur Auswahl platzieren.
6. Schritt (Alltagspraxis): Auslesen von Texten aus einem PDF
Mit diesem wichtigen (noch nicht überall bekannten Schritt) kann aus eingelesenen / eingescannten PDFs der Text extrahiert werden. Mittels CMD-A und CMD-C wird dieser Text dann in die Zwischenablage kopiert. Klickt man nun auf den "API"-Button in der Zeile oben, startet die KI-Analyse...
