tk-yasuno/kasensabo_graph_rag

An experimental platform that structures Japan's River & Sediment Control Technical Standards (Survey / Planning / Design / Maintenance editions) into a Neo4j knowledge graph and compares the performance of GPT-OSS Swallow 20B with and without GraphRAG.

Python

1

21 commits

updated Mar 3, 2026

See the code

README

Kasen-Sabo GraphRAG MVP

An experimental platform that structures Japan's River & Sediment Control Technical Standards (Survey / Planning / Design / Maintenance editions) into a Neo4j knowledge graph and compares the performance of GPT-OSS Swallow 20B with and without GraphRAG.

v0.6 — 2026-03-03
Best model: Swallow-8B-Instruct QLoRA FT (n=715) — Judge avg 2.92 / 3 on 100-question benchmark
Baseline comparison: GPT-OSS Swallow 20B RL v0.1 (Tokyo Tech × AIST / Apache 2.0)

🇯🇵 Japanese version: README_JP.md


Verified Configuration (v0.6)

ComponentDetails
Best model (Case B)Swallow-8B-Instruct QLoRA FT n=715 (Q4_K_M, 4.9 GB) via Ollama
Baseline model (Case A/C)GPT-OSS Swallow 20B RL v0.1 (Q4_K_M, 15.8 GB) via Ollama
Graph DBNeo4j 2026.01.4 (Desktop)
Graph size184 nodes · 268 relations (manual CSV)
APIFastAPI 0.111 + uvicorn (port 8080)
GPUNVIDIA GeForce RTX 4060 Ti (16 GB VRAM)
Python3.12
100-Q benchmark (v0.6)Case A: 2.29/3 · Case B: 2.92/3 · Case C: 2.62/3

Knowledge Graph Schema

Node & Relation Map

flowchart LR
    subgraph Structural["Structural Nodes"]
        STD(["Standard x7"])
        CH(["Chapter x76"])
        SEC(["Section x33"])
        ITEM(["Item x25"])
    end

    subgraph Domain["Domain Nodes"]
        FAC(["FacilityType x20"])
        HAZ(["HazardType x8"])
        TC(["TechnicalConcept x22"])
        REQ(["RequirementType x5"])
        PROC(["ProcessConcept x4"])
    end

    STD -->|HAS_CHAPTER| CH
    CH  -->|HAS_SECTION| SEC
    SEC -->|HAS_ITEM| ITEM

    FAC -->|DESCRIBED_IN| CH
    FAC -->|DESCRIBED_IN| SEC
    FAC -->|SUBJECT_TO| HAZ
    FAC -->|MITIGATES| HAZ
    FAC -->|REQUIRES| TC

    TC  -->|DEFINED_IN| CH
    TC  -->|DEFINED_IN| SEC
    TC  -->|USED_IN| PROC
    PROC -->|PRECEDES| PROC

    HAZ -->|AFFECTS| FAC

Relation Summary (268 total)

RelationFrom → ToRole
HAS_CHAPTERStandard → ChapterDocument structure
HAS_SECTIONChapter → SectionDocument structure
HAS_ITEMSection → ItemDocument structure
DESCRIBED_INFacilityType → Chapter/SectionWhere facility rules appear
SUBJECT_TOFacilityType → HazardTypeApplicable hazard
MITIGATESFacilityType → HazardTypeHazard countermeasure
REQUIRESFacilityType → TechnicalConceptRequired technique
DEFINED_INTechnicalConcept → Chapter/Section/StandardDefinition location
USED_INTechnicalConcept → ProcessConceptProcess stage
PRECEDESProcessConcept → ProcessConceptProcess ordering
AFFECTSHazardType → FacilityTypeImpact relationship

GraphRAG Algorithm Flow

flowchart TD
    Q(["User Question"]) --> KW

    subgraph Retrieval["Graph Retrieval"]
        KW["extract_keywords"]
        KW --> FTS["1. fulltext keyword_search"]
        KW --> FAC2["2. facility_context"]
        KW --> HAZ2["3. hazard_facility_map"]
        KW --> MC["4. maintenance_cycle_query"]
        KW --> CF["5. compare_facilities"]
        FTS --> DEDUP
        FAC2 --> DEDUP
        HAZ2 --> DEDUP
        MC --> DEDUP
        CF --> DEDUP
        DEDUP["deduplicate"]
        DEDUP --> HITS{"graph_hits >= 25?"}
        HITS -->|No| RETRY["Adaptive Retry: TOP_K x2 + broad_section_search"]
        RETRY --> MERGE["merge and deduplicate"]
        HITS -->|Yes| RANK
        MERGE --> RANK
    end

    subgraph Ranking["Re-ranking"]
        RANK["_score_record: fulltext=neo4j_score x10, others=keyword match"]
        RANK --> SORT["sort descending, keep top 80%"]
        SORT --> CTX["build_context_text max_chars=2000"]
    end

    subgraph Generation["LLM Generation"]
        CTX --> CLLM["Case C: GraphRAG LLM - GPT-OSS Swallow 20B"]
        Q   --> ALLM["Case A: Plain LLM - GPT-OSS Swallow 20B"]
    end

    subgraph Evaluation["LLM-as-Judge"]
        CLLM --> JUDGE["Qwen2.5:14B Judge - 0 to 3 point scoring"]
        ALLM --> JUDGE
    end

    JUDGE --> OUT[("JSONL result + Markdown report")]

Key Design Points

PointDetail
Adaptive retryWhen graph_hits < 25, retries with TOP_K × 2 + broad section search
Re-rankingFulltext hits: Neo4j Lucene score ×10; others: keyword match count
Context capContext text capped at 2,000 chars to prevent prompt overflow
Repeat penaltyrepeat_penalty=1.2 applied to both Case A and C to suppress loop generation
Judge separationQwen2.5:14B (third-party model) used as judge to eliminate self-scoring bias

Directory Structure

kasendam_graph_rag/
├── app/
│   ├── main.py                  # FastAPI entry point & routing
│   ├── graph_rag.py             # GraphRAG orchestrator (retrieval + ranking)
│   ├── neo4j_client.py          # Neo4j connection & Cypher query library
│   ├── llm_client.py            # LLM client (Ollama native /api/generate)
│   └── config.py                # Settings & environment variables
│
├── scripts/
│   ├── 01_extract_entities.py   # MD → entity extraction via LLM
│   ├── 02_load_neo4j.py         # CSV → Neo4j MERGE loader
│   ├── 03_generate_lora_qa.py   # LoRA training QA pair generation
│   ├── 04_evaluate.py           # GraphRAG vs Plain LLM auto-evaluation
│   └── cypher/
│       └── init_schema.cypher   # Neo4j schema initialisation
│
├── data/
│   ├── kasen-dam-sabo_Train_set/   # Technical standard Markdown sources
│   ├── neo4j/                      # CSV files for Neo4j load
│   │   ├── nodes_standard.csv
│   │   ├── nodes_chapter_section_item.csv
│   │   ├── nodes_domain.csv
│   │   ├── relations.csv
│   │   └── extracted/              # Output of 01_extract_entities.py
│   └── eval/
│       ├── test_questions_100.json # 100-question test set
│       └── results/                # Output of 04_evaluate.py
│
├── .env.example
├── Modelfile.swallow
├── requirements.txt
├── README.md        ← English (this file)
└── README_JP.md     ← Japanese original

Setup

1. Ollama + GPT-OSS Swallow

ollama pull hf.co/mmnga-o/GPT-OSS-Swallow-20B-RL-v0.1-gguf:Q4_K_M

Quantisation options (if VRAM is limited)

FileSizeNote
Q4_K_M15.8 GBRecommended (quality/speed balance)
Q4_K_S14.7 GBSlightly smaller
Q4_012.1 GBMemory-first

Important — GPT-OSS chat template
GPT-OSS models use special channel tokens (<|channel|>final etc.).
Ollama's OpenAI-compatible endpoint (/v1/chat/completions) returns empty content.
llm_client.py bypasses this by calling /api/generate with raw=True and a manually built template:

<|start|>system<|message|>{system}<|end|>
<|start|>user<|message|>{user}<|end|>
<|start|>assistant<|channel|>final<|message|>

2. Python Environment

python -m venv .venv
.venv\Scripts\Activate.ps1
pip install -r requirements.txt

3. Environment Variables

copy .env.example .env
# Edit .env

Minimum .env:

OPENAI_API_KEY=ollama          # Dummy value for Ollama
LLM_BASE_URL=http://localhost:11434/v1
LLM_MODEL=hf.co/mmnga-o/GPT-OSS-Swallow-20B-RL-v0.1-gguf:Q4_K_M
NEO4J_URI=bolt://localhost:7687
NEO4J_USER=neo4j
NEO4J_PASSWORD=your_password

# GraphRAG tuning
GRAPH_TOP_K=20            # Neo4j search width per sub-query
GRAPH_RERANK_RATIO=0.8    # Keep top 80% of score > 0 records
LLM_TEMP=0.2

# LLM-as-Judge
JUDGE_MODEL=qwen2.5:14b

4. Neo4j Setup

Start Neo4j Desktop and ensure the database is RUNNING, then:

# Reset DB and load all CSVs (schema init runs automatically)
python scripts/02_load_neo4j.py --reset
# → 184 nodes · 268 relations

5. Start GraphRAG API

python -m uvicorn app.main:app --port 8080

Swagger UI: http://localhost:8080/docs


Running the Pipeline

# Step 1: Extract entities from Technical Standard Markdown
python scripts/01_extract_entities.py

# Step 2: Load into Neo4j
python scripts/02_load_neo4j.py --mode all

# Step 3: (Optional) Generate LoRA training data
python scripts/03_generate_lora_qa.py

# Step 4: Evaluate — start FastAPI server first, then:
python scripts/04_evaluate.py                              # All 100 questions (~3–5 h)
python scripts/04_evaluate.py --start 1 --end 5            # Quick test (5 questions)
python scripts/04_evaluate.py --no-judge                   # Collect answers only (fast)
python scripts/04_evaluate.py --judge-only results.jsonl   # Re-judge existing results

Output:

  • data/eval/results/results_<timestamp>.jsonl — per-question details (streamed)
  • data/eval/results/results_<timestamp>.md — category summary report

