Przejdź do głównej treści
  1. Moje aplikacje/

LLM Wiki pod maską: Python, Obsidian i Gemini

·4 minut

W pierwszym wpisie na LinkedIn opisałem koncepcję własnego LLM Wiki jako alternatywę dla standardowego podejścia RAG. Zamiast wektoryzować i przeszukiwać dokumenty przy każdym zapytaniu, system jednorazowo kompiluje i strukturyzuje wiedzę.

Poniżej omawiam architekturę i kod z repozytorium llm_wiki na GitHubie. Architektura celowo odrzuca bazy wektorowe na rzecz pełnego okna kontekstowego modeli Google Gemini i ścisłej strukturyzacji danych wejściowych.

Struktura danych i wymuszanie schematu (pydantic_models.py) #

Domyślnym wyjściem modelu LLM jest nieustrukturyzowany tekst, co utrudnia automatyzację. Aby zintegrować model z systemem plików, stosuję bibliotekę Pydantic do zdefiniowania sztywnego schematu wyjściowego (JSON). Model działa tu jako agent decyzyjny operujący na notatkach:

 1# --- Python ---
 2from pydantic import BaseModel, Field
 3from typing import List
 4
 5class WikiAction(BaseModel):
 6    action_type: str = Field(description="Typ akcji: 'CREATE', 'UPDATE' lub 'LOG'")
 7    file_path: str = Field(description="Ścieżka do pliku w folderze wiki/, np. 'wiki/sztuczna_inteligencja.md'")
 8    title: str = Field(description="Tytuł notatki (używany jako nazwa pliku i frontmatter)")
 9    content: str = Field(description="Pełna treść notatki w formacie Markdown z linkami [[dwunawiasowymi]]. Jeśli UPDATE, podaj scaloną, zaktualizowaną treść.")
10    log_message: str = Field(description="Krótki opis zmiany do zapisania w logu")
11
12class WikiCompilationResult(BaseModel):
13    actions: List[WikiAction] = Field(description="Lista operacji na plikach wiki niezbędnych do przetworzenia źródła")

Asymilacja i strukturyzacja wiedzy (kompilacja_bazy.py) #

Głównym interfejsem bazy jest Obsidian. Nowa, surowa notatka wrzucona do folderu raw wyzwala proces kompilacji. Zanim model przetworzy nowy plik, funkcja get_current_wiki_context() ładuje zmapowaną strukturę istniejącego repozytorium.

Dzięki temu i narzuconemu schematowi WikiCompilationResult, model podejmuje decyzję kontekstową: tworzy nowy plik lub aktualizuje treść w już istniejącym.

 1# --- Python ---
 2import os
 3import glob
 4from google import genai
 5from google.genai import types
 6from pydantic_models import WikiCompilationResult 
 7from dotenv import load_dotenv
 8
 9load_dotenv() # Klucze API ładowane bezpiecznie z pliku .env (wykluczonego w .gitignore)
10client = genai.Client()
11
12def get_current_wiki_context() -> str:
13    """Zbiera strukturę i spis treści obecnego wiki."""
14    context = "OBECNY STAN WIKI:\n"
15    wiki_files = glob.glob("/Users/mpendolski/Obsidian/llm-wiki/wiki/*.md")
16    
17    if not wiki_files:
18        return context + "Wiki jest puste. Brak istniejących notatek."
19        
20    for file_path in wiki_files:
21        with open(file_path, "r", encoding="utf-8") as f:
22            lines = [f.readline() for _ in range(15)]
23            context += f"--- PLIK: {file_path} ---\n" + "".join(lines) + "\n"
24    return context
25
26def compile_source_to_wiki(raw_file_path: str):
27    print(f"⚡ Przetwarzanie pliku: {raw_file_path}...")
28    
29    with open(raw_file_path, "r", encoding="utf-8") as f:
30        raw_content = f.read()
31        
32    wiki_context = get_current_wiki_context()
33    system_instruction = open("/Users/mpendolski/Obsidian/llm-wiki/AGENTS.md", "r", encoding="utf-8").read()
34
35    user_prompt = f"""
36    Zintegruj poniższy materiał źródłowy z moim Wiki.
37    
38    {wiki_context}
39    
40    MATERIAŁ ŹRÓDŁOWY DO PRZETWORZENIA:
41    {raw_content}
42    """
43
44    response = client.models.generate_content(
45        model='gemini-2.5-flash',
46        contents=user_prompt,
47        config=types.GenerateContentConfig(
48            system_instruction=system_instruction,
49            temperature=0.1,  
50            response_mime_type="application/json",
51            response_schema=WikiCompilationResult,
52        ),
53    )
54
55    result: WikiCompilationResult = response.parsed
56    # [Dalsza część skryptu zapisuje wygenerowane pliki Markdown i aktualizuje log]

