Strona główna / Artykuły

GraphRAG od początku do końca z FalkorDB, embeddingami oraz routowaniem LangGraph

Hybrydowy proces GraphRAG na zbiorze danych filmowych: zapytania grafowe FalkorDB, embeddingi MiniLM do wyszukiwania wektorowego oraz routowanie LangGraph decydujące o wybranej ścieżce.

Wydawca: REACTAPP.TOP12 min czytania2737 słów

Źródła: A Complete End-to-End Guide to Implementing a GraphRAG Workflow Using FalkorDB, Hugging Face, Groq, LangChain, and LangGraph

Wielkie modele językowe odpowiadają płynnie na podstawie danych treningowych, ale napotykają trudności, gdy pytanie wymaga nowej struktury: kto reżyserował dany film, którzy aktorzy wystąpili razem, jakie połączenia łączą dwie jednostki. Systemy GraphRAG eliminują tę lukę, łącząc wyszukiwanie wektorowe w tekście niestrukturyzowanym z wyraźnymi zapytaniami grafowymi dotyczącymi relacji, a następnie pozwalając modelowi LLM rozumować na podstawie obu tych elementów.

To przewodnictwo tworzy działający proces pracy na danych filmowych: FalkorDB jako magazyn grafów, embeddingi Hugging Face MiniLM do semantycznego odzyskiwania informacji, modele hostowane na Groq do kierowania zapytaniami i generowania odpowiedzi, narzędzia LangChain do obsługi procesu wyszukiwania oraz LangGraph do koordynacji całego procesu. Celem nie jest pokaz graficzný, lecz wzorzec, który można dostosować do każdej dziedziny, w której relacje są równie ważne co akapity.

Dlaczego grafy łączą się z wektorami

Wyszukiwanie wektorowe znajduje podobne fragmenty. Zapytania grafowe wyszukują określone ścieżki. Jeśli zapytasz o filmy reżyserowane przez Christophera Nolana, krawędź grafu dostarczy dokładniejszy wynik niż przypuszczenie oparte na najbliższym sąsiedzie. Jeśli zapytasz o „filmowe przygody pełne niesamowitych zwrotów akcji, podobne do Inception”, wektory okazują się niezwykle przydatne. Prawdziwi asystenci potrzebują zarówno jednego, jak i drugiego rozwiązania, plus routera, który decyduje, którą ścieżkę wybrać – lub kiedy użyć wersji hybrydowej.

MATCH (d:Director {name: 'Christopher Nolan'})-[:DIRECTED]->(m:Movie) RETURN m.title
MATCH (m:Movie)-[r]->(d:Director) RETURN m, r, d

Środowisko i FalkorDB

Stwórz wirtualne środowisko, zainstaluj Jupyter, jeśli chcesz pracować w notatniku eksploracyjnym, i ustal stałe wersje bibliotek. Interfejs LangChain może ulegać zmianom; ustalanie stałych wersji jest elementem zapewnienia powtarzalności wyników.

$ pip install jupyterlab
$ jupyter lab
# 1. SETUP WSL (Run in Windows PowerShell as Administrator)
wsl --install
# Restart your computer if prompted, then create your Linux username/password.
# Inside the WSL/Ubuntu terminal, update packages:
sudo apt update && sudo apt upgrade -y

# 2. SET ENVIRONMENT VARIABLES (Paste your keys from Groq & Hugging Face)
export GROQ_API_KEY="your_groq_api_key_here"
export HUGGINGFACEHUB_API_TOKEN="your_hf_token_here"

# 3. ENVIRONMENT & DEPENDENCY SETUP
# Install Python pip and virtual environment tools
sudo apt install python3-pip python3-venv -y

# Create and activate a project directory
mkdir movie-graphrag && cd movie-graphrag
python3 -m venv venv
source venv/bin/activate


# Verify everything is correctly configured
echo "Groq Key: $GROQ_API_KEY"
echo "HF Token: $HUGGINGFACEHUB_API_TOKEN"