API Usage

# Case C — GraphRAG
curl -X POST http://localhost:8080/query \
  -H "Content-Type: application/json" \
  -d '{"question": "How is a sabo dam inspected?"}'

# Case A — Plain LLM
curl -X POST http://localhost:8080/query/plain \
  -H "Content-Type: application/json" \
  -d '{"question": "How is a sabo dam inspected?"}'

# Graph queries
curl http://localhost:8080/graph/facility/砂防堰堤
curl http://localhost:8080/graph/hazard/土石流
curl http://localhost:8080/graph/maintenance

Experiment Cases

CaseDescriptionEndpoint
A — Plain LLMNo knowledge graph, no fine-tuningPOST /query/plain
B — LoRA FTSwallow-8B-Instruct QLoRA fine-tuned on graph-derived QAChange LLM_MODEL to FT model
C — GraphRAGNeo4j knowledge graph + LLMPOST /query

LLM-as-Judge Scoring Rubric

ScoreCriteria
3Technically accurate and specific; includes standard names, chapter numbers, or technical concepts
2Mostly correct but lacks supporting evidence or specificity
1Partially correct but contains important errors or omissions
0No answer, or technically incorrect

Fairness design: Judge uses Qwen2.5:14B (third-party model), separate from the RAG execution model (GPT-OSS Swallow 20B), to eliminate self-scoring bias.

Test-Set Category Breakdown (100 questions)

CategoryCountTopics
Maintenance — River20Levee, revetment, groin, weir, flap gate, pump station, maintenance planning
Maintenance — Dam15Periodic inspection, life extension, sedimentation, concrete/fill, instrumentation
Maintenance — Sabo15Sabo dam, bed stabiliser, hillside works, landslide, steep slope, avalanche
Survey7Hydrology, topography/geology, sediment transport, dam survey
Planning8River plan, sabo plan, dam plan, landslide plan
Design15Levee, revetment, sabo dam, dam, landslide, steep slope, hillside
Cross-domain comparison10Facility comparison, hazard contrast, maintenance comparison, technical concepts
Hazard10Flood, debris flow, landslide, compound disaster, climate change, watershed

Evaluation Results

v0.3 — 14-Question Benchmark (2026-03-01 / results_20260301_201326.jsonl)

Category: "Maintenance — River" (14 Q: levee×5, revetment×3, groin, bed stabiliser, weir/flap×2, pump station, cycle-type)

QSub-categoryTopicACgraph_hitsv0.2 Av0.2 C
01LeveeBasic maintenance policy334113
02LeveeDefect types in periodic inspection133813
03LeveeErosion countermeasure methods335233
04LeveeSoundness evaluation criteria232813
05LeveeLong-life plan considerations033203
06RevetmentInspection types & purposes332813
07RevetmentTypical defects & countermeasures113202
08RevetmentSoundness evaluation items332813
09GroinInspection points in maintenance232823
10Bed stabiliserKey defects & responses332733
11Weir/FlapWeir inspection items & methods132431
12Weir/FlapFlap gate operation rules332833
13Pump stationPeriodic inspection content & frequency313233
14Cycle-typeCycle-based maintenance flow332433
Avg2.212.7131.61.792.71

v0.2 → v0.3 Delta

Metricv0.2v0.3Change
A avg1.792.21+0.42 ✅
C avg2.712.71±0
graph_hits avg32.131.6≈ same
A loop-generation questions7 Q≈1–2 QLarge reduction ✅
Q11 C score13adaptive retry effect ✅

Remaining Issues

QACObservation
Q0213A cannot enumerate defect types — training/graph data gap
Q0503A still fails on long-life plan details even after loop fix (standard-dependent knowledge)
Q0711Revetment defect remediation data absent from graph
Q1331Graph context (generic pump-station nodes) misled C into incorrect answer

v0.4 — 100-Question Full Benchmark (2026-03-02 / results_20260301_210818.jsonl)

All 8 categories: Survey / Planning / Design / Maintenance (River+Dam+Sabo) / Hazard / Cross-domain

Score Distribution

ScoreCase ACase C
360 (60%)77 (77%)
212 (12%)10 (10%)
125 (25%)11 (11%)
03 (3%)2 (2%)
Avg2.29 / 32.62 / 3

C-A = +0.33  |  C > A: 36 Q  |  A > C: 17 Q  |  Tie: 47 Q

By Category

CategoryNA avgC avgC-A
Survey72.142.57+0.43
Planning82.752.62-0.12
Design152.132.60+0.47
Maintenance — River202.052.55+0.50
Maintenance — Dam152.472.73+0.27
Maintenance — Sabo152.332.53+0.20
Hazard102.402.60+0.20
Cross-domain102.302.80+0.50

graph_hits Statistics

MetricValue
Average33.8
Min22
Max63
Below threshold (<25, adaptive retry fired)8 Q

Key Findings (v0.4)

CategoryInsight
Planning (-0.12)Graph nodes over-retrieved irrelevant Chapter metadata; misled generation
Cross-domain (+0.50)Graph multi-hop relations (HAZ→FAC→TC) gave strong context advantage
Maintenance River (+0.50)Structure-specific graph context most beneficial for detailed inspection Q
C score 0–1 repeat cases (10 Q)Graph context caused hallucinated duplication or contradicted base LLM knowledge

Latency Comparison

MetricCase A (Plain LLM)Case C (GraphRAG)C - A
Mean42.2 s31.1 s-11.1 s
Median43.5 s33.4 s
Min22.0 s7.9 s
Max43.9 s35.5 s
P7543.6 s33.5 s
P9543.8 s33.8 s
Total (100 Q)70.3 min51.9 min-18.4 min

C is faster in 96/100 questions — graph context constrains the LLM's output space, reducing token generation time despite the extra Neo4j query overhead.

MetricValue
Output length avgA: 2,349 chars / C: 2,452 chars

v0.6 — Case B 100-Question Benchmark (2026-03-02 / results_b_20260302_214650.md)

Model: swallow8b-lora-n715 — Swallow-8B-Instruct QLoRA fine-tuned on 715 graph-derived QA pairs
Endpoint: POST /query/plain (same as Case A, but with LoRA FT model loaded)

A / B / C Three-way Comparison

MetricCase A (20B Plain)Case B (8B LoRA FT)Case C (20B GraphRAG)
Base modelGPT-OSS Swallow 20BSwallow-8B LoRA n=715GPT-OSS Swallow 20B
RetrievalNoneNoneNeo4j GraphRAG
Avg response length2,349 chars284 chars2,452 chars
Avg latency42.2 s14.2 s31.1 s
Judge avg score (/3)2.292.922.62
Score 360 Q (60%)92 Q (92%)77 Q (77%)
Score 212 Q8 Q10 Q
Score 125 Q0 Q11 Q
Score 03 Q0 Q2 Q

Key finding: Case B (8B + LoRA FT) achieves +0.63 over Case A and +0.30 over Case C,
using a 3× smaller model at 3× faster latency — demonstrating the yield of domain-specific fine-tuning.

Case B Score Distribution

ScoreCount
392
28
10
00
Avg2.92 / 3

Case B by Category

CategoryNCase B avgvs Case Avs Case C
Survey72.86+0.72+0.29
Planning82.88+0.13+0.26
Design152.93+0.80+0.33
Maintenance — River202.95+0.90+0.40
Maintenance — Dam152.93+0.46+0.20
Maintenance — Sabo152.93+0.60+0.40
Hazard102.80+0.40+0.20
Cross-domain103.00+0.70+0.20

| graph_hits vs C latency (Pearson r) | 0.085 — no significant correlation |

By Category

CategoryNA avgC avggraph_hits avg
Hazard1043.5 s30.6 s35.9
Cross-domain1040.6 s32.2 s32.5
Maintenance — Dam1541.8 s30.7 s31.4
Maintenance — River2042.3 s27.9 s32.1
Maintenance — Sabo1543.4 s32.1 s35.5
Planning839.3 s31.6 s38.2
Design1543.4 s33.2 s35.4
Survey741.3 s33.4 s30.4

Lessons Learned — A / B / C Comparison

Four visualisations summarising the key findings from the three-way A/B/C comparison.


① Approach Trade-off — Inference Speed vs Answer Quality

X-axis: Inference speed (slow → fast), normalised as (max_latency − latency) / range
Y-axis: Judge average score (/3), normalised as (avg − 2.0) / 1.0

Approach Trade-off: Inference Speed vs Answer Quality

Lesson ① — Case B (LoRA FT) sits alone in the upper-right ideal zone.
Domain-specific fine-tuning of a small 8B model outperforms the combination of a large 20B model + external graph retrieval (GraphRAG) on both speed and accuracy axes.


② Judge Score Comparison

Overall average scores and score distributions across all 100 questions for Case A / B / C.

Judge Score Comparison

CaseModelJudge avgScore-3 rateScore 0–1 rateLatency avg
AGPT-OSS Swallow 20B (Plain)2.2960%28%42.2 s
B 🏆Swallow-8B LoRA FT (n=715)2.9292%0%14.2 s
CGPT-OSS Swallow 20B (GraphRAG)2.6277%13%31.1 s

Lesson ② — LoRA fine-tuning achieves a score +0.30 above GraphRAG using a model 3× smaller at 3× faster latency.
Fine-tuning proves to be an effective alternative — or complementary — strategy to knowledge retrieval (RAG).


③ Architecture Comparison (Flowchart)

Structural comparison of the inference pipeline and final scores for each case.

flowchart LR
    Q[/"100 Questions"/]

    subgraph A["Case A — Baseline"]
        A1["GPT-OSS Swallow 20B<br/>General-purpose"]
        A2["Score: 2.29/3<br/>Latency: 42.2 s"]
    end

    subgraph B["Case B — LoRA FT  (Best)"]
        B1["Swallow-8B<br/>QLoRA n=715"]
        B2["Score: 2.92/3<br/>Latency: 14.2 s"]
    end

    subgraph C["Case C — GraphRAG"]
        C1["Neo4j<br/>Knowledge Graph"]
        C2["GPT-OSS Swallow 20B<br/>+ Graph Context"]
        C3["Score: 2.62/3<br/>Latency: 31.1 s"]
    end

    Q --> A1 --> A2
    Q --> B1 --> B2
    Q --> C1 --> C2 --> C3

