loreguard-cli 0.14.3__tar.gz → 0.14.6__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {loreguard_cli-0.14.3 → loreguard_cli-0.14.6}/PKG-INFO +2 -2
- {loreguard_cli-0.14.3 → loreguard_cli-0.14.6}/pyproject.toml +2 -2
- {loreguard_cli-0.14.3 → loreguard_cli-0.14.6}/src/intent_classifier.py +37 -22
- loreguard_cli-0.14.6/tests/test_intent_classifier.py +48 -0
- {loreguard_cli-0.14.3 → loreguard_cli-0.14.6}/uv.lock +1 -1
- {loreguard_cli-0.14.3 → loreguard_cli-0.14.6}/.claude/skills/llama-cpp-troubleshooting/SKILL.md +0 -0
- {loreguard_cli-0.14.3 → loreguard_cli-0.14.6}/.env.example +0 -0
- {loreguard_cli-0.14.3 → loreguard_cli-0.14.6}/.github/workflows/release.yml +0 -0
- {loreguard_cli-0.14.3 → loreguard_cli-0.14.6}/.gitignore +0 -0
- {loreguard_cli-0.14.3 → loreguard_cli-0.14.6}/LICENSE +0 -0
- {loreguard_cli-0.14.3 → loreguard_cli-0.14.6}/README.md +0 -0
- {loreguard_cli-0.14.3 → loreguard_cli-0.14.6}/THIRD_PARTY_NOTICES.md +0 -0
- {loreguard_cli-0.14.3 → loreguard_cli-0.14.6}/loreguard.spec +0 -0
- {loreguard_cli-0.14.3 → loreguard_cli-0.14.6}/loreguard_entry.py +0 -0
- {loreguard_cli-0.14.3 → loreguard_cli-0.14.6}/scripts/build.py +0 -0
- {loreguard_cli-0.14.3 → loreguard_cli-0.14.6}/sdk/API.md +0 -0
- {loreguard_cli-0.14.3 → loreguard_cli-0.14.6}/sdk/csharp/LoreguardSDK.cs +0 -0
- {loreguard_cli-0.14.3 → loreguard_cli-0.14.6}/sdk/gdscript/LoreguardSDK.gd +0 -0
- {loreguard_cli-0.14.3 → loreguard_cli-0.14.6}/sdk/javascript/loreguard-sdk.js +0 -0
- {loreguard_cli-0.14.3 → loreguard_cli-0.14.6}/sdk/python/loreguard_sdk.py +0 -0
- {loreguard_cli-0.14.3 → loreguard_cli-0.14.6}/src/__init__.py +0 -0
- {loreguard_cli-0.14.3 → loreguard_cli-0.14.6}/src/__main__.py +0 -0
- {loreguard_cli-0.14.3 → loreguard_cli-0.14.6}/src/chunk_detector.py +0 -0
- {loreguard_cli-0.14.3 → loreguard_cli-0.14.6}/src/cli.py +0 -0
- {loreguard_cli-0.14.3 → loreguard_cli-0.14.6}/src/config.py +0 -0
- {loreguard_cli-0.14.3 → loreguard_cli-0.14.6}/src/dialogue_act_classifier.py +0 -0
- {loreguard_cli-0.14.3 → loreguard_cli-0.14.6}/src/hf_discovery.py +0 -0
- {loreguard_cli-0.14.3 → loreguard_cli-0.14.6}/src/http_server.py +0 -0
- {loreguard_cli-0.14.3 → loreguard_cli-0.14.6}/src/llama_server.py +0 -0
- {loreguard_cli-0.14.3 → loreguard_cli-0.14.6}/src/llm.py +0 -0
- {loreguard_cli-0.14.3 → loreguard_cli-0.14.6}/src/main.py +0 -0
- {loreguard_cli-0.14.3 → loreguard_cli-0.14.6}/src/models_registry.py +0 -0
- {loreguard_cli-0.14.3 → loreguard_cli-0.14.6}/src/nli.py +0 -0
- {loreguard_cli-0.14.3 → loreguard_cli-0.14.6}/src/npc_chat.py +0 -0
- {loreguard_cli-0.14.3 → loreguard_cli-0.14.6}/src/runtime.py +0 -0
- {loreguard_cli-0.14.3 → loreguard_cli-0.14.6}/src/steam.py +0 -0
- {loreguard_cli-0.14.3 → loreguard_cli-0.14.6}/src/term_ui.py +0 -0
- {loreguard_cli-0.14.3 → loreguard_cli-0.14.6}/src/tui/__init__.py +0 -0
- {loreguard_cli-0.14.3 → loreguard_cli-0.14.6}/src/tui/app.py +0 -0
- {loreguard_cli-0.14.3 → loreguard_cli-0.14.6}/src/tui/modals/__init__.py +0 -0
- {loreguard_cli-0.14.3 → loreguard_cli-0.14.6}/src/tui/modals/auth_menu.py +0 -0
- {loreguard_cli-0.14.3 → loreguard_cli-0.14.6}/src/tui/modals/npc_chat.py +0 -0
- {loreguard_cli-0.14.3 → loreguard_cli-0.14.6}/src/tui/modals/token_input.py +0 -0
- {loreguard_cli-0.14.3 → loreguard_cli-0.14.6}/src/tui/modals/unified_palette.py +0 -0
- {loreguard_cli-0.14.3 → loreguard_cli-0.14.6}/src/tui/screens/__init__.py +0 -0
- {loreguard_cli-0.14.3 → loreguard_cli-0.14.6}/src/tui/screens/auth.py +0 -0
- {loreguard_cli-0.14.3 → loreguard_cli-0.14.6}/src/tui/screens/main.py +0 -0
- {loreguard_cli-0.14.3 → loreguard_cli-0.14.6}/src/tui/screens/model_select.py +0 -0
- {loreguard_cli-0.14.3 → loreguard_cli-0.14.6}/src/tui/screens/nli_setup.py +0 -0
- {loreguard_cli-0.14.3 → loreguard_cli-0.14.6}/src/tui/screens/running.py +0 -0
- {loreguard_cli-0.14.3 → loreguard_cli-0.14.6}/src/tui/styles.py +0 -0
- {loreguard_cli-0.14.3 → loreguard_cli-0.14.6}/src/tui/widgets/__init__.py +0 -0
- {loreguard_cli-0.14.3 → loreguard_cli-0.14.6}/src/tui/widgets/banner.py +0 -0
- {loreguard_cli-0.14.3 → loreguard_cli-0.14.6}/src/tui/widgets/footer.py +0 -0
- {loreguard_cli-0.14.3 → loreguard_cli-0.14.6}/src/tui/widgets/hardware_info.py +0 -0
- {loreguard_cli-0.14.3 → loreguard_cli-0.14.6}/src/tui/widgets/npc_chat.py +0 -0
- {loreguard_cli-0.14.3 → loreguard_cli-0.14.6}/src/tui/widgets/server_monitor.py +0 -0
- {loreguard_cli-0.14.3 → loreguard_cli-0.14.6}/src/tui/widgets/status_panel.py +0 -0
- {loreguard_cli-0.14.3 → loreguard_cli-0.14.6}/src/tunnel.py +0 -0
- {loreguard_cli-0.14.3 → loreguard_cli-0.14.6}/src/wizard.py +0 -0
- {loreguard_cli-0.14.3 → loreguard_cli-0.14.6}/templates/llama31-no-tools.jinja +0 -0
- {loreguard_cli-0.14.3 → loreguard_cli-0.14.6}/tests/test_nli_hhem.py +0 -0
- {loreguard_cli-0.14.3 → loreguard_cli-0.14.6}/tests/test_websocket_timeout.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: loreguard-cli
|
|
3
|
-
Version: 0.14.
|
|
3
|
+
Version: 0.14.6
|
|
4
4
|
Summary: Local inference client for Loreguard NPCs
|
|
5
5
|
Project-URL: Homepage, https://loreguard.com
|
|
6
6
|
Project-URL: Documentation, https://github.com/beyond-logic-labs/loreguard-cli#readme
|
|
@@ -29,7 +29,7 @@ Requires-Dist: rich>=13.0.0
|
|
|
29
29
|
Requires-Dist: textual>=0.47.0
|
|
30
30
|
Requires-Dist: tf-keras>=2.16.0
|
|
31
31
|
Requires-Dist: torch>=2.0.0
|
|
32
|
-
Requires-Dist: transformers>=
|
|
32
|
+
Requires-Dist: transformers>=5.0.0
|
|
33
33
|
Requires-Dist: uvicorn>=0.27.0
|
|
34
34
|
Requires-Dist: websockets>=12.0
|
|
35
35
|
Provides-Extra: build
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "loreguard-cli"
|
|
7
|
-
version = "0.14.
|
|
7
|
+
version = "0.14.6"
|
|
8
8
|
description = "Local inference client for Loreguard NPCs"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "MIT"
|
|
@@ -28,7 +28,7 @@ dependencies = [
|
|
|
28
28
|
"aiofiles>=24.1.0",
|
|
29
29
|
"rich>=13.0.0",
|
|
30
30
|
"textual>=0.47.0",
|
|
31
|
-
"transformers>=
|
|
31
|
+
"transformers>=5.0.0",
|
|
32
32
|
"torch>=2.0.0",
|
|
33
33
|
"fastapi>=0.109.0",
|
|
34
34
|
"uvicorn>=0.27.0",
|
|
@@ -40,16 +40,28 @@ class IntentResult:
|
|
|
40
40
|
# DeBERTa-v3-large is state-of-the-art for zero-shot classification
|
|
41
41
|
DEFAULT_INTENT_MODEL = "MoritzLaurer/DeBERTa-v3-large-zeroshot-v2.0"
|
|
42
42
|
|
|
43
|
-
# Intent
|
|
44
|
-
#
|
|
45
|
-
#
|
|
46
|
-
#
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
43
|
+
# Intent label descriptions for zero-shot classification.
|
|
44
|
+
# Keep these mutually exclusive and concrete to reduce confusion between:
|
|
45
|
+
# - social greetings vs factual requests
|
|
46
|
+
# - working-memory questions vs retrieval questions
|
|
47
|
+
INTENT_LABEL_DESCRIPTIONS = {
|
|
48
|
+
IntentLabel.NO_RETRIEVAL: (
|
|
49
|
+
"a greeting, acknowledgement, farewell, or social small talk "
|
|
50
|
+
"(examples: hi, hello, hey, yo, thanks, okay, bye) without asking "
|
|
51
|
+
"for specific factual information"
|
|
52
|
+
),
|
|
53
|
+
IntentLabel.WORKING_MEMORY: (
|
|
54
|
+
"a question about the NPC's own current state, feelings, recent "
|
|
55
|
+
"experiences, or personal memory that can be answered from working memory"
|
|
56
|
+
),
|
|
57
|
+
IntentLabel.LIGHT_RETRIEVAL: (
|
|
58
|
+
"a request for one specific factual detail (number, fee, date, name, "
|
|
59
|
+
"location, status, or single-file fact) that needs light retrieval"
|
|
60
|
+
),
|
|
61
|
+
IntentLabel.FULL_RETRIEVAL: (
|
|
62
|
+
"a complex request requiring multiple facts, synthesis, planning, "
|
|
63
|
+
"comparison, or multi-step reasoning across sources"
|
|
64
|
+
),
|
|
53
65
|
}
|
|
54
66
|
|
|
55
67
|
# Promise detection hypothesis for follow-up triggers (ADR-0020)
|
|
@@ -156,32 +168,35 @@ class IntentClassifier:
|
|
|
156
168
|
|
|
157
169
|
start_time = time.time()
|
|
158
170
|
|
|
159
|
-
#
|
|
160
|
-
|
|
161
|
-
hypotheses = list(INTENT_HYPOTHESES.values())
|
|
171
|
+
# Candidate labels are descriptive intent classes.
|
|
172
|
+
candidate_labels = list(INTENT_LABEL_DESCRIPTIONS.values())
|
|
162
173
|
|
|
163
|
-
# Run zero-shot classification
|
|
164
|
-
#
|
|
174
|
+
# Run zero-shot classification.
|
|
175
|
+
# Using a consistent hypothesis template tends to be more stable than
|
|
176
|
+
# passing full natural-language hypotheses as labels.
|
|
165
177
|
result = self._classifier(
|
|
166
178
|
query,
|
|
167
|
-
candidate_labels=
|
|
168
|
-
hypothesis_template="{}",
|
|
179
|
+
candidate_labels=candidate_labels,
|
|
180
|
+
hypothesis_template="This user message is {}.",
|
|
169
181
|
multi_label=False,
|
|
170
182
|
)
|
|
171
183
|
|
|
172
184
|
latency_ms = int((time.time() - start_time) * 1000)
|
|
173
185
|
|
|
174
|
-
# Map the winning
|
|
175
|
-
|
|
186
|
+
# Map the winning description back to intent label
|
|
187
|
+
winning_description = result["labels"][0]
|
|
176
188
|
confidence = result["scores"][0]
|
|
177
189
|
|
|
178
|
-
# Find the intent that corresponds to the winning
|
|
190
|
+
# Find the intent that corresponds to the winning description
|
|
179
191
|
intent = IntentLabel.FULL_RETRIEVAL # Default
|
|
180
|
-
for label,
|
|
181
|
-
if
|
|
192
|
+
for label, description in INTENT_LABEL_DESCRIPTIONS.items():
|
|
193
|
+
if description == winning_description:
|
|
182
194
|
intent = label
|
|
183
195
|
break
|
|
184
196
|
|
|
197
|
+
# Log full score distribution for tuning/debugging.
|
|
198
|
+
label_score_pairs = list(zip(result["labels"], result["scores"]))
|
|
199
|
+
logger.debug("Intent score distribution: %s", label_score_pairs)
|
|
185
200
|
logger.info(f"Intent classification: {intent.value} (confidence={confidence:.2f}, latency={latency_ms}ms)")
|
|
186
201
|
|
|
187
202
|
return IntentResult(
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
from src.intent_classifier import (
|
|
2
|
+
INTENT_LABEL_DESCRIPTIONS,
|
|
3
|
+
IntentClassifier,
|
|
4
|
+
IntentLabel,
|
|
5
|
+
)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class _FakeClassifier:
|
|
9
|
+
def __init__(self, labels, scores):
|
|
10
|
+
self.labels = labels
|
|
11
|
+
self.scores = scores
|
|
12
|
+
self.calls = []
|
|
13
|
+
|
|
14
|
+
def __call__(self, query, **kwargs):
|
|
15
|
+
self.calls.append((query, kwargs))
|
|
16
|
+
return {"labels": self.labels, "scores": self.scores}
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def test_classify_uses_descriptive_labels_and_maps_winner():
|
|
20
|
+
winner = INTENT_LABEL_DESCRIPTIONS[IntentLabel.NO_RETRIEVAL]
|
|
21
|
+
second = INTENT_LABEL_DESCRIPTIONS[IntentLabel.LIGHT_RETRIEVAL]
|
|
22
|
+
fake = _FakeClassifier(labels=[winner, second], scores=[0.88, 0.12])
|
|
23
|
+
|
|
24
|
+
classifier = IntentClassifier(model_path="dummy-model")
|
|
25
|
+
classifier._classifier = fake
|
|
26
|
+
|
|
27
|
+
result = classifier.classify("hi")
|
|
28
|
+
|
|
29
|
+
assert result.intent == IntentLabel.NO_RETRIEVAL
|
|
30
|
+
assert result.confidence == 0.88
|
|
31
|
+
assert result.latency_ms >= 0
|
|
32
|
+
|
|
33
|
+
assert len(fake.calls) == 1
|
|
34
|
+
_, kwargs = fake.calls[0]
|
|
35
|
+
assert kwargs["candidate_labels"] == list(INTENT_LABEL_DESCRIPTIONS.values())
|
|
36
|
+
assert kwargs["hypothesis_template"] == "This user message is {}."
|
|
37
|
+
assert kwargs["multi_label"] is False
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def test_classify_with_fallback_returns_full_retrieval_on_error():
|
|
41
|
+
classifier = IntentClassifier(model_path="dummy-model")
|
|
42
|
+
classifier._classifier = None
|
|
43
|
+
|
|
44
|
+
result = classifier.classify_with_fallback("hi")
|
|
45
|
+
|
|
46
|
+
assert result.intent == IntentLabel.FULL_RETRIEVAL
|
|
47
|
+
assert result.confidence == 0.0
|
|
48
|
+
assert result.latency_ms == 0
|
{loreguard_cli-0.14.3 → loreguard_cli-0.14.6}/.claude/skills/llama-cpp-troubleshooting/SKILL.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|