1
0
forked from KEMT/zpwiki

Compare commits

..

14 Commits

9 changed files with 12572 additions and 2 deletions

View File

@ -0,0 +1,51 @@
---
title: Dries Huybens
published: true
taxonomy:
category: [erasmus]
tag: [nlp, ie, rag, medical]
author: Daniel Hladek
---
ERASMUS Intern Summer 2026, 20 July - 30 August
Topic:
Multilingual Knowledge Graphs from medical data
Goal:
- Construct a knowledge graph from medical package inserts in multiple languages
- Utilize the graph in an intelligent agent that recommends medication.
Tasks:
- Continue project of [Bogdan Paul Chis](/interns/bogdan_paul_chis)
- Study repositories:
- https://github.com/chis-facultate/erasmus-kosice
- https://github.com/hladek/mul-me-kg
- https://github.com/hkuds/lightrag
- Learn intelligent agents and generative models - OpenAI API, Agent frameworks, RAG systems.
- Learn about knowledge graphs and GraphRAG. Read several research papers.
- Prepare a Python based workflow, use git code repository
- Visualize the graph
- Prepare an agent that utilizes the unstructured data and graph-data.
- Evaluate the agent using DeepEval or RAGAS.
- Write a report
- Put all code to GIT
Project tasks update:
- Prepare a multilingual parallel corpus from OPUS data
- Prepare dataset for visual language model evaluation from foto
- Prepare dataset for visual language model evaluation from wikipedia. You can use https://huggingface.co/datasets/wikimedia/wit_base .
- Prepare corpus of text and image data from pravda.sk . For each subdomain, create - raw HTML data with images. Then create corpus of images plus descriptions, subtitles and tags. Then create corpus of extracted text. You can use docling , trafilatura for text extration. Or design your own parser. Give source codes to a git repository with documentation. Put data files to school server. Do not download too fast, so our IP does not reveive a ban.
Outupus:
- multilingual corpus was analyzed with embedding models. Semantic overlap between languages is low, so multilngual parallel corpus will be small.
- A [corpus](https://huggingface.co/datasets/driesaster/fotkyzadarmo_slovak) from FotkyZadarmo.sk

Binary file not shown.

View File

@ -0,0 +1,442 @@
#!/usr/bin/env python3
import argparse
import csv
import gzip
import hashlib
import re
import shutil
import sys
import time
import xml.etree.ElementTree as ET
from pathlib import Path
import duckdb
import pyarrow as pa
import pyarrow.parquet as pq
LANGS = ["cs", "hr", "pl", "sk", "sl"]
TEXT_EXTS = ("en", *LANGS)
FILENAME_RE = re.compile(
r"^(?P<corpus>.+)\.(?P<langpair>[a-z]{2}-[a-z]{2})\.(?P<ext>en|cs|hr|pl|sk|sl)$"
)
# md5 of the normalised English text. Normalisation is surface-level only:
# trim, collapse internal whitespace runs, casefold. No punctuation or
# semantic folding -- those would merge genuinely distinct strings.
NORMALISE = r"lower(regexp_replace(trim(en_text), '\s+', ' ', 'g'))"
MERGE_KEY = f"md5({NORMALISE})"
# ---------------------------------------------------------------------------
# stage 1: extract
# ---------------------------------------------------------------------------
def read_lines(path: Path) -> list[str]:
with open(path, encoding="utf-8") as fh:
return [line.rstrip("\n") for line in fh]
def drop_separator_pairs(en: list[str], tgt: list[str]) -> tuple[list[str], list[str]]:
"""
Remove line pairs blank on BOTH sides.
Some exports insert a blank line into both files at once as a document
or domain separator -- CCAligned.cs-en has 255 of them, exactly matching
its 255 <linkGrp> elements. They are not links and must go before line N
can equal link N.
Only *simultaneously* blank pairs are dropped. A real link with a missing
translation is blank on one side only, so genuine data cannot be caught
by this.
"""
keep_en, keep_tgt = [], []
for e, t in zip(en, tgt):
if e == "" and t == "":
continue
keep_en.append(e)
keep_tgt.append(t)
return keep_en, keep_tgt
def _side_is_english(doc_path: str) -> bool:
"""OPUS doc paths are language-prefixed: 'en/foo.xml.gz', 'cs/bar/baz.xml.gz'."""
return doc_path.split("/", 1)[0] == "en"
def parse_ids_file(path: Path) -> list[tuple[int, int]]:
"""
Tab-separated: doc_a, doc_b, ids_a, ids_b -- ids space-separated within
a column. Returns (n_english_ids, n_target_ids) per link.
"""
out = []
with open(path, encoding="utf-8") as fh:
for line in fh:
parts = line.rstrip("\n").split("\t")
if len(parts) < 4:
parts = re.split(r"\s{2,}", line.rstrip("\n"))
if len(parts) < 4:
out.append((0, 0)) # unparseable -> never counts as clean
continue
doc_a, _doc_b, ids_a, ids_b = parts[0], parts[1], parts[2], parts[3]
n_a, n_b = len(ids_a.split()), len(ids_b.split())
out.append((n_a, n_b) if _side_is_english(doc_a) else (n_b, n_a))
return out
def parse_xml_file(path: Path) -> list[tuple[int, int]]:
"""
XCES alignment. Streamed with iterparse and cleared as we go -- some of
these files are 500MB+ and a full DOM is not affordable.
Multiple <linkGrp> elements are concatenated in document order, matching
how the flat text file numbers its lines globally.
"""
out = []
english_left = True
for event, elem in ET.iterparse(str(path), events=("start", "end")):
if event == "start" and elem.tag == "linkGrp":
english_left = _side_is_english(elem.get("fromDoc", ""))
elif event == "end":
if elem.tag == "link":
sides = elem.get("xtargets", "").split(";")
if len(sides) != 2:
out.append((0, 0))
else:
a, b = len(sides[0].split()), len(sides[1].split())
out.append((a, b) if english_left else (b, a))
elem.clear()
elif elem.tag == "linkGrp":
elem.clear()
return out
def extract_one(root: Path, corpus: str, langpair: str) -> pa.Table:
a, b = langpair.split("-")
lang = b if a == "en" else a
en_path = root / f"{corpus}.{langpair}.en"
tgt_path = root / f"{corpus}.{langpair}.{lang}"
xml_path = root / f"{corpus}.{langpair}.xml"
ids_path = root / f"{corpus}.{langpair}.ids"
en_raw, tgt_raw = read_lines(en_path), read_lines(tgt_path)
if len(en_raw) != len(tgt_raw):
raise ValueError(
f"text files disagree: en={len(en_raw)} {lang}={len(tgt_raw)}"
)
en_lines, tgt_lines = drop_separator_pairs(en_raw, tgt_raw)
n = len(en_lines)
if xml_path.exists():
counts, align_source = parse_xml_file(xml_path), "xml"
elif ids_path.exists():
counts, align_source = parse_ids_file(ids_path), "ids"
else:
counts, align_source = [(1, 1)] * n, "none"
if len(counts) != n:
raise ValueError(
f"{align_source} has {len(counts)} links but {n} usable text lines "
"-- refusing to pair them positionally"
)
# A link whose English side is empty has no reachable key: drop it.
# A link with English present but the translation empty is kept -- the
# English key survives and that language is simply NULL.
keep = [i for i in range(n) if en_lines[i].strip() != ""]
return pa.table({
"corpus": pa.array([corpus] * len(keep)),
"langpair": pa.array([langpair] * len(keep)),
"lang": pa.array([lang] * len(keep)),
"line_no": pa.array([i + 1 for i in keep], type=pa.int64()),
"en_text": pa.array([en_lines[i] for i in keep]),
"lang_text": pa.array(
[tgt_lines[i] if tgt_lines[i].strip() != "" else None for i in keep]
),
"en_n_ids": pa.array([counts[i][0] for i in keep], type=pa.int32()),
"lang_n_ids": pa.array([counts[i][1] for i in keep], type=pa.int32()),
"clean": pa.array([counts[i] == (1, 1) for i in keep]),
"align_source": pa.array([align_source] * len(keep)),
})
def discover(root: Path, exclude: set[str]) -> list[tuple[str, str]]:
found: dict[tuple[str, str], set[str]] = {}
for path in root.iterdir():
if not path.is_file():
continue
m = FILENAME_RE.match(path.name)
if not m:
continue
corpus, langpair, ext = m["corpus"], m["langpair"], m["ext"]
if corpus in exclude:
continue
if "en" not in langpair.split("-"):
continue
found.setdefault((corpus, langpair), set()).add(ext)
combos = []
for (corpus, langpair), exts in found.items():
a, b = langpair.split("-")
target = b if a == "en" else a
if "en" in exts and target in exts:
combos.append((corpus, langpair))
return sorted(combos)
def stage_extract(root: Path, out_dir: Path, exclude: set[str], report_path: Path) -> None:
out_dir.mkdir(parents=True, exist_ok=True)
combos = discover(root, exclude)
print(f"[extract] {len(combos)} corpus x langpair combos "
f"({len(exclude)} corpora excluded)")
rows = []
for i, (corpus, langpair) in enumerate(combos, 1):
dest = out_dir / f"{corpus}.{langpair}.parquet"
if dest.exists():
print(f"[extract] {i}/{len(combos)} {corpus}.{langpair} -- already done")
continue
t0 = time.time()
tmp = dest.with_suffix(".parquet.partial")
try:
table = extract_one(root, corpus, langpair)
pq.write_table(table, tmp)
tmp.replace(dest) # atomic: a partial file never looks complete
n = table.num_rows
n_clean = sum(table.column("clean").to_pylist())
src = table.column("align_source")[0].as_py() if n else ""
pct = round(100 * n_clean / n, 2) if n else 0.0
print(f"[extract] {i}/{len(combos)} {corpus}.{langpair} -- "
f"{n:,} rows, {pct}% clean, {src}, {time.time()-t0:.0f}s")
rows.append(dict(corpus=corpus, langpair=langpair, status="ok",
rows=n, clean_pct=pct, align_source=src, error=""))
except Exception as exc:
tmp.unlink(missing_ok=True)
print(f"[extract] {i}/{len(combos)} {corpus}.{langpair} -- FAILED: {exc}")
rows.append(dict(corpus=corpus, langpair=langpair, status="failed",
rows=0, clean_pct=0.0, align_source="", error=str(exc)))
if rows:
write_header = not report_path.exists()
with open(report_path, "a", newline="", encoding="utf-8") as fh:
w = csv.DictWriter(fh, fieldnames=["corpus", "langpair", "status", "rows",
"clean_pct", "align_source", "error"])
if write_header:
w.writeheader()
w.writerows(rows)
n_fail = sum(1 for r in rows if r["status"] == "failed")
print(f"[extract] {len(rows)-n_fail} ok, {n_fail} failed -> {report_path}")
# ---------------------------------------------------------------------------
# stage 2: bucket
# ---------------------------------------------------------------------------
def stage_bucket(con, out_dir: Path, keyed_dir: Path, n_buckets: int) -> None:
"""
One streaming pass. Every row gets its merge_key and a bucket number.
Rows sharing a merge_key always land in the same bucket, so each bucket
can later be merged in isolation with no cross-bucket reconciliation.
This is a projection plus a write -- no aggregation, so it does not build
the large hash table that made the single-query merge run out of memory.
"""
print(f"[bucket] partitioning into {n_buckets} buckets -> {keyed_dir}/")
t0 = time.time()
staging = keyed_dir.with_name(keyed_dir.name + ".partial")
if staging.exists():
shutil.rmtree(staging)
con.execute(f"""
COPY (
SELECT
{MERGE_KEY} AS merge_key,
en_text, lang, lang_text, clean,
en_n_ids, lang_n_ids, corpus, langpair,
abs(hash({MERGE_KEY})) % {n_buckets} AS bucket
FROM read_parquet('{out_dir}/*.parquet')
) TO '{staging}' (FORMAT PARQUET, PARTITION_BY (bucket), OVERWRITE_OR_IGNORE true)
""")
staging.replace(keyed_dir)
print(f"[bucket] done in {time.time()-t0:.0f}s")
# ---------------------------------------------------------------------------
# stage 3: merge
# ---------------------------------------------------------------------------
def per_language_columns() -> str:
"""
For each language: pick ONE source row and take its text, flag, grouping
counts and corpus from that same row.
This deliberately avoids separate any_value() calls per field. Those are
independent aggregates -- given several candidate rows for one key and
language (exactly what happens with common boilerplate), each call may
pick a different row, so the clean flag could describe a translation that
was not the one kept. Packing the fields into a struct and unpacking after
aggregation keeps them from a single row.
max() on a struct compares field-by-field in declaration order, so putting
is_clean first makes a strict 1:1 link win over a grouped one, with the
text as a deterministic tiebreak. That is a policy choice, not a neutral
one: among equally valid candidates it prefers the cleanly-aligned link.
Three counters accompany each language:
<lang>_n_rows how many source rows supplied a translation
<lang>_n_distinct how many DIFFERENT translations among them
<lang>_n_corpora how many distinct corpora contributed
n_distinct is the one that matters for quality: = 1 means every source
agreed, so the pick was arbitrary only between identical strings. The gap
between n_rows and n_distinct measures pure duplication -- on a sample it
ran ~80%, largely because several corpora in the download appear to be the
same data under different names. n_corpora separates "one corpus repeated"
from "many corpora agreeing".
"""
parts = []
for lang in LANGS:
pick = (
f"max(CASE WHEN lang = '{lang}' THEN struct_pack("
f"is_clean := clean, txt := lang_text, "
f"n_en := en_n_ids, n_tgt := lang_n_ids, src := corpus) END)"
)
parts.append(f"{pick}.txt AS {lang}_text")
parts.append(f"{pick}.is_clean AS {lang}_clean")
parts.append(f"{pick}.n_en AS {lang}_en_n_ids")
parts.append(f"{pick}.n_tgt AS {lang}_n_ids")
parts.append(f"{pick}.src AS {lang}_corpus")
parts.append(
f"count(*) FILTER (WHERE lang = '{lang}' AND lang_text IS NOT NULL) "
f"AS {lang}_n_rows"
)
parts.append(
f"count(DISTINCT CASE WHEN lang = '{lang}' THEN lang_text END) "
f"AS {lang}_n_distinct"
)
parts.append(
f"count(DISTINCT CASE WHEN lang = '{lang}' AND lang_text IS NOT NULL "
f"THEN corpus END) AS {lang}_n_corpora"
)
return ",\n ".join(parts)
def stage_merge(con, keyed_dir: Path, merged_dir: Path, n_buckets: int) -> None:
merged_dir.mkdir(parents=True, exist_ok=True)
cols = per_language_columns()
total = 0
for i in range(n_buckets):
src_dir = keyed_dir / f"bucket={i}"
dest = merged_dir / f"bucket_{i:04d}.parquet"
if dest.exists():
total += con.execute(f"SELECT count(*) FROM '{dest}'").fetchone()[0]
continue
if not src_dir.exists() or not any(src_dir.glob("*.parquet")):
print(f"[merge] bucket {i} empty, skipping")
continue
t0 = time.time()
tmp = dest.with_suffix(".parquet.partial")
con.execute(f"""
COPY (
SELECT
merge_key,
any_value(en_text) AS en_text,
{cols}
FROM read_parquet('{src_dir}/*.parquet')
GROUP BY merge_key
) TO '{tmp}' (FORMAT PARQUET)
""")
tmp.replace(dest)
n = con.execute(f"SELECT count(*) FROM '{dest}'").fetchone()[0]
total += n
print(f"[merge] bucket {i}/{n_buckets-1}: {n:,} keys, {time.time()-t0:.0f}s")
print(f"[merge] {total:,} unique English keys across {n_buckets} buckets")
# ---------------------------------------------------------------------------
# summary
# ---------------------------------------------------------------------------
def summarise(con, merged_dir: Path) -> None:
glob = f"{merged_dir}/*.parquet"
total = con.execute(f"SELECT count(*) FROM read_parquet('{glob}')").fetchone()[0]
print(f"\n{'='*64}\nunique English keys: {total:,}\n{'='*64}")
print(f"{'lang':<6}{'covered':>16}{'%':>8}{'clean':>16}{'ambiguous':>14}")
for lang in LANGS:
cov, clean, ambiguous = con.execute(f"""
SELECT count(*) FILTER (WHERE {lang}_text IS NOT NULL),
count(*) FILTER (WHERE {lang}_clean),
count(*) FILTER (WHERE {lang}_n_distinct > 1)
FROM read_parquet('{glob}')
""").fetchone()
pct = 100 * cov / total if total else 0
print(f"{lang:<6}{cov:>16,}{pct:>7.1f}%{clean:>16,}{ambiguous:>14,}")
print("\n'ambiguous' = keys where sources supplied genuinely DIFFERENT "
"translations.\nFilter on <lang>_n_distinct = 1 for the unambiguous "
"subset, <lang>_clean for strict 1:1 links.\nNote: <lang>_clean is "
"inflated by design -- the tiebreak prefers clean candidates.")
# ---------------------------------------------------------------------------
def main() -> None:
ap = argparse.ArgumentParser(description=__doc__,
formatter_class=argparse.RawDescriptionHelpFormatter)
ap.add_argument("--root", required=True, type=Path,
help="directory holding the OPUS plain-text download")
ap.add_argument("--work", default=Path("build"), type=Path,
help="working directory for all intermediate and final output")
ap.add_argument("--buckets", type=int, default=128,
help="raise if a bucket runs out of memory during merge")
ap.add_argument("--memory-limit", default="100GB",
help="keep comfortably under the container's ceiling")
ap.add_argument("--exclude", default="",
help="comma-separated corpus names to skip entirely, e.g. "
"MultiHPLT,MultiCCAligned,MultiParaCrawl,MultiMaCoCu")
ap.add_argument("--force-stage", default="", choices=["", "bucket", "merge"],
help="redo a stage that is already on disk")
args = ap.parse_args()
work = args.work
out_dir = work / "extracted"
keyed_dir = work / "keyed"
merged_dir = work / "merged"
tmp_dir = work / "duckdb_tmp"
for d in (work, tmp_dir):
d.mkdir(parents=True, exist_ok=True)
exclude = {c.strip() for c in args.exclude.split(",") if c.strip()}
con = duckdb.connect()
con.execute("PRAGMA enable_progress_bar=true")
con.execute(f"SET memory_limit='{args.memory_limit}'")
con.execute(f"SET temp_directory='{tmp_dir}'")
con.execute("SET preserve_insertion_order=false")
started = time.time()
stage_extract(args.root, out_dir, exclude, work / "extract_report.csv")
if args.force_stage == "bucket" and keyed_dir.exists():
shutil.rmtree(keyed_dir)
if keyed_dir.exists():
print(f"[bucket] {keyed_dir}/ present -- skipping "
f"(--force-stage bucket to redo)")
else:
stage_bucket(con, out_dir, keyed_dir, args.buckets)
if args.force_stage == "merge" and merged_dir.exists():
shutil.rmtree(merged_dir)
stage_merge(con, keyed_dir, merged_dir, args.buckets)
summarise(con, merged_dir)
print(f"\ntotal wall time {(time.time()-started)/60:.1f} min")
print(f"query with: SELECT * FROM read_parquet('{merged_dir}/*.parquet')")
if __name__ == "__main__":
main()

File diff suppressed because it is too large Load Diff

View File

@ -16,6 +16,9 @@ Návrh na tému:
Prepis reči pre tvorbu štruktúrovaného zdravotného záznamu Prepis reči pre tvorbu štruktúrovaného zdravotného záznamu
Repo https://git.kemt.fei.tuke.sk/ap565wq/diplomova_praca
Ciele: Ciele:
- Vytvorte systém pre prepis reči a naplnenie formulára pomocou lokálnych jazykových modelov - Vytvorte systém pre prepis reči a naplnenie formulára pomocou lokálnych jazykových modelov
@ -37,7 +40,27 @@ Zásobník úloh:
- Vyskúšajte ako funguje rozpoznávanie reči cez OPeWEBUI. Navrhnute zlepšenia. - Vyskúšajte ako funguje rozpoznávanie reči cez OPeWEBUI. Navrhnute zlepšenia.
- Ako vieme zistiť, ktoré informácie nám chýbajú? - Ako vieme zistiť, ktoré informácie nám chýbajú?
Stretnutie 10.6.2026
Stav:
- prepísaný kód rozhrania do knižnice next js.
- použitie lokálnych modelov cez ollama, zatiaľ qwen3-4B beží na PC. Model nejde veľmi dobre.
- Na PC beží aj lokálny Whisper - funguje oveľa horšie.
- aplikácia je kontajnerizovaná - docker compose.
Úlohy:
- Oboznámte sa s postupmi pre dotrénovanie jazykového modelu - LORA, PEFT.
- Oboznámte sa s metódami Information Extraction. Vyhľadajte si články na túto tému a napíšte, aké metódy sa používajú. Vstupom je text v prir. jazyku, výstupom je niečo ako JSON. Napíšte si poznámky.
- Vyhľadajte články o podobných prístupoch - ako rečovo naplniť formulár.
- Zistite podrobnosti o procese tvorby formulára "Záznam o zhodnotení zdravotného stavu osoby". Získajte vzor. Zistite otázky ktoré sú dôležité.
Zásobník úloh:
- Zostavte testovací scenár a testovaciu množinu.
- Nasadte aplikáciu na školskej infraštruktúre a využite kvalitnejšie jazykové modely a modely pre rozpoznávanie reči.
- Implementujte mechanizmus spätnej väzby - kontrola správnosti a doplnenie chýbajúcich hodnôt.
## Bakalárska práca 2025 ## Bakalárska práca 2025

View File

@ -21,9 +21,42 @@ Ciele na semester:
- Dotrénujte a vyhodnotte Slovak Mistral. - Dotrénujte a vyhodnotte Slovak Mistral.
Stretnutie 10.6.2026
Stav:
- kódy sú na servri titan
- funguje dotrénovanie Slovak Mistral pomocou Slovak Alpaca na Titan, pomocou unsloth aj LlamaFactory. Používa sa qlora.
- po dotrénovaní to je ručne vyskúšané. Nevie odborné výrazy. Model rozumie jednoduchým inštrukciám. Model je ukecaný.
Úlohy:
- Vytvorte GIT repozitár a dajte tam kódy.
- Pre LLmamaFactory dávajte na GIT konfigurácie.
- Rozšírte trénovaciu sadu - o zdroje v https://github.com/slovak-nlp/resources Zatiaľ najlepšie vyzerá byť CohereLabs/aya_collection_language_split
- Model zverejnite na HuggingFace hube.
- Napíšte si poznámky o aktuálnych metódach PEFT a SFT. Preštudujte si vedecké články z Google Scholar.
- Vyhodnotte model pomocu lm-evaluation-harness. Pozrite si výsledky https://wandb.ai/hladek/lmeval?nw=nwuserhladek
Príkaz na vyhodnotenie je
```
/home/dh343ko/miniconda3/envs/transformers/bin/lm-eval --model hf --model_args pretrained=google/mt5-large --tasks arc_sk,hellaswag_sk,m_mmlu_sk,truthfulqa_sk_mc1,truthfulqa_sk_mc2,sklegal,skquad --output_path zzz --wandb_args project=lmeval_mt5-large --device cuda:0 --batch_size 8
```
Zásobník úloh:
- Možno bude potrebné použiť lepší HW.
- Zlepšite proces vyhodnotenia. Dá sa použiť sk bech ktorý je v príprave.
- Zistite, čo je to zarovnanie jazykových modelov. Pozrite si framework huggingface trl. Zistite, čo je to meóda DPO a RLHF. Ku tomu existuje DP práca Hyrenko.
- Strojovo preložte vybranú množinu.
- Vytvorte github repozitár so skriptami pre dotrénovanie jazykovéo modelu.
Stretnutie 27.2. Stretnutie 27.2.
- Obozn8mte sa problematikou podľa zadaných zdrojov. - Oboznámte sa problematikou podľa zadaných zdrojov.
- Pozrite si https://allenai.org/olmo - Pozrite si https://allenai.org/olmo
Úlohy: Úlohy:

View File

@ -30,8 +30,27 @@ Zásobník úloh:
- Zistite, čo je to znalostný graf - Zistite, čo je to znalostný graf
- Naučte sa čo je to GraphRAG - Naučte sa čo je to GraphRAG
- Využite znalostný graf pre zlepšenie práce alebo vysvetliteľnosti jazkového modelu - Využite znalostný graf pre zlepšenie práce alebo vysvetliteľnosti jazykového modelu
Stretnutie 8.6.2026
Stav:
- Odovzdané nejaké zdrojové kódy na https://git.kemt.fei.tuke.sk/jp170na/dp-zp-agent - načítanie z markdown, indexovanie do SQLite a FastAPI.
- Ostatné úlohy neboli vyriešené.
Úlohy:
- Pokračujte v otvorených úlohách. Vypracujte písomnú správu o preštudovaných materiáloch.
- Sústredte sa na GraphRAG. Použite google scholar a https://graphrag.com/
- Pozrite si kódy na https://github.com/hladek/kemthesis
- Pozrite si systém https://github.com/hkuds/minirag
Zásobník úloh:
- Nové smerovanie môže byť spracovanie textov záverečných prác. Vytvorte RAG systém pre vyhľadávanie v záverečných prácach.
- Napojte sa na systém CRZP a prepojte ho s LLM agentom.
- Vytvorte vyhľadávanie v dodaných textoch záverečných prác.
Stretnutie 20.2.2026 Stretnutie 20.2.2026