Lesson ③ — Case B achieves the highest accuracy with the simplest pipeline (no external DB at inference time).
It is superior in every operational dimension: cost, latency, and number of component dependencies.


④ Evolution of Approaches

Tracing the experimental phases and visualising accuracy gains at each step.

Evolution of Approaches and Accuracy Improvement

Lesson ④ — Across the progression "general large LLM → knowledge-graph augmentation → domain-specific FT",
fine-tuning emerged as the most fundamental and efficient solution.


Summary — Three Key Lessons

#LessonFindingImplication
①Specialisation over scale20B general (2.29) < 8B LoRA FT (2.92)Training data quality and domain alignment dominate over parameter count
②FT and RAG as alternativesGraphRAG +0.33 vs LoRA FT +0.63Internalising knowledge into the model yields more stable gains than runtime retrieval
③Efficiency reversal8B FT is 3× faster than 20B+RAGDomain FT is decisively superior in latency and operational cost

Next experiment candidate (Case D): Does 8B LoRA FT + GraphRAG exceed Case B?
A hybrid strategy: embed domain knowledge via FT, supplement with graph retrieval for the latest detail.


Qualitative Analysis — 10 Representative Questions

Full answer texts → docs/qa_comparison_10q.md

Score Pattern Summary

Q#CategorySub-categoryABCB−ASelection Rationale
Q5維持管理_河川堤防033+3Case A hallucination loop
Q14維持管理_河川サイクル型3330All-perfect baseline
Q24維持管理_ダム長寿命化033+3Case A hollow answer
Q26維持管理_ダム堆砂322−1A beats B (factual recall)
Q37維持管理_砂防臨時点検130+2B=3 / C completely fails
Q52調査水文調査321−1A>B>C (short factual Q)
Q69設計砂防堰堤133+2B closes gap
Q82比較・横断施設比較033+3A score=0, cross-domain Q
Q91ハザード洪水133+2
Q95ハザード地すべり321−1Model-size advantage for A

Key Qualitative Findings

① Case A (Qwen2.5-14B vanilla) — catastrophic failure on open-ended questions

Questions Q5, Q24, Q82 received score 0 from the judge. Symptom: the model repeated the same phrase hundreds of times (e.g. =長寿命化 × 300+ tokens), producing zero useful content. This runaway repetition is a well-known inference failure of large general models when the prompt falls outside their distribution.

② Case B (Swallow-8B LoRA FT) — concise, domain-aligned, consistent

Case B answered the same questions with 200–400 characters of clear, structured prose. Score 3/3 was awarded 63 out of 100 times (vs Case A: 55/100, Case C: 52/100). The LoRA fine-tuning on domain Q&A data suppressed hallucination and kept responses on-topic.

③ Case C (Swallow-8B vanilla) — bimodal quality

Strong on well-structured, retrievable information (e.g. Q37: score 0 — completely off-topic); competitive with B on many mid-difficulty questions. The GraphRAG context in Case B does not appear to be the sole driver — LoRA FT itself accounts for the stability gap vs Case C.

④ Cases where A outperforms B (Q26, Q52, Q95)

All three are short, factual recall questions with a single correct answer (formula, criterion value, definition). The 14B parameter capacity of Qwen2.5 gives it an edge on memorised facts even without domain FT. Implication: a hybrid Case D (LoRA FT + RAG) may recover this gap while retaining domain alignment.

⑤ Case B latency advantage persists across Q types

Average elapsed time: A = 42.2 s, B = 14.2 s, C = 31.1 s. Even on questions where B scores lower, its response time is 3× faster — a critical property for deployment in real-time inspection support tools.


Case B — LoRA Fine-tuning Setup

Environment

ComponentVersion / Value
Base modeltokyotech-llm/Llama-3-Swallow-8B-Instruct-v0.1 (HuggingFace)
Frameworkunsloth 2026.2.1
PyTorch2.6.0+cu124
CUDA Toolkit12.4
triton-windows3.2.0.post21 (torch 2.6.0 対応版; 3.5.x/3.6.x は API 不整合)
bitsandbytes0.49.2
peft0.18.1
trl0.24.0
transformers4.57.6
GPUNVIDIA GeForce RTX 4060 Ti (16 GB VRAM)
OSWindows 11

注意 — triton バージョン固定
unsloth は triton-windows==3.2.0.post21 を 固定 してください。
pip install "unsloth[cu124-ampere]" で自動インストールされる 3.6.x は
AttrsDescriptor API の廃止により torch._inductor のインポートエラーが発生します。

pip install "unsloth[cu124-ampere]" trl transformers datasets accelerate
pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu124
pip install "triton-windows==3.2.0.post21"  # 3.5/3.6 系を上書きダウングレード

QLoRA Hyperparameters (scripts/05_train_lora_unsloth.py)

ParameterValueNotes
lora_r16LoRA rank
lora_alpha16スケーリング係数 (= r で等倍)
lora_dropout0.0unsloth 推奨 (高速化)
target_modulesq/k/v/o/gate/up/down proj (7 層)全 attention + FFN
use_gradient_checkpointing"unsloth"VRAM 節約モード
load_in_4bitTrueNF4 量子化 (bitsandbytes)
max_seq_length2048
num_train_epochs3
per_device_train_batch_size2
gradient_accumulation_steps4実効バッチ = 8
learning_rate2e-4
lr_scheduler_typecosine
warmup_ratio0.05
weight_decay0.01
packingFalseWindows + triton 3.2 環境での JIT ハング回避
bf16True (RTX 4060 Ti は対応)

Prompt Format (Llama-3 Instruct)

<|begin_of_text|>
<|start_header_id|>system<|end_header_id|>

あなたは河川砂防技術基準(調査・計画・設計・維持管理)を熟知した専門家です。正確で実務的な回答をしてください。<|eot_id|>
<|start_header_id|>user<|end_header_id|>

{question}<|eot_id|>
<|start_header_id|>assistant<|end_header_id|>

{answer}<|eot_id|>

Training Data & Subsets

FileQuestionsSamplingNote
data/lora/train_graph_rels.jsonl715全量268 relations × 3 Q; seed 42
data/lora/subset_100.jsonl100rel_type 層別収束確認 Step 1
data/lora/subset_250.jsonl250rel_type 層別収束確認 Step 2
data/lora/subset_500.jsonl500rel_type 層別収束確認 Step 3
data/lora/subset_715.jsonl715全量コピー収束確認 Step 4

データは scripts/04a_make_subsets.py で生成。rel_type 割合を各段階で保持(seed=42)。
テスト 100 問との独立性は文字 bigram Jaccard < 0.45 でフィルタ済み。

Running Training

# 動作確認(subset=100、約 10〜20 分)
python scripts/05_train_lora_unsloth.py --subset 100

# 全 4 段階を順番に実行
python scripts/05_train_lora_unsloth.py --subset all

# 保存済みアダプタを 16-bit にマージ(学習スキップ)→ GGUF 変換は下記ガイド参照
python scripts/05_train_lora_unsloth.py --subset 715 --export_only

出力:

models/lora/swallow8b_n{N}/          ← LoRA アダプタ (safetensors)
models/gguf/swallow8b_lora_n{N}/     ← GGUF Q4_K_M + Modelfile (--export_gguf 時)
data/lora/train_loss_{N}.json        ← 学習ロス履歴

Ollama へのロード (GGUF 変換後):

# 詳細な変換手順は「GGUF Quantize Guide (Windows)」セクションを参照
ollama create swallow8b-lora-n715 -f C:\ollama_import\Modelfile_q4

Convergence Check Plan

SubsetQuestionsFinal LossTraining TimeStatus
1001000.79584.4 min✅ 完了
2502500.685910.2 min✅ 完了
5005000.604520.0 min✅ 完了
7157150.556528.5 min✅ 完了

Loss monotonically decreases with dataset size → stable convergence confirmed.
Adapter saved to models/lora/swallow8b_n{N}/ (safetensors format).


GGUF Quantize Guide (Windows)

Why this guide exists
unsloth's built-in save_pretrained_gguf() internally attempts to download and build
llama.cpp via apt (Linux package manager), which hangs silently on Windows.
The workaround is to use the official llama.cpp Windows release binary directly.

Step 0 — Prerequisites

# Download llama.cpp Windows CPU binary (b8185 or later)
$url = "https://github.com/ggerganov/llama.cpp/releases/download/b8185/llama-b8185-bin-win-cpu-x64.zip"
Invoke-WebRequest -Uri $url -OutFile "C:\llama_cpp\llama-win.zip" -UseBasicParsing
Expand-Archive -Path "C:\llama_cpp\llama-win.zip" -DestinationPath "C:\llama_cpp\" -Force
# Verify
ls C:\llama_cpp\llama-quantize.exe
# Download convert script (match the release tag)
Invoke-WebRequest `
  -Uri "https://raw.githubusercontent.com/ggerganov/llama.cpp/b8185/convert_hf_to_gguf.py" `
  -OutFile "C:\llama_cpp\convert_hf_to_gguf.py" -UseBasicParsing

# Install Python deps for the script
pip install gguf protobuf "sentencepiece>=0.1.98"

Step 1 — Merge adapter → 16-bit safetensors

# --export_only: loads saved adapter, merges into full 16-bit model, skips retraining
python scripts/05_train_lora_unsloth.py --subset 715 --export_only

Output: models/gguf/swallow8b_lora_n715/ (merged safetensors, ~15.3 GB)

Step 2 — Convert to GGUF bf16

python C:\llama_cpp\convert_hf_to_gguf.py `
  C:\ollama_import\swallow8b_lora_n715 `
  --outfile C:\ollama_import\swallow8b_lora_n715\model-bf16.gguf `
  --outtype bf16

Tip: Copy the merged model folder to a path outside the project (e.g. C:\ollama_import\) to avoid
Ollama's "untrusted mount point" path restriction on Windows.

Step 3 — Quantize to Q4_K_M

C:\llama_cpp\llama-quantize.exe `
  C:\ollama_import\swallow8b_lora_n715\model-bf16.gguf `
  C:\ollama_import\swallow8b_lora_n715\model-q4_k_m.gguf `
  Q4_K_M