Zainstaluj FalkorDB lokalnie za pomocą Dockera lub wykorzystaj zarządzaną instancję, jeśli to odpowiada potrzebom twojego zespołu. Lokalny Docker sprawia, że eksperymenty są tańsze:

docker run -p 6379:6379 -p 3000:3000 -it --rm -v ./data:/data falkordb/falkordb:edge

Po usunięciu starszych instalacji LangChain zainstaluj dokładnie tę samą kolekcję bibliotek, przy użyciu której przeprowadzono testy tego workflow.

pip uninstall langchain langchain-core langchain-community langchain-groq \
              langchain-huggingface langgraph -y

pip install falkordb pandas pydantic sentence-transformers
pip install "langchain==0.3.26"
pip install "langchain-core==0.3.68"
pip install "langchain-community==0.3.23"
pip install "langchain-groq==0.2.5"
pip install "langchain-huggingface==0.1.2"
pip install "langgraph==0.3.34"

Ładuj zbiór danych w stylu IMDB

Pobierz plik CSV z filmami w stylu Top-1000, a następnie załaduj go przy użyciu funkcji importu oraz konfiguracji połączenia wymaganej przez twój notatnik:

import os, re, sys, logging
import pandas as pd
from typing import List, TypedDict, Literal
from falkordb import FalkorDB
from langchain_community.document_loaders.csv_loader import CSVLoader
from langchain_community.vectorstores.falkordb_vector import FalkorDBVector
from langchain_community.graphs import FalkorDBGraph
from langchain_community.chains.graph_qa.falkordb import FalkorDBQAChain
from langchain_core.documents import Document
from langchain_core.prompts import ChatPromptTemplate
from langchain.chains import create_retrieval_chain
from langchain.chains.combine_documents import create_stuff_documents_chain
from langchain.output_parsers import PydanticToolsParser
from pydantic import BaseModel, Field          # pydantic v2 — not langchain_core.pydantic_v1
from langchain_huggingface import HuggingFaceEmbeddings  # not langchain.embeddings (deprecated)
from langchain_groq import ChatGroq
from langgraph.graph import END, StateGraph

GROQ_API_KEY = "your_groq_api_key"
CSV_PATH     = "imdb_top_1000.csv"
os.environ["HF_TOKEN"] = "your_hf_token"

logging.basicConfig(stream=sys.stdout, level=logging.WARNING)

# Connect to FalkorDB and select the graph
db    = FalkorDB(host='localhost', port=6379)
graph = db.select_graph('imdb')

data = pd.read_csv(CSV_PATH)

Budowanie grafu wiedzy

Stwórz węzły filmów z takimi atrybutami jak tytuł, rok wydania i certyfikat. Stwórz również węzły reżyserów i gwiazd. Połącz je za pomocą sprecyzowanych relacji, aby zapytania w stylu Cypher mogły odpowiadać na pytania dotyczące obsady i ekipy, bez konieczności polegania na tym, że embedding zapamiętał tę relację.

def clean_column(column_data):
    """Remove special characters that would break Cypher queries."""
    return column_data.apply(
        lambda x: re.sub(r"[^a-zA-Z0-9\s]", "", x) if pd.notnull(x) else 'NA'
    )

for col in ['Series_Title', 'Director', 'Certificate', 'Runtime', 'Genre',
            'Overview', 'Star1', 'Star2', 'Star3', 'Star4']:
    data[col] = clean_column(data[col])

data['IMDB_Rating']   = data['IMDB_Rating'].fillna('NA')
data['Released_Year'] = pd.to_numeric(data['Released_Year'], errors='coerce').fillna(-1)

star_columns = ['Star1', 'Star2', 'Star3', 'Star4']

