198 lines
6.7 KiB
Python
198 lines
6.7 KiB
Python
"""
|
|
Module: api_interface.py
|
|
--------------------------
|
|
Obsahuje funkce pro komunikaci s OpenAI API a správu konverzačního vlákna.
|
|
"""
|
|
|
|
import openai
|
|
import json
|
|
import time
|
|
import threading
|
|
import logging
|
|
from configparser import ConfigParser
|
|
|
|
logger = logging.getLogger("robovojtik.api_interface")
|
|
config = ConfigParser()
|
|
config.read("config.ini")
|
|
|
|
try:
|
|
openai.api_key = config["OpenAI"]["api_key"]
|
|
except KeyError:
|
|
raise ValueError("API key not found in config.ini. Zadejte 'api_key' do [OpenAI].")
|
|
|
|
ASSISTANT_ID = config["OpenAI"].get("assistant_id", None)
|
|
if not ASSISTANT_ID:
|
|
raise ValueError("assistant_id not found in config.ini. Zadejte 'assistant_id' do [OpenAI].")
|
|
|
|
# Globální vlákno konverzace
|
|
thread_id = None
|
|
|
|
def vytvor_nove_vlakno():
|
|
"""
|
|
Vytvoří nové vlákno konverzace s asistentem pomocí OpenAI API.
|
|
"""
|
|
global thread_id
|
|
try:
|
|
thread = openai.beta.threads.create()
|
|
thread_id = thread.id
|
|
logger.debug(f"Vytvořeno nové vlákno: {thread_id}")
|
|
return thread_id
|
|
except Exception as e:
|
|
logger.exception("Chyba při vytváření nového vlákna konverzace.")
|
|
raise
|
|
|
|
def posli_dotaz_do_assistenta(prompt):
|
|
"""
|
|
Odesílá dotaz v přirozeném jazyce do asistenta a vrací jeho odpověď.
|
|
Ošetřuje stav "queued", "running", "pending" i "in_progress" tak, aby cyklus čekal na dokončení.
|
|
Vrací poslední zprávu od asistenta.
|
|
"""
|
|
global thread_id
|
|
if thread_id is None:
|
|
vytvor_nove_vlakno()
|
|
logger.debug(f"Odesílám dotaz: {prompt}")
|
|
|
|
openai.beta.threads.messages.create(
|
|
thread_id=thread_id,
|
|
role="user",
|
|
content=prompt
|
|
)
|
|
run = openai.beta.threads.runs.create(
|
|
thread_id=thread_id,
|
|
assistant_id=ASSISTANT_ID
|
|
)
|
|
while True:
|
|
run_status = openai.beta.threads.runs.retrieve(thread_id=thread_id, run_id=run.id)
|
|
if run_status.status == "requires_action":
|
|
handle_required_action(run_status, run.id, thread_id)
|
|
elif run_status.status == "completed":
|
|
break
|
|
elif run_status.status in ["queued", "running", "pending", "in_progress"]:
|
|
time.sleep(1)
|
|
else:
|
|
logger.warning(f"Neočekávaný stav run_status: {run_status.status}")
|
|
time.sleep(1)
|
|
break
|
|
|
|
messages = openai.beta.threads.messages.list(thread_id=thread_id)
|
|
answer = ""
|
|
for msg in reversed(messages.data):
|
|
if msg.role == "assistant" and msg.content and msg.content[0].text:
|
|
answer = msg.content[0].text.value
|
|
break
|
|
logger.debug(f"Asistent odpověděl: {answer}")
|
|
return answer
|
|
|
|
def handle_required_action(run_status, run_id, thread_id):
|
|
"""
|
|
Vyřídí volání funkcí, které asistent vyžaduje (execute_shell_command, create_script, read_file).
|
|
"""
|
|
from shell_functions import spust_prikaz, vytvor_skript, read_file_content
|
|
|
|
try:
|
|
tool_calls = run_status.required_action.submit_tool_outputs.tool_calls
|
|
tool_outputs = []
|
|
for tool_call in tool_calls:
|
|
tool_name = tool_call.function.name
|
|
logger.debug(f"Asistent volá funkci: {tool_name}")
|
|
try:
|
|
arguments = json.loads(tool_call.function.arguments)
|
|
except Exception as e:
|
|
logger.exception("Chyba při parsování argumentů pro funkci.")
|
|
tool_outputs.append({
|
|
"tool_call_id": tool_call.id,
|
|
"output": "Chyba: Nepodařilo se přečíst argumenty."
|
|
})
|
|
continue
|
|
|
|
if tool_name == "execute_shell_command":
|
|
vysledek = spust_prikaz(arguments["command"])
|
|
elif tool_name == "create_script":
|
|
vysledek = vytvor_skript(arguments["file_name"], arguments["content"])
|
|
elif tool_name == "read_file":
|
|
vysledek = read_file_content(arguments["path"])
|
|
else:
|
|
vysledek = "Neznámá funkce."
|
|
|
|
tool_outputs.append({
|
|
"tool_call_id": tool_call.id,
|
|
"output": vysledek
|
|
})
|
|
|
|
openai.beta.threads.runs.submit_tool_outputs(
|
|
thread_id=thread_id,
|
|
run_id=run_id,
|
|
tool_outputs=tool_outputs
|
|
)
|
|
except Exception as e:
|
|
logger.exception("Chyba při handle_required_action.")
|
|
|
|
def posli_prikaz_do_assistenta(command):
|
|
"""
|
|
Odesílá schválený příkaz k vykonání do asistenta (např. cmd:).
|
|
Ošetřuje stavy podobně jako posli_dotaz_do_assistenta.
|
|
"""
|
|
global thread_id
|
|
if thread_id is None:
|
|
vytvor_nove_vlakno()
|
|
logger.debug(f"Odesílám příkaz k vykonání: {command}")
|
|
run = openai.beta.threads.runs.create(
|
|
thread_id=thread_id,
|
|
assistant_id=ASSISTANT_ID,
|
|
instructions=f"Prosím, spusť tento příkaz: {command}"
|
|
)
|
|
while True:
|
|
run_status = openai.beta.threads.runs.retrieve(thread_id=thread_id, run_id=run.id)
|
|
if run_status.status == "requires_action":
|
|
handle_required_action(run_status, run.id, thread_id)
|
|
elif run_status.status == "completed":
|
|
break
|
|
elif run_status.status in ["queued", "running", "pending", "in_progress"]:
|
|
time.sleep(1)
|
|
else:
|
|
logger.warning(f"Neočekávaný stav run_status: {run_status.status}")
|
|
time.sleep(1)
|
|
break
|
|
messages = openai.beta.threads.messages.list(thread_id=thread_id)
|
|
answer = ""
|
|
for msg in reversed(messages.data):
|
|
if msg.role == "assistant" and msg.content and msg.content[0].text:
|
|
answer = msg.content[0].text.value
|
|
break
|
|
logger.debug(f"Výsledek spuštěného příkazu: {answer}")
|
|
return answer
|
|
|
|
def volani_asistenta(prompt, spinner_func=None):
|
|
"""
|
|
Spustí dotaz do asistenta v samostatném vlákně a během čekání volá spinner_func.
|
|
Vrací odpověď asistenta nebo chybovou hlášku.
|
|
"""
|
|
result_container = {}
|
|
|
|
def worker():
|
|
try:
|
|
odpoved = posli_dotaz_do_assistenta(prompt)
|
|
result_container['answer'] = odpoved
|
|
except Exception as ex:
|
|
result_container['answer'] = f"Chyba při volání asistenta: {str(ex)}"
|
|
logger.exception("Chyba v worker funkci volani_asistenta.")
|
|
|
|
thread = threading.Thread(target=worker)
|
|
thread.start()
|
|
idx = 0
|
|
spinner = ["|", "/", "-", "\\"]
|
|
while thread.is_alive():
|
|
if spinner_func:
|
|
spinner_func(spinner[idx % len(spinner)])
|
|
time.sleep(0.2)
|
|
idx += 1
|
|
thread.join()
|
|
return result_container.get('answer', "")
|
|
|
|
def reset_thread():
|
|
"""
|
|
Resetuje konverzační vlákno tím, že nastaví globální thread_id na None.
|
|
"""
|
|
global thread_id
|
|
thread_id = None
|