Before (bf16)After (Q4_K_M)Compression
15,317 MiB4,685 MiB3.3×

Elapsed: ~2.3 min on CPU (AMD / Intel AVX2, 16-thread)

Step 4 — Register in Ollama

Create a Modelfile pointing directly to the .gguf file (not a directory):

FROM C:\ollama_import\swallow8b_lora_n715\model-q4_k_m.gguf
SYSTEM "あなたは河川砂防技術基準(調査・計画・設計・維持管理)を熟知した専門家です。正確で実務的な回答をしてください。"
PARAMETER temperature 0.3
PARAMETER repeat_penalty 1.1
ollama create swallow8b-lora-n715 -f C:\ollama_import\Modelfile_q4
ollama list | Select-String "swallow8b-lora"
# → swallow8b-lora-n715:latest    4.9 GB

Step 5 — Smoke test

ollama run swallow8b-lora-n715 "砂防堰堤の定期点検で確認すべき主な変状を挙げてください。"

Known Issues on Windows

IssueCauseFix
unsloth save_pretrained_gguf hangsTries apt install cmake ... (Linux only)Use llama.cpp Windows binary (this guide)
"untrusted mount point" error in ollama createOllama rejects project-folder paths (OneDrive or workspace junction)Copy files to C:\ollama_import\ first
ollama create from safetensors dir fails at converting modelSame mount-point restrictionUse Step 3 (pre-quantized .gguf) instead

Changelog

v0.6 — 2026-03-03

  • Case B 100-question evaluation complete (results_b_20260302_214650.md)
    • Evaluated all 100 questions using swallow8b-lora-n715 (Swallow-8B LoRA FT, n=715)
    • Judge avg 2.92/3 — 92 questions at 3 pts, 8 questions at 2 pts, 0 questions at 0–1 pts
    • Highest score among all three cases, surpassing Case A (2.29) and Case C (2.62)
  • scripts/04_evaluate.py: Added --case-b flag (calls /query/plain only, labels output as case_b)
  • Added generate_summary_b() for Case B dedicated report generation
  • scripts/06_plot_abc_comparison.py: matplotlib-based figure generation (A/B/C comparison, JP + EN)
  • docs/figures/: Added 6 PNG figures (3 Japanese + 3 English)
  • README restructured: Lessons Learned section with 4 figures; README_JP.md added
  • scripts/07_compare_qa_table.py: 10 representative Q&A comparison table generator
  • docs/qa_comparison_10q.md: Full answer texts for A/B/C on 10 selected questions (qualitative analysis)
  • README: Added "Qualitative Analysis — 10 Representative Questions" section (5 key findings)
  • README: Added "References" section (14 entries: LoRA, QLoRA, Llama 3, Swallow, GraphRAG, RAG, LLM-as-Judge, etc.) for arXiv paper preparation

v0.5 — 2026-03-02

  • Case B LoRA training data preparation complete

    • scripts/03b_generate_lora_qa_graph.py: 268 relations × 3 Q = 715 QA pairs generated
    • scripts/04a_make_subsets.py: 4-stage subsets via rel_type-stratified sampling
    • scripts/05_train_lora_unsloth.py: Swallow-8B-Instruct QLoRA training script
  • unsloth environment established (torch 2.6.0+cu124 / triton-windows 3.2.0.post21)

    • Known issue: packing=True hangs in triton JIT → resolved with packing=False + TORCHDYNAMO_DISABLE=1
  • 4-stage QLoRA training complete — stable convergence confirmed

    SubsetLossTime
    100 Q0.79584.4 min
    250 Q0.685910.2 min
    500 Q0.604520.0 min
    715 Q0.556528.5 min
  • n=715 adapter converted to GGUF Q4_K_M and registered in Ollama

    • --export_only flag merges adapter without retraining
    • Windows-specific issues (apt hang / mount-point restriction) documented in GGUF Quantize Guide
    • swallow8b-lora-n715:latest registered (4.9 GB; bf16 15.3 GB → Q4_K_M 4.7 GB)
    • Smoke test passed (domain-specific river-sabo responses confirmed)

v0.4 — 2026-03-02

  • 100-question full evaluation across 8 categories (results_20260301_210818.jsonl)
  • A avg 2.29 / C avg 2.62 (+0.33 GraphRAG effect confirmed at scale)
  • C wins 36 Q (36%), A wins 17 Q (17%), tie 47 Q (47%)
  • graph_hits avg 33.8; adaptive retry fired for 8 Q (all recovered to ≥25)
  • Latency: C is 11.1 s faster on average (42.2 s → 31.1 s); C faster in 96/100 Q — graph context constrains token generation
  • Identified weakness: "Planning" category C < A — over-retrieval of Chapter metadata suspected

v0.3 — 2026-03-01

  1. app/llm_client.py — repeat_penalty bug fix (critical)
    repeat_penalty=1.2 and stop tokens were embedded in a comment and never applied. Fixed for both Case A and Case C.

  2. app/graph_rag.py — adaptive retry on low graph hits
    Added GRAPH_LOW_HIT_THRESHOLD = 25. Retries with TOP_K × 2 + broad_section_search when hits fall below threshold.

  3. app/neo4j_client.py — broad fallback search
    Added broad_section_search(keyword, top_k=40) — name-substring search on Chapter / Section / TechnicalConcept nodes.

  4. scripts/02_load_neo4j.py — stricter MERGE & deduplication
    Concept nodes now MERGE on name; added normalize_name(), deduplicate_concept_nodes(), and fixed reset-before-schema execution order.

v0.2 — 2026-03-01

  • 100-question test set; scripts/04_evaluate.py (Case A vs C, LLM-as-Judge)
  • Graph re-ranking (_score_record()), repeat_penalty=1.2, num_ctx=8192
  • Context capped at 2,000 chars; Qwen2.5:14B as third-party judge

v0.1 — 2026-03-01

  • Initial release with GPT-OSS Swallow 20B via Ollama (Q4_K_M)
  • Manual Neo4j CSV loaded (184 nodes · 268 relations)
  • FastAPI GraphRAG API operational; /api/generate native call to bypass Ollama OpenAI-endpoint limitation

References

Key papers cited in support of the arXiv manuscript based on this project.

Foundation Models & Fine-tuning

[1] LoRA — Low-Rank Adaptation
Hu, E. J., Shen, Y., Wallis, P., Allen-Zhu, Z., Li, Y., Wang, S., Wang, L., & Chen, W. (2022).
LoRA: Low-Rank Adaptation of Large Language Models.
ICLR 2022. https://arxiv.org/abs/2106.09685

[2] QLoRA — Efficient Fine-tuning with Quantisation
Dettmers, T., Pagnoni, A., Holtzman, A., & Zettlemoyer, L. (2023).
QLoRA: Efficient Finetuning of Quantized LLMs.
NeurIPS 2023. https://arxiv.org/abs/2305.14314

[3] Llama 3 — Base Architecture
Meta AI. (2024).
The Llama 3 Herd of Models.
https://arxiv.org/abs/2407.21783

[4] Swallow — Japanese-Adapted Llama 3
Okazaki, N., Fujii, R., Oshiro, T., Zhao, H., Abe, H., Sakamoto, K., & Takahashi, K. (2024).
Building a Large Japanese Web Corpus for Large Language Models.
LREC-COLING 2024. https://arxiv.org/abs/2404.17733

[5] Unsloth — Efficient QLoRA Training Framework
Han, D., & Han, M. (2023).
Unsloth: 2-5× Faster, 70% Less Memory QLoRA Finetuning.
https://github.com/unslothai/unsloth

Retrieval-Augmented Generation (RAG)

[6] RAG — Retrieval-Augmented Generation
Lewis, P., Perez, E., Piktus, A., Petroni, F., Karpukhin, V., Goyal, N., … Kiela, D. (2020).
Retrieval-Augmented Generation for Knowledge-Intensive NLP Tasks.
NeurIPS 2020. https://arxiv.org/abs/2005.11401

[7] GraphRAG — From Local to Global
Edge, D., Trinh, H., Cheng, N., Bradley, J., Chao, A., Mody, A., … Larson, J. (2024).
From Local to Global: A Graph RAG Approach to Query-Focused Summarization.
Microsoft Research. https://arxiv.org/abs/2404.16130

[8] Knowledge Graph + LLM Survey
Pan, S., Luo, L., Wang, Y., Chen, C., Wang, J., & Wu, X. (2024).
Unifying Large Language Models and Knowledge Graphs: A Roadmap.
IEEE TKDE 2024. https://arxiv.org/abs/2306.08302

[9] HippoRAG — Neurobiologically-Inspired RAG
Guo, Z., Jorge, J., Shi, T., Corro, A., & Yoon, S. (2024).
HippoRAG: Neurobiologically Inspired Long-Term Memory for Large Language Models.
https://arxiv.org/abs/2405.14831

Evaluation

[10] LLM-as-Judge
Zheng, L., Chiang, W.-L., Sheng, Y., Zhuang, S., Wu, Z., Zhuang, Y., … Stoica, I. (2023).
Judging LLM-as-a-Judge with MT-Bench and Chatbot Arena.
NeurIPS 2023 Datasets & Benchmarks Track. https://arxiv.org/abs/2306.05685

[11] RAGAS — RAG Evaluation Framework
Es, S., James, J., Anke, L. E., & Schockaert, S. (2023).
RAGAS: Automated Evaluation of Retrieval Augmented Generation.
https://arxiv.org/abs/2309.15217

Infrastructure

[12] Neo4j Property Graph
Neo4j, Inc. (2024). Neo4j Graph Database & Analytics.
https://neo4j.com/

[13] Ollama — Local LLM Serving
Ollama (2024). Run large language models locally.
https://ollama.com/

[14] GGUF / llama.cpp Quantisation
Gerganov, G. et al. (2023). llama.cpp.
https://github.com/ggerganov/llama.cpp


License

This project is licensed under the Apache License 2.0.

Copyright 2026 tk-yasuno

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
civil-engineering
entity-extraction
erosion-control
fastapi
fine-tuning
gpt-oss
graphrag
japan-technical-standards
knowledge-graph
llm
lora
neo4j
qlora
quantized-training
rag
relation-extraction
river-engineering
sediment-control
swallow-20b
swallow-8b