for index, row in data.iterrows():
    movie_title   = row['Series_Title']
    director_name = row['Director']
    released_year = int(row['Released_Year']) if row['Released_Year'] != -1 else None

    if released_year is None:
        print(f"Skipping '{movie_title}' — invalid year.")
        continue

    movie_node = (
        f"MERGE (m:Movie {{"
        f"title: '{movie_title}', year: {released_year}, "
        f"certificate: '{row['Certificate']}', runtime: '{row['Runtime']}', "
        f"genre: '{row['Genre']}', imdb_rating: {row['IMDB_Rating']}, "
        f"description: '{row['Overview']}'"
        f"}})"
    )
    director_node     = f"MERGE (d:Director {{name: '{director_name}'}})"
    directed_relation = "MERGE (d)-[:DIRECTED]->(m)"

    star_nodes, starred_relations = "", ""
    for s, star_col in enumerate(star_columns, start=1):
        star_name = row[star_col]
        if star_name != 'NA':
            star_nodes        += f"MERGE (s{s}:Star {{name: '{star_name}'}})\n"
            starred_relations += f"MERGE (s{s})-[:STARRED_IN]->(m)\n"

    cypher_query = (
        f"{movie_node} {director_node} {directed_relation} "
        f"{star_nodes.strip()}\n{starred_relations.strip()}"
    )
    graph.query(cypher_query)

print("Knowledge Graph created.")

Embeddingi do semantycznego wyszukiwania

Zamiast przestarzałych ścieżek importu, użyj all-MiniLM-L6-v2 poprzez nowoczesny pakiet integracji Hugging Face:

embedding_model = HuggingFaceEmbeddings(
    model_name="sentence-transformers/all-MiniLM-L6-v2"
)

result = graph.query(
    "MATCH (m:Movie) RETURN m.title AS title, m.description AS description"
)

documents = [
    Document(
        page_content=row[1] if row[1] else "",
        metadata={"title": row[0], "description": row[1]}
    )
    for row in result.result_set if row[1]
]

vector_index = FalkorDBVector(host='localhost', port=6379, embedding=embedding_model)
vector_index.add_documents(documents)
print("Vector index created.")

Zachowaj wektory obok grafu lub w zgodzie z jego strukturą, aby węzły hybrydowe mogły pobierać tytuły filmów z wyszukiwania semantycznego, a następnie rozwijać je za pomocą relacji.

GraphState dla LangGraph

Zdefiniuj TypedDict (lub równoważny) typu GraphState, który przechowuje pytanie użytkownika, decyzję dotyczącą ścieżki, podzapytania, wyniki wyszukiwania wektorowego, odpowiedzi grafu oraz ostateczną odpowiedź. LangGraph zachowuje ten stan pomiędzy węzłami, dzięki czemu kolejne kroki mogą ponownie wykorzystać kontekst zamiast rozpoczynać się od pustego promptu.

class GraphState(TypedDict):
    """
    Tracks state across the entire LangGraph workflow.
    Every node reads from and writes to this dictionary.
    """
    question:            str
    documents:           object
    movie_titles:        List[str]
    subqueries:          object
    prompt_with_context: object

Kierowanie zapytaniami

Nie każde pytanie powinno trafiać do każdego podsystemu. Router określa, czy pytanie ma charakter głównie relacyjny, głównie semantyczny, hybrydowy lub wykracza poza zakres. Dzięki temu zmniejsza się opóźnienie i unika się sprzecznych informacji w kontekście.

llm = ChatGroq(groq_api_key=GROQ_API_KEY, model_name="llama-3.3-70b-versatile")

class RouteQuery(BaseModel):
    """Route a user query to the most relevant datasource."""
    datasource: Literal["vector search", "graph query"] = Field(
        ...,
        description="Route to 'vector search' for similarity queries, "
                    "'graph query' for relationship/fact queries.",
    )

structured_llm_router = llm.with_structured_output(RouteQuery)

route_system_prompt = """You are an expert at routing user questions.

Route to VECTOR SEARCH for questions involving:
- Similarity (similar, related, like, closest, relevant)
- Themes or topics (movies about space, about love, etc.)
- "Find movies similar to X"

Route to GRAPH QUERY for questions involving:
- Specific facts (who directed X, what year was X released)
- Specific people (movies by Christopher Nolan)
- Filtering by property (movies from 1990, Action movies)
"""

route_prompt    = ChatPromptTemplate.from_messages([
    ("system", route_system_prompt),
    ("human",  "{question}"),
])
question_router = route_prompt | structured_llm_router

