Robovojtik/api_interface.py
sinuhet 2e2a18272a Oprava, po které to ale blbne
Po příkazu se ptá stále dokola
2025-03-22 23:24:23 +01:00

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