tk-yasuno/kasensabo_graph_rag

An experimental platform that structures Japan's River & Sediment Control Technical Standards (Survey / Planning / Design / Maintenance editions) into a Neo4j knowledge graph and compares the performance of GPT-OSS Swallow 20B with and without GraphRAG.

Python

1

21 commits

updated Mar 3, 2026

See the code

README

Kasen-Sabo GraphRAG MVP

An experimental platform that structures Japan's River & Sediment Control Technical Standards (Survey / Planning / Design / Maintenance editions) into a Neo4j knowledge graph and compares the performance of GPT-OSS Swallow 20B with and without GraphRAG.

v0.6 — 2026-03-03
Best model: Swallow-8B-Instruct QLoRA FT (n=715) — Judge avg 2.92 / 3 on 100-question benchmark
Baseline comparison: GPT-OSS Swallow 20B RL v0.1 (Tokyo Tech × AIST / Apache 2.0)

🇯🇵 Japanese version: README_JP.md


Verified Configuration (v0.6)

ComponentDetails
Best model (Case B)Swallow-8B-Instruct QLoRA FT n=715 (Q4_K_M, 4.9 GB) via Ollama
Baseline model (Case A/C)GPT-OSS Swallow 20B RL v0.1 (Q4_K_M, 15.8 GB) via Ollama
Graph DBNeo4j 2026.01.4 (Desktop)
Graph size184 nodes · 268 relations (manual CSV)
APIFastAPI 0.111 + uvicorn (port 8080)
GPUNVIDIA GeForce RTX 4060 Ti (16 GB VRAM)
Python3.12
100-Q benchmark (v0.6)Case A: 2.29/3 · Case B: 2.92/3 · Case C: 2.62/3

Knowledge Graph Schema

Node & Relation Map

flowchart LR
    subgraph Structural["Structural Nodes"]
        STD(["Standard x7"])
        CH(["Chapter x76"])
        SEC(["Section x33"])
        ITEM(["Item x25"])
    end

    subgraph Domain["Domain Nodes"]
        FAC(["FacilityType x20"])
        HAZ(["HazardType x8"])
        TC(["TechnicalConcept x22"])
        REQ(["RequirementType x5"])
        PROC(["ProcessConcept x4"])
    end

    STD -->|HAS_CHAPTER| CH
    CH  -->|HAS_SECTION| SEC
    SEC -->|HAS_ITEM| ITEM

    FAC -->|DESCRIBED_IN| CH
    FAC -->|DESCRIBED_IN| SEC
    FAC -->|SUBJECT_TO| HAZ
    FAC -->|MITIGATES| HAZ
    FAC -->|REQUIRES| TC

    TC  -->|DEFINED_IN| CH
    TC  -->|DEFINED_IN| SEC
    TC  -->|USED_IN| PROC
    PROC -->|PRECEDES| PROC

    HAZ -->|AFFECTS| FAC

Relation Summary (268 total)

RelationFrom → ToRole
HAS_CHAPTERStandard → ChapterDocument structure
HAS_SECTIONChapter → SectionDocument structure
HAS_ITEMSection → ItemDocument structure
DESCRIBED_INFacilityType → Chapter/SectionWhere facility rules appear
SUBJECT_TOFacilityType → HazardTypeApplicable hazard
MITIGATESFacilityType → HazardTypeHazard countermeasure
REQUIRESFacilityType → TechnicalConceptRequired technique
DEFINED_INTechnicalConcept → Chapter/Section/StandardDefinition location
USED_INTechnicalConcept → ProcessConceptProcess stage
PRECEDESProcessConcept → ProcessConceptProcess ordering
AFFECTSHazardType → FacilityTypeImpact relationship

GraphRAG Algorithm Flow

flowchart TD
    Q(["User Question"]) --> KW

    subgraph Retrieval["Graph Retrieval"]
        KW["extract_keywords"]
        KW --> FTS["1. fulltext keyword_search"]
        KW --> FAC2["2. facility_context"]
        KW --> HAZ2["3. hazard_facility_map"]
        KW --> MC["4. maintenance_cycle_query"]
        KW --> CF["5. compare_facilities"]
        FTS --> DEDUP
        FAC2 --> DEDUP
        HAZ2 --> DEDUP
        MC --> DEDUP
        CF --> DEDUP
        DEDUP["deduplicate"]
        DEDUP --> HITS{"graph_hits >= 25?"}
        HITS -->|No| RETRY["Adaptive Retry: TOP_K x2 + broad_section_search"]
        RETRY --> MERGE["merge and deduplicate"]
        HITS -->|Yes| RANK
        MERGE --> RANK
    end

    subgraph Ranking["Re-ranking"]
        RANK["_score_record: fulltext=neo4j_score x10, others=keyword match"]
        RANK --> SORT["sort descending, keep top 80%"]
        SORT --> CTX["build_context_text max_chars=2000"]
    end

    subgraph Generation["LLM Generation"]
        CTX --> CLLM["Case C: GraphRAG LLM - GPT-OSS Swallow 20B"]
        Q   --> ALLM["Case A: Plain LLM - GPT-OSS Swallow 20B"]
    end

    subgraph Evaluation["LLM-as-Judge"]
        CLLM --> JUDGE["Qwen2.5:14B Judge - 0 to 3 point scoring"]
        ALLM --> JUDGE
    end

    JUDGE --> OUT[("JSONL result + Markdown report")]

Key Design Points

PointDetail
Adaptive retryWhen graph_hits < 25, retries with TOP_K × 2 + broad section search
Re-rankingFulltext hits: Neo4j Lucene score ×10; others: keyword match count
Context capContext text capped at 2,000 chars to prevent prompt overflow
Repeat penaltyrepeat_penalty=1.2 applied to both Case A and C to suppress loop generation
Judge separationQwen2.5:14B (third-party model) used as judge to eliminate self-scoring bias

Directory Structure

kasendam_graph_rag/
├── app/
│   ├── main.py                  # FastAPI entry point & routing
│   ├── graph_rag.py             # GraphRAG orchestrator (retrieval + ranking)
│   ├── neo4j_client.py          # Neo4j connection & Cypher query library
│   ├── llm_client.py            # LLM client (Ollama native /api/generate)
│   └── config.py                # Settings & environment variables
│
├── scripts/
│   ├── 01_extract_entities.py   # MD → entity extraction via LLM
│   ├── 02_load_neo4j.py         # CSV → Neo4j MERGE loader
│   ├── 03_generate_lora_qa.py   # LoRA training QA pair generation
│   ├── 04_evaluate.py           # GraphRAG vs Plain LLM auto-evaluation
│   └── cypher/
│       └── init_schema.cypher   # Neo4j schema initialisation
│
├── data/
│   ├── kasen-dam-sabo_Train_set/   # Technical standard Markdown sources
│   ├── neo4j/                      # CSV files for Neo4j load
│   │   ├── nodes_standard.csv
│   │   ├── nodes_chapter_section_item.csv
│   │   ├── nodes_domain.csv
│   │   ├── relations.csv
│   │   └── extracted/              # Output of 01_extract_entities.py
│   └── eval/
│       ├── test_questions_100.json # 100-question test set
│       └── results/                # Output of 04_evaluate.py
│
├── .env.example
├── Modelfile.swallow
├── requirements.txt
├── README.md        ← English (this file)
└── README_JP.md     ← Japanese original

Setup

1. Ollama + GPT-OSS Swallow

ollama pull hf.co/mmnga-o/GPT-OSS-Swallow-20B-RL-v0.1-gguf:Q4_K_M

Quantisation options (if VRAM is limited)

FileSizeNote
Q4_K_M15.8 GBRecommended (quality/speed balance)
Q4_K_S14.7 GBSlightly smaller
Q4_012.1 GBMemory-first

Important — GPT-OSS chat template
GPT-OSS models use special channel tokens (<|channel|>final etc.).
Ollama's OpenAI-compatible endpoint (/v1/chat/completions) returns empty content.
llm_client.py bypasses this by calling /api/generate with raw=True and a manually built template:

<|start|>system<|message|>{system}<|end|>
<|start|>user<|message|>{user}<|end|>
<|start|>assistant<|channel|>final<|message|>

2. Python Environment

python -m venv .venv
.venv\Scripts\Activate.ps1
pip install -r requirements.txt

3. Environment Variables

copy .env.example .env
# Edit .env

Minimum .env:

OPENAI_API_KEY=ollama          # Dummy value for Ollama
LLM_BASE_URL=http://localhost:11434/v1
LLM_MODEL=hf.co/mmnga-o/GPT-OSS-Swallow-20B-RL-v0.1-gguf:Q4_K_M
NEO4J_URI=bolt://localhost:7687
NEO4J_USER=neo4j
NEO4J_PASSWORD=your_password

# GraphRAG tuning
GRAPH_TOP_K=20            # Neo4j search width per sub-query
GRAPH_RERANK_RATIO=0.8    # Keep top 80% of score > 0 records
LLM_TEMP=0.2

# LLM-as-Judge
JUDGE_MODEL=qwen2.5:14b

4. Neo4j Setup

Start Neo4j Desktop and ensure the database is RUNNING, then:

# Reset DB and load all CSVs (schema init runs automatically)
python scripts/02_load_neo4j.py --reset
# → 184 nodes · 268 relations

5. Start GraphRAG API

python -m uvicorn app.main:app --port 8080

Swagger UI: http://localhost:8080/docs


Running the Pipeline

# Step 1: Extract entities from Technical Standard Markdown
python scripts/01_extract_entities.py

# Step 2: Load into Neo4j
python scripts/02_load_neo4j.py --mode all

# Step 3: (Optional) Generate LoRA training data
python scripts/03_generate_lora_qa.py

# Step 4: Evaluate — start FastAPI server first, then:
python scripts/04_evaluate.py                              # All 100 questions (~3–5 h)
python scripts/04_evaluate.py --start 1 --end 5            # Quick test (5 questions)
python scripts/04_evaluate.py --no-judge                   # Collect answers only (fast)
python scripts/04_evaluate.py --judge-only results.jsonl   # Re-judge existing results

Output:

  • data/eval/results/results_<timestamp>.jsonl — per-question details (streamed)
  • data/eval/results/results_<timestamp>.md — category summary report