def route_question(state: GraphState):
    source = question_router.invoke({"question": state["question"]})
    if source.datasource == "vector search":
        return "decomposer"
    else:
        return "graph_qa"

Rozbieracz

Gdy uruchamiana jest ścieżka hybrydowa, rozbieracz dzieli pytanie użytkownika na podzapytania, które mogą być przetworzone przez węzły wektorowe i grafowe:

class SubQuery(BaseModel):
    """A single sub-query decomposed from the original question."""
    sub_query: str = Field(
        ..., description="A unique paraphrasing of the original question.",
    )

decompose_system = """You are an expert at breaking down complex questions.
Given a user question, split it into exactly two sub-queries:
  1. A query for vector similarity search (find related/similar items)
  2. A query for a graph database (find specific relationships or facts)

Example:
Question: Find movies directed by Christopher Nolan related to dreams.
sub_query 1: Find movies related to dreams or the subconscious.
sub_query 2: Find movies directed by Christopher Nolan.
"""

decompose_prompt = ChatPromptTemplate.from_messages([
    ("system", decompose_system),
    ("human",  "{question}"),
])

llm_with_tools = llm.bind_tools([SubQuery])
parser         = PydanticToolsParser(tools=[SubQuery])
query_analyzer = decompose_prompt | llm_with_tools | parser

def decomposer(state: GraphState):
    question   = state["question"]
    subqueries = query_analyzer.invoke({"question": question})
    return {"subqueries": subqueries, "question": question}

Węzeł wyszukiwania wektorowego

Zaimplementuj wyszukiwanie podobieństwa przy użyciu dostępnych narzędzi do odzyskiwania danych w LangChain:

def vector_search(state: GraphState):
    question  = state["question"]
    retriever = vector_index.as_retriever(search_kwargs={"k": 3})

    qa_prompt = ChatPromptTemplate.from_messages([
        ("system",
         "You are a movie expert. Use the context below to answer the question.\n\n"
         "Context:\n{context}"),
        ("human", "{input}"),
    ])

    combine_docs_chain = create_stuff_documents_chain(llm, qa_prompt)
    retrieval_chain    = create_retrieval_chain(retriever, combine_docs_chain)

    result      = retrieval_chain.invoke({"input": question})
    source_docs = result.get("context", [])

    extracted_data, movie_titles = [], []
    for doc in source_docs:
        title       = doc.metadata.get("title", "Unknown")
        description = doc.metadata.get("description", "")
        extracted_data.append({"title": title, "description": description})
        movie_titles.append(title)

    return {
        "movie_titles": movie_titles,
        "documents":    extracted_data,
        "question":     question,
        "subqueries":   state["subqueries"],
    }

Węzeł QA grafowego

Tłumacz subpytania relacyjne na zapytania grafowe. W przypadku pytań o filmografię reżysera graf powinien zwracać tytuły połączone krawędziami typu „reżyserował”, a nie parafrazowane przypuszczenia:

def graph_qa(state: GraphState):
    question      = state["question"]
    graph_wrapper = FalkorDBGraph(database="imdb", host="localhost", port=6379)

    chain  = FalkorDBQAChain.from_llm(
        llm,
        graph=graph_wrapper,
        verbose=True,
        allow_dangerous_requests=True,
    )
    result = chain.invoke({"query": question})
    answer = result.get("result", str(result))

    return {"documents": answer, "question": question}

Pytania i odpowiedzi w grafie z kontekstem wektorowym

Węzeł hybrydowy bierze tytuły odzyskane semantycznie i rozszerza je o strukturę grafu – obsadę, ekipę, powiązane filmy – zanim model językowy napisze ostateczną odpowiedź:

def graph_qa_with_context(state: GraphState):
    titles      = state["movie_titles"]
    question    = state["question"]
    input_query = (
        f"Return the title, genre, director, and IMDB rating of movies "
        f"with these titles: {str(titles)}"
    )

    graph_wrapper = FalkorDBGraph(database="imdb", host="localhost", port=6379)
    chain  = FalkorDBQAChain.from_llm(
        llm,
        graph=graph_wrapper,
        verbose=True,
        allow_dangerous_requests=True,
        return_intermediate_steps=True,
    )
    result  = chain.invoke({"query": input_query})
    context = ""
    steps   = result.get("intermediate_steps", [])
    if len(steps) > 1:
        context = str(steps[1].get("context", ""))

    return {
        "documents": context,
        "subqueries": state["subqueries"],
        "question":   question,
    }

Węzeł odpowiadania

Znormalizuj ostateczną odpowiedź w języku naturalnym, dołącz informacje o pochodzeniu, jeśli to możliwe, oraz usuń pola pośrednie, których nie chcesz ujawnić klientowi:

def respond(state: GraphState):
    question = state["question"]
    context  = state["documents"]

    respond_prompt = ChatPromptTemplate.from_messages([
        ("system",
         "You are a helpful movie expert assistant.\n"
         "Use the context below to answer the user's question clearly and concisely.\n\n"
         "Context:\n{context}"),
        ("human", "{question}"),
    ])

    answer = (respond_prompt | llm).invoke({"context": context, "question": question})
    return {"documents": answer.content}

Układanie procesu LangGraph

Połącz węzły routera, dekompresora, wektorowego, grafowego, hybrydowego oraz odpowiadającego za pomocą krawędzi warunkowych, które odpowiadają etykietom trasy:

workflow = StateGraph(GraphState)

# Register all nodes
workflow.add_node("graph_qa",              graph_qa)
workflow.add_node("decomposer",            decomposer)
workflow.add_node("vector_search",         vector_search)
workflow.add_node("graph_qa_with_context", graph_qa_with_context)
workflow.add_node("respond",               respond)

# Conditional entry point — route_question decides the first node
workflow.set_conditional_entry_point(
    route_question,
    {
        "decomposer": "decomposer",
        "graph_qa":   "graph_qa",
    },
)

# Hybrid path
workflow.add_edge("decomposer",            "vector_search")
workflow.add_edge("vector_search",         "graph_qa_with_context")
workflow.add_edge("graph_qa_with_context", "respond")
workflow.add_edge("respond",               END)

# Direct graph path
workflow.add_edge("graph_qa", END)

app = workflow.compile()

Wizualizuj graf w celach dokumentacji i wprowadzania do pracy:

from IPython.display import Image, display

try:
    display(Image(app.get_graph().draw_mermaid_png()))
except Exception:
    pass

Test i konsola interaktywna

Zapewnij wykonywanie zapytań skryptowanych obejmujących przypadki relacyjne, semantyczne i hybrydowe:

# Graph query — specific fact question
result1 = app.invoke({"question": "Which movies did Christopher Nolan direct?"})
print(result1["documents"])

Następnie utwórz prosty pętla konsolową do ręcznego badania:

print("\n" + "=" * 60)
print("INTERACTIVE MODE — type a question (or 'quit' to exit)")
print("=" * 60)

while True:
    user_question = input("\nYour question: ").strip()
    if user_question.lower() in ("quit", "exit", "q"):
        print("Goodbye!")
        break
    if not user_question:
        continue
    try:
        result = app.invoke({"question": user_question})
        print("\nANSWER:", result["documents"])
    except Exception as e:
        print(f"Error: {e}")

Wyniki wymagające weryfikacji

P sprawdź, czy zapytania typu Nolan zwracają tytuły oparte na grafie, czy zapytania o klimat zwracają filmy semantycznie sensowne, oraz czy zapytania hybrydowe odnoszą się do obu typów dowodów. Gdy odpowiedzi odbiegają od oczekiwań, sprawdź zapisy stanu pomiędzy węzłami, a nie tylko końcową wartość.

Podsumowanie i wskazówki adaptacyjne

