Spaces:
Sleeping
Sleeping
Persist article title, source and analyzed_at in Article node
Browse files- backend/pipeline/extraction/spacy_extractor.py +10 -1
- backend/pipeline/graph/neo4j_store.py +31 -6
- backend/pipeline/orchestrator.py +6 -1
- backend/pipeline/scoring/tcs.py +11 -9
- backend/pipeline/verification/internal.py +2 -1
- frontend/src/components/ArticleHistory.tsx +3 -1
- start.ps1 +0 -149
- start.sh +1 -1
backend/pipeline/extraction/spacy_extractor.py
CHANGED
|
@@ -248,11 +248,20 @@ class SpacyExtractor(AbstractExtractor):
|
|
| 248 |
|
| 249 |
# Relation classification
|
| 250 |
def _classify_relation(self, verb: Token) -> RelationType:
|
| 251 |
-
"""Determine the relation type based on the verb lemma."""
|
| 252 |
lemma = verb.lemma_.lower()
|
| 253 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 254 |
if lemma in POSITION_VERBS:
|
| 255 |
return RelationType.HOLDS_POSITION
|
|
|
|
|
|
|
| 256 |
elif lemma in MEMBERSHIP_VERBS:
|
| 257 |
return RelationType.MEMBER_OF
|
| 258 |
elif lemma in EVENT_VERBS:
|
|
|
|
| 248 |
|
| 249 |
# Relation classification
|
| 250 |
def _classify_relation(self, verb: Token) -> RelationType:
|
| 251 |
+
"""Determine the relation type based on the verb lemma and compound phrases."""
|
| 252 |
lemma = verb.lemma_.lower()
|
| 253 |
|
| 254 |
+
# Compound verb phrases take priority over single-lemma lookup
|
| 255 |
+
phrase = " ".join(t.lemma_.lower() for t in verb.subtree if t.dep_ in {"aux", "prt", "compound"} or t == verb)
|
| 256 |
+
if any(p in phrase for p in ("swear in", "take office", "be elect", "be appoint", "be inaugurate", "step down", "take over")):
|
| 257 |
+
return RelationType.HOLDS_POSITION
|
| 258 |
+
if any(p in phrase for p in ("win election", "win race", "defeat opponent")):
|
| 259 |
+
return RelationType.HOLDS_POSITION
|
| 260 |
+
|
| 261 |
if lemma in POSITION_VERBS:
|
| 262 |
return RelationType.HOLDS_POSITION
|
| 263 |
+
elif lemma in {"win", "defeat", "beat"}:
|
| 264 |
+
return RelationType.HOLDS_POSITION
|
| 265 |
elif lemma in MEMBERSHIP_VERBS:
|
| 266 |
return RelationType.MEMBER_OF
|
| 267 |
elif lemma in EVENT_VERBS:
|
backend/pipeline/graph/neo4j_store.py
CHANGED
|
@@ -52,13 +52,22 @@ class Neo4jTKGStore(AbstractTKGStore):
|
|
| 52 |
|
| 53 |
# Write operations
|
| 54 |
|
| 55 |
-
def add_fact(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 56 |
"""Store a fact as two Entity nodes + a TEMPORAL_RELATION edge."""
|
| 57 |
with self._driver.session() as session:
|
| 58 |
-
session.execute_write(self._create_fact_tx, fact, article_id)
|
| 59 |
|
| 60 |
@staticmethod
|
| 61 |
-
def _create_fact_tx(
|
|
|
|
|
|
|
|
|
|
| 62 |
def to_iso(expr: TemporalExpression | None) -> str | None:
|
| 63 |
if expr and expr.normalized_date:
|
| 64 |
return expr.normalized_date.isoformat()
|
|
@@ -113,11 +122,27 @@ class Neo4jTKGStore(AbstractTKGStore):
|
|
| 113 |
""", **params)
|
| 114 |
|
| 115 |
if article_id:
|
| 116 |
-
tx.run("
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 117 |
|
| 118 |
-
def add_facts(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 119 |
for fact in facts:
|
| 120 |
-
self.add_fact(fact, article_id)
|
| 121 |
logger.info(
|
| 122 |
f"Neo4jTKGStore: {len(facts)} facts written"
|
| 123 |
+ (f" for article {article_id}" if article_id else "")
|
|
|
|
| 52 |
|
| 53 |
# Write operations
|
| 54 |
|
| 55 |
+
def add_fact(
|
| 56 |
+
self,
|
| 57 |
+
fact: TemporalFact,
|
| 58 |
+
article_id: str | None = None,
|
| 59 |
+
title: str | None = None,
|
| 60 |
+
source: str | None = None,
|
| 61 |
+
) -> None:
|
| 62 |
"""Store a fact as two Entity nodes + a TEMPORAL_RELATION edge."""
|
| 63 |
with self._driver.session() as session:
|
| 64 |
+
session.execute_write(self._create_fact_tx, fact, article_id, title, source)
|
| 65 |
|
| 66 |
@staticmethod
|
| 67 |
+
def _create_fact_tx(
|
| 68 |
+
tx, fact: TemporalFact, article_id: str | None,
|
| 69 |
+
title: str | None = None, source: str | None = None,
|
| 70 |
+
) -> None:
|
| 71 |
def to_iso(expr: TemporalExpression | None) -> str | None:
|
| 72 |
if expr and expr.normalized_date:
|
| 73 |
return expr.normalized_date.isoformat()
|
|
|
|
| 122 |
""", **params)
|
| 123 |
|
| 124 |
if article_id:
|
| 125 |
+
tx.run("""
|
| 126 |
+
MERGE (a:Article {article_id: $article_id})
|
| 127 |
+
ON CREATE SET a.title = $title,
|
| 128 |
+
a.source = $source,
|
| 129 |
+
a.analyzed_at = $analyzed_at
|
| 130 |
+
""",
|
| 131 |
+
article_id=article_id,
|
| 132 |
+
title=title or "",
|
| 133 |
+
source=source or "",
|
| 134 |
+
analyzed_at=datetime.utcnow().isoformat(),
|
| 135 |
+
)
|
| 136 |
|
| 137 |
+
def add_facts(
|
| 138 |
+
self,
|
| 139 |
+
facts: list[TemporalFact],
|
| 140 |
+
article_id: str | None = None,
|
| 141 |
+
title: str | None = None,
|
| 142 |
+
source: str | None = None,
|
| 143 |
+
) -> None:
|
| 144 |
for fact in facts:
|
| 145 |
+
self.add_fact(fact, article_id, title=title, source=source)
|
| 146 |
logger.info(
|
| 147 |
f"Neo4jTKGStore: {len(facts)} facts written"
|
| 148 |
+ (f" for article {article_id}" if article_id else "")
|
backend/pipeline/orchestrator.py
CHANGED
|
@@ -151,7 +151,12 @@ class PipelineOrchestrator:
|
|
| 151 |
# Persist facts after scoring
|
| 152 |
if self._persistent_store and article_id:
|
| 153 |
try:
|
| 154 |
-
self._persistent_store.add_facts(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 155 |
logger.info(f"Neo4j: {len(all_facts)} facts persisted (article_id={article_id})")
|
| 156 |
except Exception as e:
|
| 157 |
logger.error(f"Neo4j persistence failed: {e}")
|
|
|
|
| 151 |
# Persist facts after scoring
|
| 152 |
if self._persistent_store and article_id:
|
| 153 |
try:
|
| 154 |
+
self._persistent_store.add_facts(
|
| 155 |
+
all_facts,
|
| 156 |
+
article_id=article_id,
|
| 157 |
+
title=article.title or "",
|
| 158 |
+
source=article.source or "",
|
| 159 |
+
)
|
| 160 |
logger.info(f"Neo4j: {len(all_facts)} facts persisted (article_id={article_id})")
|
| 161 |
except Exception as e:
|
| 162 |
logger.error(f"Neo4j persistence failed: {e}")
|
backend/pipeline/scoring/tcs.py
CHANGED
|
@@ -42,10 +42,10 @@ class TCSCalculator:
|
|
| 42 |
|
| 43 |
# Severity weights
|
| 44 |
SEVERITY_WEIGHTS = {
|
|
|
|
|
|
|
| 45 |
Severity.HIGH: 1.0,
|
| 46 |
-
Severity.
|
| 47 |
-
Severity.LOW: 0.3,
|
| 48 |
-
Severity.CRITICAL: 1.0
|
| 49 |
}
|
| 50 |
|
| 51 |
# Weighted penalty
|
|
@@ -53,19 +53,21 @@ class TCSCalculator:
|
|
| 53 |
# Worst-case: every claim has a critical inconsistency
|
| 54 |
max_possible_penalty = n_claims * max(SEVERITY_WEIGHTS.values())
|
| 55 |
|
| 56 |
-
# Coverage factor:
|
| 57 |
if facts_total > 0:
|
| 58 |
-
|
|
|
|
| 59 |
else:
|
| 60 |
-
|
| 61 |
-
coverage_factor = 0.5
|
| 62 |
|
| 63 |
# Main formula
|
| 64 |
penalty_ratio = min(1.0, weighted_penalty / max_possible_penalty) if max_possible_penalty > 0 else 0.0
|
| 65 |
raw_tcs = (1.0 - penalty_ratio) * score_coherence
|
| 66 |
|
| 67 |
-
#
|
| 68 |
-
tcs = raw_tcs
|
|
|
|
|
|
|
| 69 |
|
| 70 |
# Clamp
|
| 71 |
tcs = max(0.0, min(1.0, tcs))
|
|
|
|
| 42 |
|
| 43 |
# Severity weights
|
| 44 |
SEVERITY_WEIGHTS = {
|
| 45 |
+
Severity.LOW: 0.2,
|
| 46 |
+
Severity.MEDIUM: 0.5,
|
| 47 |
Severity.HIGH: 1.0,
|
| 48 |
+
Severity.CRITICAL: 1.5,
|
|
|
|
|
|
|
| 49 |
}
|
| 50 |
|
| 51 |
# Weighted penalty
|
|
|
|
| 53 |
# Worst-case: every claim has a critical inconsistency
|
| 54 |
max_possible_penalty = n_claims * max(SEVERITY_WEIGHTS.values())
|
| 55 |
|
| 56 |
+
# Coverage factor: sqrt-softened ratio; higher coverage amplifies the raw signal
|
| 57 |
if facts_total > 0:
|
| 58 |
+
raw_coverage = facts_verified / facts_total
|
| 59 |
+
coverage_factor = max(0.5, raw_coverage ** 0.5)
|
| 60 |
else:
|
| 61 |
+
coverage_factor = 0.7
|
|
|
|
| 62 |
|
| 63 |
# Main formula
|
| 64 |
penalty_ratio = min(1.0, weighted_penalty / max_possible_penalty) if max_possible_penalty > 0 else 0.0
|
| 65 |
raw_tcs = (1.0 - penalty_ratio) * score_coherence
|
| 66 |
|
| 67 |
+
# Confidence multiplier: amplify deviation from 0.5 proportionally to coverage
|
| 68 |
+
tcs = raw_tcs + coverage_factor * (raw_tcs - 0.5) * 0.3
|
| 69 |
+
if facts_verified == 0:
|
| 70 |
+
tcs = raw_tcs
|
| 71 |
|
| 72 |
# Clamp
|
| 73 |
tcs = max(0.0, min(1.0, tcs))
|
backend/pipeline/verification/internal.py
CHANGED
|
@@ -228,7 +228,8 @@ class InternalVerifier:
|
|
| 228 |
if SequenceMatcher(None, subj_h, subj_e).ratio() >= 0.85:
|
| 229 |
if _is_inauguration_or_election(e_fact):
|
| 230 |
point_e = _extract_point_time(e_fact)
|
| 231 |
-
|
|
|
|
| 232 |
inconsistencies.append(Inconsistency(
|
| 233 |
inconsistency_type=InconsistencyType.IMPLICIT_CONTRADICTION,
|
| 234 |
severity=Severity.MEDIUM,
|
|
|
|
| 228 |
if SequenceMatcher(None, subj_h, subj_e).ratio() >= 0.85:
|
| 229 |
if _is_inauguration_or_election(e_fact):
|
| 230 |
point_e = _extract_point_time(e_fact)
|
| 231 |
+
# 180-day buffer: election -> inauguration transition is normal (up to ~6 months)
|
| 232 |
+
if point_e and (point_e - start_h).days > 180:
|
| 233 |
inconsistencies.append(Inconsistency(
|
| 234 |
inconsistency_type=InconsistencyType.IMPLICIT_CONTRADICTION,
|
| 235 |
severity=Severity.MEDIUM,
|
frontend/src/components/ArticleHistory.tsx
CHANGED
|
@@ -147,7 +147,9 @@ function ArticleHistory({ open, onClose }: ArticleHistoryProps): React.ReactElem
|
|
| 147 |
<ListItemText
|
| 148 |
primary={
|
| 149 |
<Typography variant="body2" noWrap sx={{ fontWeight: "medium" }}>
|
| 150 |
-
{article.title
|
|
|
|
|
|
|
| 151 |
</Typography>
|
| 152 |
}
|
| 153 |
secondary={
|
|
|
|
| 147 |
<ListItemText
|
| 148 |
primary={
|
| 149 |
<Typography variant="body2" noWrap sx={{ fontWeight: "medium" }}>
|
| 150 |
+
{article.title && article.title.trim()
|
| 151 |
+
? article.title.trim().slice(0, 60)
|
| 152 |
+
: <em>Untitled</em>}
|
| 153 |
</Typography>
|
| 154 |
}
|
| 155 |
secondary={
|
start.ps1
DELETED
|
@@ -1,149 +0,0 @@
|
|
| 1 |
-
# Usage: .\start.ps1
|
| 2 |
-
# Starts Ollama, pulls required models, verifies spaCy models,
|
| 3 |
-
# then launches the backend (FastAPI :8000) and frontend (React :3000).
|
| 4 |
-
|
| 5 |
-
$BACKEND_URL = "http://localhost:8000"
|
| 6 |
-
$FRONTEND_URL = "http://localhost:3000"
|
| 7 |
-
|
| 8 |
-
function Write-Step { param($msg) Write-Host "[*] $msg" -ForegroundColor Cyan }
|
| 9 |
-
function Write-Ok { param($msg) Write-Host "[OK] $msg" -ForegroundColor Green }
|
| 10 |
-
function Write-Warn { param($msg) Write-Host "[!] $msg" -ForegroundColor Yellow }
|
| 11 |
-
function Write-Err { param($msg) Write-Host "[ERR] $msg" -ForegroundColor Red }
|
| 12 |
-
|
| 13 |
-
# Load .env into the current process environment
|
| 14 |
-
$envFile = Join-Path $PSScriptRoot ".env"
|
| 15 |
-
if (Test-Path $envFile) {
|
| 16 |
-
Get-Content $envFile | Where-Object { $_ -notmatch '^\s*#' -and $_ -match '=' } | ForEach-Object {
|
| 17 |
-
if ($_ -match '^([^=]+)=(.*)$') {
|
| 18 |
-
[System.Environment]::SetEnvironmentVariable($Matches[1].Trim(), $Matches[2].Trim(), 'Process')
|
| 19 |
-
}
|
| 20 |
-
}
|
| 21 |
-
}
|
| 22 |
-
|
| 23 |
-
# Resolve values from env with fallbacks
|
| 24 |
-
$OLLAMA_URL = if ($env:OLLAMA_HOST) { $env:OLLAMA_HOST } else { "http://localhost:11434" }
|
| 25 |
-
$LLM_MODELS = if ($env:LLM_MODELS) { $env:LLM_MODELS -split ',' | ForEach-Object { $_.Trim() } } else { @("sciphi/triplex") }
|
| 26 |
-
$SPACY_MODELS = if ($env:SPACY_MODELS) { $env:SPACY_MODELS -split ',' | ForEach-Object { $_.Trim() } } else { @("en_core_web_trf") }
|
| 27 |
-
|
| 28 |
-
# Check if a URL responds with HTTP 200
|
| 29 |
-
function Test-Http {
|
| 30 |
-
param($url)
|
| 31 |
-
try {
|
| 32 |
-
$resp = Invoke-WebRequest -Uri $url -UseBasicParsing -TimeoutSec 5 -ErrorAction Stop
|
| 33 |
-
return $resp.StatusCode -eq 200
|
| 34 |
-
} catch {
|
| 35 |
-
return $false
|
| 36 |
-
}
|
| 37 |
-
}
|
| 38 |
-
|
| 39 |
-
# [1/5] Ollama daemon
|
| 40 |
-
Write-Step "[1/5] Checking Ollama..."
|
| 41 |
-
if (-not (Test-Http "$OLLAMA_URL/api/tags")) {
|
| 42 |
-
Write-Warn "Ollama not running — starting ollama serve"
|
| 43 |
-
Start-Process -FilePath "ollama" -ArgumentList "serve" -WindowStyle Hidden
|
| 44 |
-
Start-Sleep -Seconds 3
|
| 45 |
-
if (-not (Test-Http "$OLLAMA_URL/api/tags")) {
|
| 46 |
-
Write-Err "Ollama failed to start. Is it installed?"
|
| 47 |
-
exit 1
|
| 48 |
-
}
|
| 49 |
-
}
|
| 50 |
-
Write-Ok "Ollama is running"
|
| 51 |
-
|
| 52 |
-
# [2/5] Ollama LLM models
|
| 53 |
-
Write-Step "[2/5] Checking Ollama LLM models..."
|
| 54 |
-
try {
|
| 55 |
-
$tagsJson = Invoke-RestMethod -Uri "$OLLAMA_URL/api/tags" -TimeoutSec 5
|
| 56 |
-
$pulledNames = $tagsJson.models | ForEach-Object { $_.name -replace ":latest$", "" }
|
| 57 |
-
} catch {
|
| 58 |
-
$pulledNames = @()
|
| 59 |
-
}
|
| 60 |
-
|
| 61 |
-
foreach ($model in $LLM_MODELS) {
|
| 62 |
-
$shortName = $model -replace ":latest$", ""
|
| 63 |
-
if ($pulledNames -contains $shortName) {
|
| 64 |
-
Write-Ok "LLM model present: $model"
|
| 65 |
-
} else {
|
| 66 |
-
Write-Warn "Pulling LLM model: $model"
|
| 67 |
-
& ollama pull $model
|
| 68 |
-
if ($LASTEXITCODE -ne 0) {
|
| 69 |
-
Write-Err "Failed to pull $model"
|
| 70 |
-
exit 1
|
| 71 |
-
}
|
| 72 |
-
Write-Ok "Pulled: $model"
|
| 73 |
-
}
|
| 74 |
-
}
|
| 75 |
-
|
| 76 |
-
# [3/5] spaCy NLP models
|
| 77 |
-
Write-Step "[3/5] Checking spaCy models..."
|
| 78 |
-
foreach ($model in $SPACY_MODELS) {
|
| 79 |
-
$result = & python -c "import spacy; spacy.load('$model'); print('ok')" 2>&1
|
| 80 |
-
if ("$result" -match "ok") {
|
| 81 |
-
Write-Ok "spaCy model present: $model"
|
| 82 |
-
} else {
|
| 83 |
-
Write-Warn "spaCy model not found: $model — downloading..."
|
| 84 |
-
& python -m spacy download $model
|
| 85 |
-
if ($LASTEXITCODE -ne 0) {
|
| 86 |
-
Write-Err "Failed to download spaCy model: $model"
|
| 87 |
-
exit 1
|
| 88 |
-
}
|
| 89 |
-
Write-Ok "Downloaded spaCy model: $model"
|
| 90 |
-
}
|
| 91 |
-
}
|
| 92 |
-
|
| 93 |
-
# [4/5] Backend
|
| 94 |
-
Write-Step "[4/5] Starting backend (uvicorn on :8000)..."
|
| 95 |
-
$backendJob = Start-Job -ScriptBlock {
|
| 96 |
-
param($root)
|
| 97 |
-
Set-Location $root
|
| 98 |
-
& "$root\venv\Scripts\Activate.ps1"
|
| 99 |
-
& "$root\venv\Scripts\uvicorn.exe" backend.main:app --host 0.0.0.0 --port 8000
|
| 100 |
-
} -ArgumentList $PWD.Path
|
| 101 |
-
|
| 102 |
-
Start-Sleep -Seconds 5
|
| 103 |
-
if (Test-Http "$BACKEND_URL/health") {
|
| 104 |
-
Write-Ok "Backend healthy at $BACKEND_URL"
|
| 105 |
-
} else {
|
| 106 |
-
Write-Warn "Backend did not respond in time — check logs below"
|
| 107 |
-
}
|
| 108 |
-
|
| 109 |
-
# [5/5] Frontend
|
| 110 |
-
Write-Step "[5/5] Starting frontend (npm start on :3000)..."
|
| 111 |
-
$frontendJob = Start-Job -ScriptBlock {
|
| 112 |
-
param($root)
|
| 113 |
-
Set-Location "$root\frontend"
|
| 114 |
-
& npm start
|
| 115 |
-
} -ArgumentList $PWD.Path
|
| 116 |
-
|
| 117 |
-
# Summary
|
| 118 |
-
Write-Host ""
|
| 119 |
-
Write-Host "======================================" -ForegroundColor Cyan
|
| 120 |
-
Write-Host " Backend $BACKEND_URL" -ForegroundColor Green
|
| 121 |
-
Write-Host " Frontend $FRONTEND_URL" -ForegroundColor Green
|
| 122 |
-
Write-Host " Ollama $OLLAMA_URL" -ForegroundColor Green
|
| 123 |
-
Write-Host " Press Ctrl+C to stop all services" -ForegroundColor Yellow
|
| 124 |
-
Write-Host "======================================" -ForegroundColor Cyan
|
| 125 |
-
Write-Host ""
|
| 126 |
-
|
| 127 |
-
# Stream job output until Ctrl+C
|
| 128 |
-
try {
|
| 129 |
-
while ($true) {
|
| 130 |
-
Receive-Job -Job $backendJob | ForEach-Object { Write-Host "[backend] $_" }
|
| 131 |
-
Receive-Job -Job $frontendJob | ForEach-Object { Write-Host "[frontend] $_" }
|
| 132 |
-
Start-Sleep -Milliseconds 500
|
| 133 |
-
|
| 134 |
-
if ($backendJob.State -eq "Failed") {
|
| 135 |
-
Write-Err "Backend job failed"
|
| 136 |
-
break
|
| 137 |
-
}
|
| 138 |
-
if ($frontendJob.State -eq "Failed") {
|
| 139 |
-
Write-Err "Frontend job failed"
|
| 140 |
-
break
|
| 141 |
-
}
|
| 142 |
-
}
|
| 143 |
-
} finally {
|
| 144 |
-
Write-Host ""
|
| 145 |
-
Write-Step "Shutting down..."
|
| 146 |
-
Stop-Job -Job $backendJob, $frontendJob -ErrorAction SilentlyContinue
|
| 147 |
-
Remove-Job -Job $backendJob, $frontendJob -ErrorAction SilentlyContinue -Force
|
| 148 |
-
Write-Ok "All services stopped"
|
| 149 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
start.sh
CHANGED
|
@@ -41,7 +41,7 @@ fi
|
|
| 41 |
# Resolve values from env with fallbacks
|
| 42 |
OLLAMA_URL="${OLLAMA_HOST:-http://localhost:11434}"
|
| 43 |
|
| 44 |
-
IFS=',' read -ra LLM_MODELS <<< "${LLM_MODELS:-
|
| 45 |
IFS=',' read -ra SPACY_MODELS <<< "${SPACY_MODELS:-en_core_web_trf}"
|
| 46 |
|
| 47 |
# Trim whitespace from each element
|
|
|
|
| 41 |
# Resolve values from env with fallbacks
|
| 42 |
OLLAMA_URL="${OLLAMA_HOST:-http://localhost:11434}"
|
| 43 |
|
| 44 |
+
IFS=',' read -ra LLM_MODELS <<< "${LLM_MODELS:-llama3}"
|
| 45 |
IFS=',' read -ra SPACY_MODELS <<< "${SPACY_MODELS:-en_core_web_trf}"
|
| 46 |
|
| 47 |
# Trim whitespace from each element
|