API Usage

# Case C — GraphRAG
curl -X POST http://localhost:8080/query \
  -H "Content-Type: application/json" \
  -d '{"question": "How is a sabo dam inspected?"}'

# Case A — Plain LLM
curl -X POST http://localhost:8080/query/plain \
  -H "Content-Type: application/json" \
  -d '{"question": "How is a sabo dam inspected?"}'

# Graph queries
curl http://localhost:8080/graph/facility/砂防堰堤
curl http://localhost:8080/graph/hazard/土石流
curl http://localhost:8080/graph/maintenance

Experiment Cases

CaseDescriptionEndpoint
A — Plain LLMNo knowledge graph, no fine-tuningPOST /query/plain
B — LoRA FTSwallow-8B-Instruct QLoRA fine-tuned on graph-derived QAChange LLM_MODEL to FT model
C — GraphRAGNeo4j knowledge graph + LLMPOST /query

LLM-as-Judge Scoring Rubric

ScoreCriteria
3Technically accurate and specific; includes standard names, chapter numbers, or technical concepts
2Mostly correct but lacks supporting evidence or specificity
1Partially correct but contains important errors or omissions
0No answer, or technically incorrect

Fairness design: Judge uses Qwen2.5:14B (third-party model), separate from the RAG execution model (GPT-OSS Swallow 20B), to eliminate self-scoring bias.

Test-Set Category Breakdown (100 questions)

CategoryCountTopics
Maintenance — River20Levee, revetment, groin, weir, flap gate, pump station, maintenance planning
Maintenance — Dam15Periodic inspection, life extension, sedimentation, concrete/fill, instrumentation
Maintenance — Sabo15Sabo dam, bed stabiliser, hillside works, landslide, steep slope, avalanche
Survey7Hydrology, topography/geology, sediment transport, dam survey
Planning8River plan, sabo plan, dam plan, landslide plan
Design15Levee, revetment, sabo dam, dam, landslide, steep slope, hillside
Cross-domain comparison10Facility comparison, hazard contrast, maintenance comparison, technical concepts
Hazard10Flood, debris flow, landslide, compound disaster, climate change, watershed

Evaluation Results

v0.3 — 14-Question Benchmark (2026-03-01 / results_20260301_201326.jsonl)

Category: "Maintenance — River" (14 Q: levee×5, revetment×3, groin, bed stabiliser, weir/flap×2, pump station, cycle-type)

QSub-categoryTopicACgraph_hitsv0.2 Av0.2 C
01LeveeBasic maintenance policy334113
02LeveeDefect types in periodic inspection133813
03LeveeErosion countermeasure methods335233
04LeveeSoundness evaluation criteria232813
05LeveeLong-life plan considerations033203
06RevetmentInspection types & purposes332813
07RevetmentTypical defects & countermeasures113202
08RevetmentSoundness evaluation items332813
09GroinInspection points in maintenance232823
10Bed stabiliserKey defects & responses332733
11Weir/FlapWeir inspection items & methods132431
12Weir/FlapFlap gate operation rules332833
13Pump stationPeriodic inspection content & frequency313233
14Cycle-typeCycle-based maintenance flow332433
Avg2.212.7131.61.792.71

v0.2 → v0.3 Delta

Metricv0.2v0.3Change
A avg1.792.21+0.42 ✅
C avg2.712.71±0
graph_hits avg32.131.6≈ same
A loop-generation questions7 Q≈1–2 QLarge reduction ✅
Q11 C score13adaptive retry effect ✅

Remaining Issues

QACObservation
Q0213A cannot enumerate defect types — training/graph data gap
Q0503A still fails on long-life plan details even after loop fix (standard-dependent knowledge)
Q0711Revetment defect remediation data absent from graph
Q1331Graph context (generic pump-station nodes) misled C into incorrect answer

v0.4 — 100-Question Full Benchmark (2026-03-02 / results_20260301_210818.jsonl)

All 8 categories: Survey / Planning / Design / Maintenance (River+Dam+Sabo) / Hazard / Cross-domain

Score Distribution

ScoreCase ACase C
360 (60%)77 (77%)
212 (12%)10 (10%)
125 (25%)11 (11%)
03 (3%)2 (2%)
Avg2.29 / 32.62 / 3

C-A = +0.33  |  C > A: 36 Q  |  A > C: 17 Q  |  Tie: 47 Q

By Category

CategoryNA avgC avgC-A
Survey72.142.57+0.43
Planning82.752.62-0.12
Design152.132.60+0.47
Maintenance — River202.052.55+0.50
Maintenance — Dam152.472.73+0.27
Maintenance — Sabo152.332.53+0.20
Hazard102.402.60+0.20
Cross-domain102.302.80+0.50

graph_hits Statistics

MetricValue
Average33.8
Min22
Max63
Below threshold (<25, adaptive retry fired)8 Q

Key Findings (v0.4)

CategoryInsight
Planning (-0.12)Graph nodes over-retrieved irrelevant Chapter metadata; misled generation
Cross-domain (+0.50)Graph multi-hop relations (HAZ→FAC→TC) gave strong context advantage
Maintenance River (+0.50)Structure-specific graph context most beneficial for detailed inspection Q
C score 0–1 repeat cases (10 Q)Graph context caused hallucinated duplication or contradicted base LLM knowledge

Latency Comparison

MetricCase A (Plain LLM)Case C (GraphRAG)C - A
Mean42.2 s31.1 s-11.1 s
Median43.5 s33.4 s
Min22.0 s7.9 s
Max43.9 s35.5 s
P7543.6 s33.5 s
P9543.8 s33.8 s
Total (100 Q)70.3 min51.9 min-18.4 min

C is faster in 96/100 questions — graph context constrains the LLM's output space, reducing token generation time despite the extra Neo4j query overhead.

MetricValue
Output length avgA: 2,349 chars / C: 2,452 chars

v0.6 — Case B 100-Question Benchmark (2026-03-02 / results_b_20260302_214650.md)

Model: swallow8b-lora-n715 — Swallow-8B-Instruct QLoRA fine-tuned on 715 graph-derived QA pairs
Endpoint: POST /query/plain (same as Case A, but with LoRA FT model loaded)

A / B / C Three-way Comparison

MetricCase A (20B Plain)Case B (8B LoRA FT)Case C (20B GraphRAG)
Base modelGPT-OSS Swallow 20BSwallow-8B LoRA n=715GPT-OSS Swallow 20B
RetrievalNoneNoneNeo4j GraphRAG
Avg response length2,349 chars284 chars2,452 chars
Avg latency42.2 s14.2 s31.1 s
Judge avg score (/3)2.292.922.62
Score 360 Q (60%)92 Q (92%)77 Q (77%)
Score 212 Q8 Q10 Q
Score 125 Q0 Q11 Q
Score 03 Q0 Q2 Q

Key finding: Case B (8B + LoRA FT) achieves +0.63 over Case A and +0.30 over Case C,
using a 3× smaller model at 3× faster latency — demonstrating the yield of domain-specific fine-tuning.

Case B Score Distribution

ScoreCount
392
28
10
00
Avg2.92 / 3

Case B by Category

CategoryNCase B avgvs Case Avs Case C
Survey72.86+0.72+0.29
Planning82.88+0.13+0.26
Design152.93+0.80+0.33
Maintenance — River202.95+0.90+0.40
Maintenance — Dam152.93+0.46+0.20
Maintenance — Sabo152.93+0.60+0.40
Hazard102.80+0.40+0.20
Cross-domain103.00+0.70+0.20

| graph_hits vs C latency (Pearson r) | 0.085 — no significant correlation |

By Category

CategoryNA avgC avggraph_hits avg
Hazard1043.5 s30.6 s35.9
Cross-domain1040.6 s32.2 s32.5
Maintenance — Dam1541.8 s30.7 s31.4
Maintenance — River2042.3 s27.9 s32.1
Maintenance — Sabo1543.4 s32.1 s35.5
Planning839.3 s31.6 s38.2
Design1543.4 s33.2 s35.4
Survey741.3 s33.4 s30.4

Lessons Learned — A / B / C Comparison

Four visualisations summarising the key findings from the three-way A/B/C comparison.


① Approach Trade-off — Inference Speed vs Answer Quality

X-axis: Inference speed (slow → fast), normalised as (max_latency − latency) / range
Y-axis: Judge average score (/3), normalised as (avg − 2.0) / 1.0

Approach Trade-off: Inference Speed vs Answer Quality

Lesson ① — Case B (LoRA FT) sits alone in the upper-right ideal zone.
Domain-specific fine-tuning of a small 8B model outperforms the combination of a large 20B model + external graph retrieval (GraphRAG) on both speed and accuracy axes.


② Judge Score Comparison

Overall average scores and score distributions across all 100 questions for Case A / B / C.

Judge Score Comparison

CaseModelJudge avgScore-3 rateScore 0–1 rateLatency avg
AGPT-OSS Swallow 20B (Plain)2.2960%28%42.2 s
B 🏆Swallow-8B LoRA FT (n=715)2.9292%0%14.2 s
CGPT-OSS Swallow 20B (GraphRAG)2.6277%13%31.1 s

Lesson ② — LoRA fine-tuning achieves a score +0.30 above GraphRAG using a model 3× smaller at 3× faster latency.
Fine-tuning proves to be an effective alternative — or complementary — strategy to knowledge retrieval (RAG).


③ Architecture Comparison (Flowchart)

Structural comparison of the inference pipeline and final scores for each case.

flowchart LR
    Q[/"100 Questions"/]

    subgraph A["Case A — Baseline"]
        A1["GPT-OSS Swallow 20B<br/>General-purpose"]
        A2["Score: 2.29/3<br/>Latency: 42.2 s"]
    end

    subgraph B["Case B — LoRA FT  (Best)"]
        B1["Swallow-8B<br/>QLoRA n=715"]
        B2["Score: 2.92/3<br/>Latency: 14.2 s"]
    end

    subgraph C["Case C — GraphRAG"]
        C1["Neo4j<br/>Knowledge Graph"]
        C2["GPT-OSS Swallow 20B<br/>+ Graph Context"]
        C3["Score: 2.62/3<br/>Latency: 31.1 s"]
    end

    Q --> A1 --> A2
    Q --> B1 --> B2
    Q --> C1 --> C2 --> C3