GraphRAG to zarówno problem routingu, jak i problem przechowywania danych. FalkorDB (lub każda baza danych grafowych o niskiej opóźnieniu) zapewnia ustrukturyzowane dane. Embeddingi umożliwiają dokładniejsze odnajdywanie informacji. LangGraph sprawia, że przepływ sterowania jest na tyle jawny, by można go było testować. Zastąp filmy elementami z Twojej dziedziny – częściami i dostawcami, pacjentami i protokołami, usługami i zależnościami – zachowując przy tym te same role węzłów: routowanie, pobieranie, rozszerzanie, reagowanie.

Zaznacz wersje, przechowuj zestaw idealnych pytań dla każdego typu trasy oraz rejestruj zmiany stanu. Ta dyscyplina operacyjna jest ważniejsza niż to, od jakiego zestawu danych demonstracyjnych zaczęto pracę.

Uwagi projektowe, które przetrwają kontakt z środowiskiem produkcyjnym

Rozwiązywanie problemów z identyfikacją elementów będzie kluczowe w rzeczywistych implementacjach. Tytuły filmów są stosunkowo proste do obsługi; nazwy firm i numery SKU produktów już nie. Planuj krok normalizacji przed tworzeniem węzłów i utrzymuj mapowanie od surowych ciągów znaków do kanonicznych identyfikatorów. Bez tego zapytania grafowe stają się niejasne, a wyniki wyszukiwania wskazują na duplikaty.

Ewolucja schematu wymaga migracji, tak jak w SQL. Dodanie nowego typu relacji jest proste w notatniku, ale trudne, gdy wykresy analityczne polegają na starych połączeniach. Wersjonuj swój schemat grafu i przechowuj go obok kodu procesów pracy.

Budżety opóźnień powinny być jasno określone. Wywołania modeli LLM przez routery dodają czas przed samym rozpoczęciem pobierania danych. W przypadku systemów o wysokiej liczbie zapytań na sekundę rozważ zasady lub proste klasyfikatory do identyfikacji oczywistych wzorców relacyjnych („kto reżyserował X”) i zachowaj routowanie za pomocą modeli LLM na teksty niejednoznaczne.

Ocena powinna oddzielać ocenę dokładności trasy od jakości odpowiedzi. Doskonała odpowiedź na niewłaściwej trasie nadal zużywa budżet i spowoduje awarię, gdy jeden z podsystemów przestanie działać. Przeprowadź test chaotyczny, wyłączając graf lub indeks wektorowy i upewniając się, że przepływ pracy faktycznie ulega pogorszeniu.

Bезpieczeństwo jest ważne: generowanie zapytań do grafu musi być ograniczone. Wolno preferować szablony z parametrami zamiast swobodnie skonstruowanych zapytań typu Cypher generowanych przez modele. Ta sama zasada, którą stosuje się w narzędziach SQL, ma tu zastosowanie.

Kontrola kosztów: przechowuj w pamięci tymczasowej reprezentacje dokumentów o stałej strukturze, przechowuj w pamięci tymczasowej decyzje routera dotyczące identycznych pytań przez krótki czas ważności, oraz unikaj rozszerzeń hybrydowych, gdy czysty graf już zwraca entytety o wysokim poziomie pewności.

Gdy wyjaśniasz to podczas rozmowy kwalifikacyjnej, narysuj najpierw obiekt stanu, a następnie krawędzie. Rozmówcy szybciej śledzą przepływ sterowania niż nazwy pakietów. Wspomnij o FalkorDB ze względu na szybkość obsługi grafów właściwości, o MiniLM w kontekście lokalnych embeddingów, a o Groq jedynie jako o jednym z możliwych hostów LLM – twoja architektura nie powinna być przytwierdzona do jednego dostawcy usług inferencji.