Odpytywanie bazy bez halucynacji (odpytanie_bazy.py) #

Zamiast wyszukiwania semantycznego po fragmentach (chunkach), skrypt ładuje pełną zawartość skompilowanych notatek z Obsidiana wprost do okna kontekstowego Gemini. System prompt ściśle ogranicza model do dostarczonych danych i wymusza tagowanie pojęć (np. [[Nazwa Notatki]]). Niska temperatura (0.2) minimalizuje ryzyko halucynacji i zapewnia deterministyczne wyjście.

 1# --- Python ---
 2import os
 3import glob
 4from google import genai
 5from google.genai import types
 6from dotenv import load_dotenv
 7
 8load_dotenv()
 9client = genai.Client()
10
11def load_compiled_wiki() -> str:
12    """Ładuje PEŁNĄ zawartość wszystkich skompilowanych notatek wiki."""
13    full_wiki_content = ""
14    wiki_files = glob.glob("/Users/mpendolski/Obsidian/llm-wiki/wiki/*.md")
15    
16    for file_path in wiki_files:
17        if "log.md" in file_path:
18            continue
19            
20        with open(file_path, "r", encoding="utf-8") as f:
21            filename = os.path.basename(file_path).replace(".md", "")
22            full_wiki_content += f"\n=== NOTATKA: {filename} ===\n"
23            full_wiki_content += f.read() + "\n"
24            
25    return full_wiki_content
26
27def ask_wiki(question: str):
28    wiki_data = load_compiled_wiki()
29    
30    system_instruction = """
31    Jesteś ekspertem analizującym bazę wiedzy użytkownika. 
32    Twoje odpowiedzi muszą opierać się WYŁĄCZNIE na dostarczonych notatkach z sekcji KOMPILACJA WIKI.
33    Jeśli w bazie nie ma informacji potrzebnych do odpowiedzi, zwróć komunikat: 'Brak danych w wiki'.
34    Kiedy odwołujesz się do pojęcia, które istnieje w bazie, użyj formatu [[Nazwa Notatki]], tak jak w Obsidianie.
35    """
36    
37    prompt = f"""
38    KOMPILACJA WIKI:
39    {wiki_data}
40    
41    PYTANIE UŻYTKOWNIKA:
42    {question}
43    """
44    
45    response = client.models.generate_content(
46        model='gemini-3.5-flash',
47        contents=prompt,
48        config=types.GenerateContentConfig(
49            system_instruction=system_instruction,
50            temperature=0.2, 
51        ),
52    )
53    print(response.text)

Podsumowanie #

LLM Wiki automatyzuje asymilację luźnych notatek i zamienia je w ustrukturyzowaną bazę Markdown, pozwalając na analizę danych i wyciąganie wniosków w oparciu o pełny kontekst projektowy. Cały udostępniony kod znajdziesz w repozytorium: GitHub - llm_wiki.

Marcin Pendolski
Autor
Marcin Pendolski
Zapraszam do kontaktu i wymiany doświadczeń w obszarze Data & AI!