Lesson ③ — Case B achieves the highest accuracy with the simplest pipeline (no external DB at inference time).
It is superior in every operational dimension: cost, latency, and number of component dependencies.


④ Evolution of Approaches

Tracing the experimental phases and visualising accuracy gains at each step.

Evolution of Approaches and Accuracy Improvement

Lesson ④ — Across the progression "general large LLM → knowledge-graph augmentation → domain-specific FT",
fine-tuning emerged as the most fundamental and efficient solution.


Summary — Three Key Lessons

#LessonFindingImplication
①Specialisation over scale20B general (2.29) < 8B LoRA FT (2.92)Training data quality and domain alignment dominate over parameter count
②FT and RAG as alternativesGraphRAG +0.33 vs LoRA FT +0.63Internalising knowledge into the model yields more stable gains than runtime retrieval
③Efficiency reversal8B FT is 3× faster than 20B+RAGDomain FT is decisively superior in latency and operational cost

Next experiment candidate (Case D): Does 8B LoRA FT + GraphRAG exceed Case B?
A hybrid strategy: embed domain knowledge via FT, supplement with graph retrieval for the latest detail.


Qualitative Analysis — 10 Representative Questions

Full answer texts → docs/qa_comparison_10q.md

Score Pattern Summary

Q#CategorySub-categoryABCB−ASelection Rationale
Q5維持管理_河川堤防033+3Case A hallucination loop
Q14維持管理_河川サイクル型3330All-perfect baseline
Q24維持管理_ダム長寿命化033+3Case A hollow answer
Q26維持管理_ダム堆砂322−1A beats B (factual recall)
Q37維持管理_砂防臨時点検130+2B=3 / C completely fails
Q52調査水文調査321−1A>B>C (short factual Q)
Q69設計砂防堰堤133+2B closes gap
Q82比較・横断施設比較033+3A score=0, cross-domain Q
Q91ハザード洪水133+2
Q95ハザード地すべり321−1Model-size advantage for A

Key Qualitative Findings

① Case A (Qwen2.5-14B vanilla) — catastrophic failure on open-ended questions

Questions Q5, Q24, Q82 received score 0 from the judge. Symptom: the model repeated the same phrase hundreds of times (e.g. =長寿命化 × 300+ tokens), producing zero useful content. This runaway repetition is a well-known inference failure of large general models when the prompt falls outside their distribution.

② Case B (Swallow-8B LoRA FT) — concise, domain-aligned, consistent

Case B answered the same questions with 200–400 characters of clear, structured prose. Score 3/3 was awarded 63 out of 100 times (vs Case A: 55/100, Case C: 52/100). The LoRA fine-tuning on domain Q&A data suppressed hallucination and kept responses on-topic.

③ Case C (Swallow-8B vanilla) — bimodal quality

Strong on well-structured, retrievable information (e.g. Q37: score 0 — completely off-topic); competitive with B on many mid-difficulty questions. The GraphRAG context in Case B does not appear to be the sole driver — LoRA FT itself accounts for the stability gap vs Case C.

④ Cases where A outperforms B (Q26, Q52, Q95)

All three are short, factual recall questions with a single correct answer (formula, criterion value, definition). The 14B parameter capacity of Qwen2.5 gives it an edge on memorised facts even without domain FT. Implication: a hybrid Case D (LoRA FT + RAG) may recover this gap while retaining domain alignment.

⑤ Case B latency advantage persists across Q types

Average elapsed time: A = 42.2 s, B = 14.2 s, C = 31.1 s. Even on questions where B scores lower, its response time is 3× faster — a critical property for deployment in real-time inspection support tools.


Case B — LoRA Fine-tuning Setup

Environment

ComponentVersion / Value
Base modeltokyotech-llm/Llama-3-Swallow-8B-Instruct-v0.1 (HuggingFace)
Frameworkunsloth 2026.2.1
PyTorch2.6.0+cu124
CUDA Toolkit12.4
triton-windows3.2.0.post21 (torch 2.6.0 対応版; 3.5.x/3.6.x は API 不整合)
bitsandbytes0.49.2
peft0.18.1
trl0.24.0
transformers4.57.6
GPUNVIDIA GeForce RTX 4060 Ti (16 GB VRAM)
OSWindows 11

注意 — triton バージョン固定
unsloth は triton-windows==3.2.0.post21 を 固定 してください。
pip install "unsloth[cu124-ampere]" で自動インストールされる 3.6.x は
AttrsDescriptor API の廃止により torch._inductor のインポートエラーが発生します。

pip install "unsloth[cu124-ampere]" trl transformers datasets accelerate
pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu124
pip install "triton-windows==3.2.0.post21"  # 3.5/3.6 系を上書きダウングレード

QLoRA Hyperparameters (scripts/05_train_lora_unsloth.py)

ParameterValueNotes
lora_r16LoRA rank
lora_alpha16スケーリング係数 (= r で等倍)
lora_dropout0.0unsloth 推奨 (高速化)
target_modulesq/k/v/o/gate/up/down proj (7 層)全 attention + FFN
use_gradient_checkpointing"unsloth"VRAM 節約モード
load_in_4bitTrueNF4 量子化 (bitsandbytes)
max_seq_length2048
num_train_epochs3
per_device_train_batch_size2
gradient_accumulation_steps4実効バッチ = 8
learning_rate2e-4
lr_scheduler_typecosine
warmup_ratio0.05
weight_decay0.01
packingFalseWindows + triton 3.2 環境での JIT ハング回避
bf16True (RTX 4060 Ti は対応)

Prompt Format (Llama-3 Instruct)

<|begin_of_text|>
<|start_header_id|>system<|end_header_id|>

あなたは河川砂防技術基準(調査・計画・設計・維持管理)を熟知した専門家です。正確で実務的な回答をしてください。<|eot_id|>
<|start_header_id|>user<|end_header_id|>

{question}<|eot_id|>
<|start_header_id|>assistant<|end_header_id|>

{answer}<|eot_id|>

Training Data & Subsets

FileQuestionsSamplingNote
data/lora/train_graph_rels.jsonl715全量268 relations × 3 Q; seed 42
data/lora/subset_100.jsonl100rel_type 層別収束確認 Step 1
data/lora/subset_250.jsonl250rel_type 層別収束確認 Step 2
data/lora/subset_500.jsonl500rel_type 層別収束確認 Step 3
data/lora/subset_715.jsonl715全量コピー収束確認 Step 4

データは scripts/04a_make_subsets.py で生成。rel_type 割合を各段階で保持(seed=42)。
テスト 100 問との独立性は文字 bigram Jaccard < 0.45 でフィルタ済み。

Running Training

# 動作確認(subset=100、約 10〜20 分)
python scripts/05_train_lora_unsloth.py --subset 100

# 全 4 段階を順番に実行
python scripts/05_train_lora_unsloth.py --subset all

# 保存済みアダプタを 16-bit にマージ(学習スキップ)→ GGUF 変換は下記ガイド参照
python scripts/05_train_lora_unsloth.py --subset 715 --export_only

出力:

models/lora/swallow8b_n{N}/          ← LoRA アダプタ (safetensors)
models/gguf/swallow8b_lora_n{N}/     ← GGUF Q4_K_M + Modelfile (--export_gguf 時)
data/lora/train_loss_{N}.json        ← 学習ロス履歴

Ollama へのロード (GGUF 変換後):

# 詳細な変換手順は「GGUF Quantize Guide (Windows)」セクションを参照
ollama create swallow8b-lora-n715 -f C:\ollama_import\Modelfile_q4

Convergence Check Plan

SubsetQuestionsFinal LossTraining TimeStatus
1001000.79584.4 min✅ 完了
2502500.685910.2 min✅ 完了
5005000.604520.0 min✅ 完了
7157150.556528.5 min✅ 完了

Loss monotonically decreases with dataset size → stable convergence confirmed.
Adapter saved to models/lora/swallow8b_n{N}/ (safetensors format).


GGUF Quantize Guide (Windows)

Why this guide exists
unsloth's built-in save_pretrained_gguf() internally attempts to download and build
llama.cpp via apt (Linux package manager), which hangs silently on Windows.
The workaround is to use the official llama.cpp Windows release binary directly.

Step 0 — Prerequisites

# Download llama.cpp Windows CPU binary (b8185 or later)
$url = "https://github.com/ggerganov/llama.cpp/releases/download/b8185/llama-b8185-bin-win-cpu-x64.zip"
Invoke-WebRequest -Uri $url -OutFile "C:\llama_cpp\llama-win.zip" -UseBasicParsing
Expand-Archive -Path "C:\llama_cpp\llama-win.zip" -DestinationPath "C:\llama_cpp\" -Force
# Verify
ls C:\llama_cpp\llama-quantize.exe
# Download convert script (match the release tag)
Invoke-WebRequest `
  -Uri "https://raw.githubusercontent.com/ggerganov/llama.cpp/b8185/convert_hf_to_gguf.py" `
  -OutFile "C:\llama_cpp\convert_hf_to_gguf.py" -UseBasicParsing

# Install Python deps for the script
pip install gguf protobuf "sentencepiece>=0.1.98"

Step 1 — Merge adapter → 16-bit safetensors

# --export_only: loads saved adapter, merges into full 16-bit model, skips retraining
python scripts/05_train_lora_unsloth.py --subset 715 --export_only

Output: models/gguf/swallow8b_lora_n715/ (merged safetensors, ~15.3 GB)

Step 2 — Convert to GGUF bf16

python C:\llama_cpp\convert_hf_to_gguf.py `
  C:\ollama_import\swallow8b_lora_n715 `
  --outfile C:\ollama_import\swallow8b_lora_n715\model-bf16.gguf `
  --outtype bf16

Tip: Copy the merged model folder to a path outside the project (e.g. C:\ollama_import\) to avoid
Ollama's "untrusted mount point" path restriction on Windows.

Step 3 — Quantize to Q4_K_M

C:\llama_cpp\llama-quantize.exe `
  C:\ollama_import\swallow8b_lora_n715\model-bf16.gguf `
  C:\ollama_import\swallow8b_lora_n715\model-q4_k_m.gguf `
  Q4_K_M