Na koniec należy udokumentować komunikaty o błędach, które będą widoczne u użytkowników, gdy wykres jest pusty lub router odmawia działania. Ciche „halucynacje” są powodem, dla którego demo GraphRAG tracą zaufanie już po pierwszym tygodniu. Zachowuj status zielony ustawień na poziomie trasy, zanim rozszerzysz schemat wykresu o nowe typy entytetów. Zachowuj status zielony ustawień na poziomie trasy, zanim rozszerzysz schemat wykresu o nowe typy entytetów. Zachowuj status zielony ustawień na poziomie trasy, zanim rozszerzysz schemat wykresu o nowe typy entytetów. Zachowuj status zielony ustawień na poziomie trasy, zanim rozszerzysz schemat wykresu o nowe typy entytetów. Zachowuj status zielony ustawień na poziomie trasy, zanim rozszerzysz schemat wykresu o nowe typy entytetów. Zachowuj status zielony ustawień na poziomie trasy, zanim rozszerzysz schemat wykresu o nowe typy entytetów. Zachowuj status zielony ustawień na poziomie trasy, zanim rozszerzysz schemat wykresu o nowe typy entytetów. Zachowuj status zielony ustawień na poziomie trasy, zanim rozszerzysz schemat wykresu o nowe typy entytetów. Zachowuj rout

Utrzymuj elementy na poziomie e na kolorze zielonym przed rozszerzeniem schematu grafu o nowe typy entytetów. Utrzymuj elementy na poziomie trasy na kolorze zielonym przed rozszerzeniem schematu grafu o nowe typy entytetów. Utrzymuj elementy na poziomie trasy na kolorze zielonym przed rozszerzeniem schematu grafu o nowe typy entytetów. Utrzymuj elementy na poziomie trasy na kolorze zielonym przed rozszerzeniem schematu grafu o nowe typy entytetów. Utrzymuj elementy na poziomie trasy na kolorze zielonym przed rozszerzeniem schematu grafu o nowe typy entytetów. Utrzymuj elementy na poziomie trasy na kolorze zielonym przed rozszerzeniem schematu grafu o nowe typy entytetów. Utrzymuj elementy na poziomie trasy na kolorze zielonym przed rozszerzeniem schematu grafu o nowe typy entytetów. Utrzymuj elementy na poziomie trasy na kolorze zielonym przed rozszerzeniem schematu grafu o nowe typy entytetów. Utrzymuj elementy na poziomie trasy na kolorze zielonym przed rozszerzeniem schematu grafu o nowe typy entytetów. Utrzymuj elementy na poziomie trasy na kolorze zielonym przed rozszerzeniem schematu grafu o nowe typy entytetów. Utrzymuj elementy na poziomie trasy na kolorze zielonym przed rozszerzeniem schematu grafu o nowe typy entytetów. Utrzymuj fi na poziomie trasy na kolorze zielonym.Utrzymuj teksty w kolorze zielonym przed rozszerzeniem schematu grafu o nowe typy entytetów. Utrzymuj elementy na poziomie trasy w kolorze zielonym przed rozszerzeniem schematu grafu o nowe typy entytetów. Utrzymuj elementy na poziomie trasy w kolorze zielonym przed rozszerzeniem schematu grafu o nowe typy entytetów. Utrzymuj elementy na poziomie trasy w kolorze zielonym przed rozszerzeniem schematu grafu o nowe typy entytetów. Utrzymuj elementy na poziomie trasy w kolorze zielonym przed rozszerzeniem schematu grafu o nowe typy entytetów. Utrzymuj elementy na poziomie trasy w kolorze zielonym przed rozszerzeniem schematu grafu o nowe typy entytetów. Utrzymuj elementy na poziomie trasy w kolorze zielonym przed rozszerzeniem schematu grafu o nowe typy entytetów. Utrzymuj elementy na poziomie trasy w kolorze zielonym przed rozszerzeniem schematu grafu o nowe typy entytetów. Utrzymuj elementy na poziomie trasy w kolorze zielonym przed rozszerzeniem schematu grafu o nowe typy entytetów. Utrzymuj elementy na poziomie trasy w kolorze zielonym przed rozszerzeniem schematu grafu o nowe typy entytetów. Utrzymuj elementy na poziomie trasy w kolorze zielonym przed rozszerzeniem schematu grafu o nowe typy entytetów. Utrzymuj elementy na poziomie trasy w kolorze zielonymTłumacz tekst źródłowy poniżej z języka angielskiego na polski. Wynik: Tylko tłumaczenie po polsku.