Podręcznik uczestnika z objaśnieniami, przykładami i ćwiczeniami do samodzielnej nauki do Modułu 5
7. Integracja własnego asystenta
Połączysz elementy tak, aby odmowa lub wyjątek nie omijały zakończenia tury. Koordynator odpowiada za kolejność, a poszczególne funkcje zachowują wąskie zadania.
1. Przepływ pojedynczej wiadomości
- begin_turn: identyfikatory, czas i wyzerowanie znacznika próby.
- Walidacja wiadomości i kontrola zakresu.
- Ograniczona historia i rozwiązanie pytania zależnego.
- Propozycja modelu oraz normalizacja, ewentualnie zakończenie bez narzędzia.
- Złożenie wywołania z zaufanych danych, walidacja i wykonanie.
- Ślad narzędzia, złożenie wyniku i finalize_turn.
2. Konfiguracja aplikacji ma pierwszeństwo
Do pliku kod/moj_m5.py: build_tool_call()
def build_tool_call(decision, resolved, *, top_k=DEFAULT_TOP_K, similarity_threshold=DEFAULT_SIMILARITY_THRESHOLD):
args = deepcopy(decision.get('arguments', {}))
if decision['action'] == 'answer_from_documents':
args.update(question=resolved['standalone_question'], top_k=top_k, similarity_threshold=similarity_threshold)
return {'action': decision['action'], 'arguments': args}
Dla RAG selektor nie zmienia jawnie wybranego top_k ani progu. Wykorzystujemy pytanie z resolvera. args.update() zastępuje te trzy pola, ale pozostawia nieznane dodatkowe pola, aby walidator mógł je odrzucić. Ich ciche usunięcie ukrywałoby niezgodną propozycję.
Test R6: zaufane dane zastępują propozycję parametrów
decision = {"action": "answer_from_documents", "arguments": {
"question": "inna treść", "similarity_threshold": None}}
resolved = {"standalone_question": "Jak skonfigurować TLS?"}
call = m.build_tool_call(decision, resolved, similarity_threshold=1.0)
assert call["arguments"]["question"] == resolved["standalone_question"]
assert call["arguments"]["similarity_threshold"] == 1.0
3. Koordynator tury
To kompletna definicja do ostatniego miejsca WARSZTAT. Pomocnicze _base_turn(), _finish_early(), build_validation_error_result(), build_assistant_result() i append_tool_trace() już istnieją. _finish_early() też korzysta z Twojego finalize_turn(). Wszystkie kontrolowane zakończenia zwiększają numer i zachowują ślad.
Do pliku kod/moj_m5.py: assistant_step()
def assistant_step(user_message, state, *, top_k=DEFAULT_TOP_K, similarity_threshold=DEFAULT_SIMILARITY_THRESHOLD, use_selector=True):
turn = begin_turn(state)
result = _base_turn(user_message, turn)
try:
clean = validate_chat_message(user_message)
except (TypeError, ValueError) as error:
return finalize_turn(state, build_validation_error_result(error, turn), turn)
result['validation'] = {'status': 'valid'}
if _forbidden(clean):
result['decision'] = {'status': 'rejected', 'origin': 'application_policy'}
return _finish_early(state, result, turn, 'rejected', 'forbidden_action', 'DocAssist opisuje procedury, ale nie wykonuje operacji systemowych ani wysyłania danych.')
if 'metadan' in clean.casefold() and (not re.search('\\bDOC-[A-Z]+\\b', clean)):
result['decision'] = {'status': 'clarification_needed', 'origin': 'application_policy'}
return _finish_early(state, result, turn, 'clarification_needed', 'missing_document_id', 'Podaj jawny identyfikator dokumentu, np. DOC-INSTR.')
history = get_recent_history(state['history'])
resolved = resolve_followup_question(clean, history)
result['resolution'] = resolved
result['conversation'].update(standalone_question=resolved['standalone_question'], history_used=resolved['history_used'])
result['diagnostics']['trace'] += ['history', 'resolve']
if resolved['resolution_status'] == 'clarification_needed':
return _finish_early(state, result, turn, 'clarification_needed', resolved['reason'], resolved['clarification_question'])
if is_last_sources_request(clean) and (not state['last_sources']):
result['decision'] = {'status': 'clarification_needed', 'origin': 'application_state'}
return _finish_early(state, result, turn, 'clarification_needed', 'missing_previous_sources', NO_SOURCES_MESSAGE)
result['diagnostics']['trace'].append('decide')
if use_selector:
try:
selection = propose_action(clean, history, build_state_summary(state), TOOL_SPECS, standalone_question=resolved['standalone_question'])
except Exception:
logger.exception('Błąd selektora')
return _finish_early(state, result, turn, 'error', 'generation_error', 'Nie udało się uzyskać propozycji działania.')
decision = normalize_action_proposal(selection)
result['diagnostics']['selection'] = selection
else:
decision = {'status': 'action_proposed', 'action': 'show_last_sources' if is_last_sources_request(clean) else 'answer_from_documents', 'arguments': {}, 'message': None, 'origin': 'fixed_flow_r3'}
result['decision'] = decision
if decision['status'] != 'action_proposed':
ds = decision['status']
reason = 'forbidden_action' if ds == 'rejected' else ds
return _finish_early(state, result, turn, 'error' if ds == 'parse_error' else 'clarification_needed' if ds == 'clarification_needed' else 'rejected', reason, decision.get('message') or 'Niepoprawna propozycja działania.')
if decision['action'] == 'get_document_metadata':
explicit_ids = re.findall('\\bDOC-[A-Z]+\\b', clean)
if decision['arguments'].get('document_id') not in explicit_ids:
return _finish_early(state, result, turn, 'rejected', 'invalid_arguments', 'Propozycja metadanych nie wskazuje identyfikatora z bieżącej wiadomości.')
call = build_tool_call(decision, resolved, top_k=top_k, similarity_threshold=similarity_threshold)
result['diagnostics']['trace'] += ['build_call', 'tool']
tool_result = execute_tool_call(call, state)
append_tool_trace(tool_result, turn)
combined = build_assistant_result(user_message, resolved, decision, tool_result, turn)
combined['diagnostics'] = result['diagnostics']
if combined['rag']:
combined['diagnostics']['trace'].append('rag')
combined['diagnostics']['trace'].append('build')
return finalize_turn(state, combined, turn)
Kontrole przed selektorem obsługują rozpoznane żądanie operacji systemowej, brak document_id i brak poprzednich źródeł. To reguły prototypu, nie uniwersalny filtr języka. Możliwości ogranicza też rejestr: brak w nim powłoki. Dla metadanych dodatkowo porównujemy zaproponowane ID z bieżącą wiadomością, aby model nie wybrał innego znanego dokumentu.
use_selector=False uruchamia stały przepływ R3 bez modelu wybierającego narzędzie. Domyślna ścieżka dodaje selektor. Nie ma automatycznego ponawiania: niepoprawną propozycję zapisujemy i kończymy turę zamiast ukrywać błąd kolejną próbą.
4. Komendy obsłuż przed turą
| Wejście | Działanie |
|---|---|
| /exit lub /koniec | Koniec interfejsu bez RAG. |
| /debug | Przełączenie diagnostyki, bez nowej tury. |
| /sources | Odczyt źródeł jako komenda, bez nowej tury. |
| /clear | Nowy conversation_id i numer od 1; czyszczenie historii i źródeł, zachowanie plików. |
| Pokaż źródła. | Naturalna wiadomość z osobnym result_id. |
Gotowe parse_interface_input(), handle_interface_command(), process_interface_input() i run_assistant() łączą interfejs z koordynatorem. Do wykonania komendy użyj process_interface_input(). Dosłowne /clear przekazane wprost do assistant_step() nie przejdzie przez parser komend i może zostać potraktowane jak pytanie.
5. Własna implementacja i scenariusze pakietu
Po uzupełnieniu wszystkich funkcji uruchom w warsztacie część „Scenariusze własnego modułu”. Adapter scenariusz_warsztatu() przekazuje do istniejących scenariuszy właśnie moj_m5. Sama zmiana importu w standardowym notebooku nie zmieniłaby wewnętrznej referencji używanej przez m5_exercises.
11 scenariuszy R6; po przygotowaniu STUDENT w notebooku
from sprawdz_moj_m5 import scenariusz_warsztatu
from m5_exercises import save_review, save_integration_report, check_submission
OUT = m.new_run(STUDENT, "6_ksiazka")
MODE = "checkpoint" # albo jawnie "ollama" dla nowych wywołań
m.configure(STUDENT, mode=MODE, output_dir=OUT, save_raw=True)
wyniki = scenariusz_warsztatu(m, 6, OUT)
for r in wyniki:
print(r["case_id"], r["status"], r["reason"])
Każda próba otrzymuje nowy katalog. Powtórny scenariusz w tym samym OUT jest blokowany. Pełny zapis włączamy jawnie dla fikcyjnych danych. Zwykłe ślady przechowują skrót pytania; functional_tests.jsonl i surowe_wyniki.jsonl pozwalają odczytać pełną treść. Nie dodawaj danych poufnych do udostępnianych prób.
6. Oceń rzeczywiste zakończenia
| Przypadek z pilotażu | Obserwacja | Wniosek |
|---|---|---|
| tura_2: stan usługi | show_last_sources i answered. | Błędna intencja mimo poprawnego wykonania. |
| tura_3: failed | Odmowa; brak potrzebnego chunk_id w sources. | Oceń pobrany kontekst osobno od generatora. |
| tls_domyslnie | answered z odmową. | Nie zamieniaj na no_usable_results. |
| tls_prog | below_threshold, rag.similarity_threshold=1.0. | Kod zastosował próg mimo None w propozycji. |
| zabronione / puste | rejected przed narzędziem. | Kontrola zakresu i wejścia. |
| po_clear / zrodla_po_clear | clarification_needed. | Nie użyto starego stanu. |
decision.status=action_proposed oznacza propozycję, tool.status=tool_completed wykonanie, a rag.status=answered tekst wygenerowany przez RAG. Zewnętrzne answered może też oznaczać odczyt źródeł lub metadanych. Sprawdź reason, wybraną akcję i treść przed uznaniem wyniku za poprawny.
7. Zapis oceny i raportu
Przy każdym result_id uzupełnij obserwacja, dowod i wniosek. Dowodem jest konkretne pole lub fragment źródła, nie ogólne „działa”. Raport ma pola granice, dowody i przekazanie_do_M6. Funkcje nie zapiszą wersji zawierającej UZUPEŁNIJ. Po zmianie komentarza uruchom tylko ocenę i zapis, bez nowych prób modelu.
Zapis oceny tej samej próby OUT
# Po uzupełnieniu oceny i raportu w notebooku:
zapis_oceny = save_review(OUT, wyniki, oceny)
zapis_raportu = save_integration_report(OUT, raport)
if zapis_oceny and zapis_raportu:
print(check_submission(OUT))
Ćwiczenie 6. Złóż prototyp i pokaż dowody
- Uzupełnij build_tool_call() i assistant_step(), sprawdź etap R6.
- Uruchom scenariusze dla własnego modułu: dialog, TLS, restart, puste wejście i reset.
- Wskaż poprawne zakończenie bez odpowiedzi oraz błąd modelu, jeśli wystąpił. Zachowaj go w logu.
- Zapisz oceny, raport i notebook; uruchom kontrolę kompletności.
- Opcjonalnie w nowej próbie Ollama zmień jeden element promptu lub resolvera i oceń te same przypadki.
ENHANCER+ · Politechnika Warszawska · podręcznik do samodzielnej pracy · M5