Implementacja kontekstu i historii wiadomości w lokalnym czacie LLM w Pythonie
Czat konsolowy oparty na LiteLLM i Ollama z poprzedniej części przetwarza zapytania niezależnie. Model nie widzi poprzednich wypowiedzi, co psuje dialog: odwołania do wcześniejszych odpowiedzi są ignorowane. Rozwiązaniem jest przechowywanie historii wiadomości w liście Pythona i przekazywanie jej z każdym nowym zapytaniem.
To przekształca sekwencję wywołań w spójną rozmowę. Model otrzymuje pełny kontekst: instrukcję systemową, poprzednie pary user/assistant oraz bieżące dane wejściowe.
Struktura wiadomości dla kontekstu
Format wiadomości jest standardowy dla API zgodnych z OpenAI:
system: stała instrukcja, dodawana osobnouser: zapytanie użytkownikaassistant: odpowiedź modelu
Historia zawiera tylko user/assistant. Przy wysyłaniu:
[
{"role": "system", "content": SYSTEM_PROMPT},
*conversation_history,
{"role": "user", "content": user_message}
]
Kolejność jest kluczowa: system określa zachowanie, historia daje kontekst, nowy user to bieżące zapytanie.
Zaktualizowany kod main.py
Pełny skrypt z historią i przycinaniem kontekstu:
# -*- coding: utf-8 -*-
import time
from typing import Optional
from litellm import completion
MODEL = "ollama_chat/qwen2.5:3b"
API_BASE = "http://localhost:11434"
SYSTEM_PROMPT = "Jesteś pomocnym asystentem. Odpowiadaj zwięźle i na temat."
MAX_HISTORY_MESSAGES = 6
def trim_history(history: list, limit: int) -> list:
if len(history) <= limit:
return history
return history[-limit:]
def send_request_to_llm(user_message: str, history: list) -> Optional[str]:
messages = [
{"role": "system", "content": SYSTEM_PROMPT},
*history,
{"role": "user", "content": user_message},
]
try:
start_time = time.time()
response = completion(
model=MODEL,
messages=messages,
api_base=API_BASE,
request_timeout=120,
)
duration = time.time() - start_time
print(f"\nCzas generowania: {duration:.2f} sek")
return response.choices[0].message.content
except Exception as e:
print(f"\nBłąd przy zapytaniu: {e}")
return None
def main() -> None:
print("Lokalny asystent AI z pamięcią uruchomiony.")
print("Wprowadź pytanie lub 'wyjście' aby zakończyć.\n")
conversation_history = []
while True:
user_input = input("Ty: ").strip()
if user_input.lower() in ("wyjście", "exit", "quit"):
print("Do widzenia!")
break
if not user_input:
print("Wprowadź pytanie.")
continue
print("\nModel myśli...")
answer = send_request_to_llm(user_input, conversation_history)
if answer is not None:
print(f"\nAI: {answer}\n")
conversation_history.append({"role": "user", "content": user_input})
conversation_history.append({"role": "assistant", "content": answer})
conversation_history = trim_history(conversation_history, MAX_HISTORY_MESSAGES)
else:
print("\nNie udało się uzyskać odpowiedzi. Sprawdź, czy Ollama jest uruchomiona.\n")
if __name__ == "__main__":
main()
Kluczowe zmiany w logice
- conversation_history: lista w main() przechowuje pary wiadomości
- send_request_to_llm: przyjmuje history, rozwija przez
* - Zapisywanie par: po odpowiedzi dodawane są user i assistant
- trim_history: przycina do MAX_HISTORY_MESSAGES (6 sztuk — 3 dialogi)
| Komponent | Przed aktualizacją | Po aktualizacji |
|-----------|---------------|------------------|
| messages | system + user | system + history + user |
| Pamięć | Brak | Lista w RAM |
| Rozmiar | Nieograniczony | Do 6 wiadomości |
Przycinanie zapobiega przepełnieniu kontekstu: stare wiadomości zwiększają tokeny, spowalniając inference.
Testowanie kontekstu
Dialog z zależnymi zapytaniami:
- Ty: Wymień trzy frameworki webowe w Pythonie
- AI: Django, Flask, FastAPI
- Ty: Opowiedz szczegółowo o drugim
- AI: Flask — lekki microframework...
Model poprawnie odnosi się do „drugiego”, widząc poprzednią odpowiedź.
Ograniczenia obecnej implementacji
- Historia w RAM: znika przy ponownym uruchomieniu
- Stały limit: 6 wiadomości (można dostosować)
- Brak trwałości: dla produkcji potrzebny Redis/DB
Dla bota Telegram lub aplikacji webowej logika pozostaje: history w sesji użytkownika.
Co jest ważne
- Format wiadomości: system osobno, history tylko user/assistant
- Przycinanie historii: [-limit:] zachowuje ostatnie pary
- Podział odpowiedzialności: zapytania LLM w send_request_to_llm, stan w main()
- Limity czasu: request_timeout=120 dla długich kontekstów
- Logowanie: czas generowania do debugowania wydajności
— Editorial Team
Brak komentarzy.