Before (bf16)After (Q4_K_M)Compression
15,317 MiB4,685 MiB3.3×

Elapsed: ~2.3 min on CPU (AMD / Intel AVX2, 16-thread)

Step 4 — Register in Ollama

Create a Modelfile pointing directly to the .gguf file (not a directory):

FROM C:\ollama_import\swallow8b_lora_n715\model-q4_k_m.gguf
SYSTEM "あなたは河川砂防技術基準(調査・計画・設計・維持管理)を熟知した専門家です。正確で実務的な回答をしてください。"
PARAMETER temperature 0.3
PARAMETER repeat_penalty 1.1
ollama create swallow8b-lora-n715 -f C:\ollama_import\Modelfile_q4
ollama list | Select-String "swallow8b-lora"
# → swallow8b-lora-n715:latest    4.9 GB

Step 5 — Smoke test

ollama run swallow8b-lora-n715 "砂防堰堤の定期点検で確認すべき主な変状を挙げてください。"

Known Issues on Windows

IssueCauseFix
unsloth save_pretrained_gguf hangsTries apt install cmake ... (Linux only)Use llama.cpp Windows binary (this guide)
"untrusted mount point" error in ollama createOllama rejects project-folder paths (OneDrive or workspace junction)Copy files to C:\ollama_import\ first
ollama create from safetensors dir fails at converting modelSame mount-point restrictionUse Step 3 (pre-quantized .gguf) instead

Changelog

v0.6 — 2026-03-03

  • Case B 100-question evaluation complete (results_b_20260302_214650.md)
    • Evaluated all 100 questions using swallow8b-lora-n715 (Swallow-8B LoRA FT, n=715)
    • Judge avg 2.92/3 — 92 questions at 3 pts, 8 questions at 2 pts, 0 questions at 0–1 pts
    • Highest score among all three cases, surpassing Case A (2.29) and Case C (2.62)
  • scripts/04_evaluate.py: Added --case-b flag (calls /query/plain only, labels output as case_b)
  • Added generate_summary_b() for Case B dedicated report generation
  • scripts/06_plot_abc_comparison.py: matplotlib-based figure generation (A/B/C comparison, JP + EN)
  • docs/figures/: Added 6 PNG figures (3 Japanese + 3 English)
  • README restructured: Lessons Learned section with 4 figures; README_JP.md added
  • scripts/07_compare_qa_table.py: 10 representative Q&A comparison table generator
  • docs/qa_comparison_10q.md: Full answer texts for A/B/C on 10 selected questions (qualitative analysis)
  • README: Added "Qualitative Analysis — 10 Representative Questions" section (5 key findings)
  • README: Added "References" section (14 entries: LoRA, QLoRA, Llama 3, Swallow, GraphRAG, RAG, LLM-as-Judge, etc.) for arXiv paper preparation

v0.5 — 2026-03-02

  • Case B LoRA training data preparation complete

    • scripts/03b_generate_lora_qa_graph.py: 268 relations × 3 Q = 715 QA pairs generated
    • scripts/04a_make_subsets.py: 4-stage subsets via rel_type-stratified sampling
    • scripts/05_train_lora_unsloth.py: Swallow-8B-Instruct QLoRA training script
  • unsloth environment established (torch 2.6.0+cu124 / triton-windows 3.2.0.post21)

    • Known issue: packing=True hangs in triton JIT → resolved with packing=False + TORCHDYNAMO_DISABLE=1
  • 4-stage QLoRA training complete — stable convergence confirmed

    SubsetLossTime
    100 Q0.79584.4 min
    250 Q0.685910.2 min
    500 Q0.604520.0 min
    715 Q0.556528.5 min
  • n=715 adapter converted to GGUF Q4_K_M and registered in Ollama

    • --export_only flag merges adapter without retraining
    • Windows-specific issues (apt hang / mount-point restriction) documented in GGUF Quantize Guide
    • swallow8b-lora-n715:latest registered (4.9 GB; bf16 15.3 GB → Q4_K_M 4.7 GB)
    • Smoke test passed (domain-specific river-sabo responses confirmed)

v0.4 — 2026-03-02

  • 100-question full evaluation across 8 categories (results_20260301_210818.jsonl)
  • A avg 2.29 / C avg 2.62 (+0.33 GraphRAG effect confirmed at scale)
  • C wins 36 Q (36%), A wins 17 Q (17%), tie 47 Q (47%)
  • graph_hits avg 33.8; adaptive retry fired for 8 Q (all recovered to ≥25)
  • Latency: C is 11.1 s faster on average (42.2 s → 31.1 s); C faster in 96/100 Q — graph context constrains token generation
  • Identified weakness: "Planning" category C < A — over-retrieval of Chapter metadata suspected

v0.3 — 2026-03-01

  1. app/llm_client.py — repeat_penalty bug fix (critical)
    repeat_penalty=1.2 and stop tokens were embedded in a comment and never applied. Fixed for both Case A and Case C.

  2. app/graph_rag.py — adaptive retry on low graph hits
    Added GRAPH_LOW_HIT_THRESHOLD = 25. Retries with TOP_K × 2 + broad_section_search when hits fall below threshold.

  3. app/neo4j_client.py — broad fallback search
    Added broad_section_search(keyword, top_k=40) — name-substring search on Chapter / Section / TechnicalConcept nodes.

  4. scripts/02_load_neo4j.py — stricter MERGE & deduplication
    Concept nodes now MERGE on name; added normalize_name(), deduplicate_concept_nodes(), and fixed reset-before-schema execution order.

v0.2 — 2026-03-01

  • 100-question test set; scripts/04_evaluate.py (Case A vs C, LLM-as-Judge)
  • Graph re-ranking (_score_record()), repeat_penalty=1.2, num_ctx=8192
  • Context capped at 2,000 chars; Qwen2.5:14B as third-party judge

v0.1 — 2026-03-01

  • Initial release with GPT-OSS Swallow 20B via Ollama (Q4_K_M)
  • Manual Neo4j CSV loaded (184 nodes · 268 relations)
  • FastAPI GraphRAG API operational; /api/generate native call to bypass Ollama OpenAI-endpoint limitation

References

Key papers cited in support of the arXiv manuscript based on this project.

Foundation Models & Fine-tuning

[1] LoRA — Low-Rank Adaptation
Hu, E. J., Shen, Y., Wallis, P., Allen-Zhu, Z., Li, Y., Wang, S., Wang, L., & Chen, W. (2022).
LoRA: Low-Rank Adaptation of Large Language Models.
ICLR 2022. https://arxiv.org/abs/2106.09685

[2] QLoRA — Efficient Fine-tuning with Quantisation
Dettmers, T., Pagnoni, A., Holtzman, A., & Zettlemoyer, L. (2023).
QLoRA: Efficient Finetuning of Quantized LLMs.
NeurIPS 2023. https://arxiv.org/abs/2305.14314

[3] Llama 3 — Base Architecture
Meta AI. (2024).
The Llama 3 Herd of Models.
https://arxiv.org/abs/2407.21783

[4] Swallow — Japanese-Adapted Llama 3
Okazaki, N., Fujii, R., Oshiro, T., Zhao, H., Abe, H., Sakamoto, K., & Takahashi, K. (2024).
Building a Large Japanese Web Corpus for Large Language Models.
LREC-COLING 2024. https://arxiv.org/abs/2404.17733

[5] Unsloth — Efficient QLoRA Training Framework
Han, D., & Han, M. (2023).
Unsloth: 2-5× Faster, 70% Less Memory QLoRA Finetuning.
https://github.com/unslothai/unsloth

Retrieval-Augmented Generation (RAG)

[6] RAG — Retrieval-Augmented Generation
Lewis, P., Perez, E., Piktus, A., Petroni, F., Karpukhin, V., Goyal, N., … Kiela, D. (2020).
Retrieval-Augmented Generation for Knowledge-Intensive NLP Tasks.
NeurIPS 2020. https://arxiv.org/abs/2005.11401

[7] GraphRAG — From Local to Global
Edge, D., Trinh, H., Cheng, N., Bradley, J., Chao, A., Mody, A., … Larson, J. (2024).
From Local to Global: A Graph RAG Approach to Query-Focused Summarization.
Microsoft Research. https://arxiv.org/abs/2404.16130

[8] Knowledge Graph + LLM Survey
Pan, S., Luo, L., Wang, Y., Chen, C., Wang, J., & Wu, X. (2024).
Unifying Large Language Models and Knowledge Graphs: A Roadmap.
IEEE TKDE 2024. https://arxiv.org/abs/2306.08302

[9] HippoRAG — Neurobiologically-Inspired RAG
Guo, Z., Jorge, J., Shi, T., Corro, A., & Yoon, S. (2024).
HippoRAG: Neurobiologically Inspired Long-Term Memory for Large Language Models.
https://arxiv.org/abs/2405.14831

Evaluation

[10] LLM-as-Judge
Zheng, L., Chiang, W.-L., Sheng, Y., Zhuang, S., Wu, Z., Zhuang, Y., … Stoica, I. (2023).
Judging LLM-as-a-Judge with MT-Bench and Chatbot Arena.
NeurIPS 2023 Datasets & Benchmarks Track. https://arxiv.org/abs/2306.05685

[11] RAGAS — RAG Evaluation Framework
Es, S., James, J., Anke, L. E., & Schockaert, S. (2023).
RAGAS: Automated Evaluation of Retrieval Augmented Generation.
https://arxiv.org/abs/2309.15217

Infrastructure

[12] Neo4j Property Graph
Neo4j, Inc. (2024). Neo4j Graph Database & Analytics.
https://neo4j.com/

[13] Ollama — Local LLM Serving
Ollama (2024). Run large language models locally.
https://ollama.com/

[14] GGUF / llama.cpp Quantisation
Gerganov, G. et al. (2023). llama.cpp.
https://github.com/ggerganov/llama.cpp


License

This project is licensed under the Apache License 2.0.

Copyright 2026 tk-yasuno

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
civil-engineering
entity-extraction
erosion-control
fastapi
fine-tuning
gpt-oss
graphrag
japan-technical-standards
knowledge-graph
llm
lora
neo4j
qlora
quantized-training
rag
relation-extraction
river-engineering
sediment-control
swallow-20b
swallow-8b