Skip to content

Commit ff506f4

Browse files
committed
feat: introduce unified LLM client for multi-provider support and add project requirements document for v2 init build
1 parent 7207687 commit ff506f4

9 files changed

Lines changed: 744 additions & 248 deletions

File tree

packages/cli/agents/domain_extractor.py

Lines changed: 4 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,7 @@
2626
from pathlib import Path
2727
from typing import Any
2828

29-
from packages.arc42gen.models.config import LLMConfig
30-
from packages.arc42gen.providers.factory import create_llm_provider
29+
from packages.cli.agents.llm_client import client_from_settings
3130
from packages.cli.graph.neo4j_client import NeoClient
3231
from packages.config.settings import Settings
3332

@@ -83,41 +82,6 @@
8382
_COUNT_EDGES = "MATCH ()-[r:IMPLEMENTS_DOMAIN]->() RETURN count(r) AS c"
8483

8584

86-
# ---------------------------------------------------------------------------
87-
# LLM config builder (mirrors summarizer pattern)
88-
# ---------------------------------------------------------------------------
89-
90-
def _build_llm_config(settings: Settings) -> LLMConfig:
91-
provider = settings.LLM_PROVIDER.lower()
92-
if provider == "ollama":
93-
return LLMConfig(
94-
provider="ollama",
95-
model=settings.LLM_MODEL_OLLAMA,
96-
api_key="",
97-
base_url=settings.OLLAMA_BASE_URL,
98-
max_tokens=2000,
99-
)
100-
elif provider == "anthropic":
101-
return LLMConfig(
102-
provider="anthropic",
103-
model=LLMConfig.DEFAULT_MODELS["anthropic"],
104-
api_key=settings.ANTHROPIC_API_KEY,
105-
max_tokens=2000,
106-
)
107-
elif provider == "gemini":
108-
return LLMConfig(
109-
provider="gemini",
110-
model=LLMConfig.DEFAULT_MODELS["gemini"],
111-
api_key=settings.GEMINI_API_KEY,
112-
max_tokens=2000,
113-
)
114-
else:
115-
raise ValueError(
116-
f"Unsupported LLM_PROVIDER '{provider}'. "
117-
"Set LLM_PROVIDER to ollama, anthropic, or gemini in .env"
118-
)
119-
120-
12185
# ---------------------------------------------------------------------------
12286
# Prompt builder
12387
# ---------------------------------------------------------------------------
@@ -258,8 +222,7 @@ def run_domain_extractor(
258222
progress_cb("fetch", f"Loaded {len(nodes)} nodes.")
259223

260224
# 2. Call LLM in batches; merge domains across responses
261-
llm_config = _build_llm_config(settings)
262-
llm = create_llm_provider(llm_config)
225+
llm = client_from_settings(settings)
263226

264227
merged: dict[str, dict] = {}
265228
batches = [nodes[i : i + batch_size] for i in range(0, len(nodes), batch_size)]
@@ -269,8 +232,8 @@ def run_domain_extractor(
269232
progress_cb("llm", f"LLM call {i}/{len(batches)} ({len(batch)} nodes)...")
270233
prompt = _build_batch_prompt(batch)
271234
try:
272-
response = llm.generate(prompt=prompt, temperature=0.1)
273-
parsed = _extract_json(response.content)
235+
raw_text = llm.complete(prompt, max_tokens=2000, temperature=0.1)
236+
parsed = _extract_json(raw_text)
274237
domains = parsed.get("domains", [])
275238
_merge_domains(merged, domains)
276239
except Exception as exc:

packages/cli/agents/embedder.py

Lines changed: 10 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,23 @@
22
Embedding agent.
33
44
Fetches nodes that have a summary but no embedding from Neo4j,
5-
generates a vector via Ollama, and writes it back.
5+
generates a vector via the configured LLM client, and writes it back.
66
77
Also creates Neo4j vector indexes on first run.
88
99
Config from packages.config.settings.Settings:
10-
OLLAMA_BASE_URL Ollama host (default http://localhost:11434)
11-
OLLAMA_EMBEDDING_MODEL Model for embeddings (default mxbai-embed-large)
12-
EMBEDDING_DIMENSION Vector dimensions (must match the model, default 1024)
10+
LLM_PROVIDER ollama | openai | anthropic
11+
OLLAMA_BASE_URL Ollama host (ollama + anthropic embed back-end)
12+
OLLAMA_EMBEDDING_MODEL Embed model (ollama + anthropic)
13+
OPENAI_EMBEDDING_MODEL Embed model (openai)
14+
OPENAI_API_KEY Required when LLM_PROVIDER=openai
15+
EMBEDDING_DIMENSION Vector dimensions (must match the model)
1316
"""
1417
from __future__ import annotations
1518

1619
from typing import Any
1720

18-
import requests
19-
21+
from packages.cli.agents.llm_client import client_from_settings
2022
from packages.cli.graph.neo4j_client import NeoClient
2123
from packages.config.settings import Settings
2224

@@ -56,22 +58,6 @@ def ensure_vector_indexes(client: NeoClient, dimensions: int) -> None:
5658
"""
5759

5860

59-
# ---------------------------------------------------------------------------
60-
# Ollama embed call
61-
# ---------------------------------------------------------------------------
62-
63-
def _embed(text: str, settings: Settings) -> list[float]:
64-
"""Call Ollama /api/embeddings and return the embedding vector."""
65-
url = f"{settings.OLLAMA_BASE_URL.rstrip('/')}/api/embeddings"
66-
resp = requests.post(
67-
url,
68-
json={"model": settings.OLLAMA_EMBEDDING_MODEL, "prompt": text},
69-
timeout=60,
70-
)
71-
resp.raise_for_status()
72-
return resp.json()["embedding"]
73-
74-
7561
# ---------------------------------------------------------------------------
7662
# Core runner
7763
# ---------------------------------------------------------------------------
@@ -96,6 +82,7 @@ def run_embedder(
9682
"""
9783
ensure_vector_indexes(client, settings.EMBEDDING_DIMENSION)
9884

85+
llm = client_from_settings(settings)
9986
counts: dict[str, int] = {label: 0 for label in _LABELS}
10087

10188
def _write(tx: Any, node_id: str, embedding: list[float]) -> None:
@@ -111,7 +98,7 @@ def _write(tx: Any, node_id: str, embedding: list[float]) -> None:
11198

11299
for node in rows:
113100
try:
114-
embedding = _embed(node["summary"], settings)
101+
embedding = llm.embed(node["summary"])
115102
except Exception as exc:
116103
print(f" [warn] embedding failed for {node['id']}: {exc}")
117104
continue

0 commit comments

Comments
 (0)