kstudiochat 1.0.0__py3-none-any.whl
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.
- deployments/cli.py +318 -0
- deployments/server.py +491 -0
- deployments/start.py +22 -0
- engine/__init__.py +57 -0
- engine/__main__.py +5 -0
- engine/audit.py +809 -0
- engine/bot.py +632 -0
- engine/config.py +255 -0
- engine/curator.py +225 -0
- engine/extension_base.py +48 -0
- engine/extensions/__init__.py +28 -0
- engine/extensions/adaptive.py +272 -0
- engine/extensions/conversational_memory.py +285 -0
- engine/extensions/live.py +420 -0
- engine/extensions/multiagent.py +231 -0
- engine/extensions/multimodal.py +218 -0
- engine/extensions/predictive.py +187 -0
- engine/extensions/rlhf.py +308 -0
- engine/extensions/voice.py +312 -0
- engine/llm.py +236 -0
- engine/memory.py +253 -0
- engine/modules/__init__.py +6 -0
- engine/modules/cultural.py +78 -0
- engine/modules/empathy.py +56 -0
- engine/modules/ethics.py +96 -0
- engine/modules/intent.py +85 -0
- engine/modules/wcce.py +112 -0
- engine/modules/xai.py +55 -0
- engine/pack_context.py +110 -0
- engine/personality.py +102 -0
- engine/provider_runtime.py +163 -0
- engine/retrieval.py +347 -0
- engine/session.py +190 -0
- engine/skills.py +559 -0
- engine/templates.py +189 -0
- engine/tools.py +227 -0
- engine/utils.py +120 -0
- engine/verification.py +204 -0
- kstudiochat-1.0.0.dist-info/METADATA +243 -0
- kstudiochat-1.0.0.dist-info/RECORD +49 -0
- kstudiochat-1.0.0.dist-info/WHEEL +5 -0
- kstudiochat-1.0.0.dist-info/entry_points.txt +2 -0
- kstudiochat-1.0.0.dist-info/top_level.txt +3 -0
- widgets/integrations/__init__.py +26 -0
- widgets/integrations/adapters.py +933 -0
- widgets/integrations/base.py +112 -0
- widgets/integrations/slack.py +144 -0
- widgets/integrations/telegram.py +149 -0
- widgets/integrations/whatsapp.py +141 -0
deployments/cli.py
ADDED
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
"""
|
|
2
|
+
K-CHAT CLI entry point.
|
|
3
|
+
|
|
4
|
+
Usage:
|
|
5
|
+
python -m kchat chat --data ./examples/government
|
|
6
|
+
python -m kchat serve --data ./examples/government --port 8000
|
|
7
|
+
python -m kchat init ./my_industry
|
|
8
|
+
python -m kchat validate ./examples/government
|
|
9
|
+
python -m kchat info ./examples/government
|
|
10
|
+
"""
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import argparse
|
|
14
|
+
import asyncio
|
|
15
|
+
import json
|
|
16
|
+
import logging
|
|
17
|
+
import sys
|
|
18
|
+
from pathlib import Path
|
|
19
|
+
|
|
20
|
+
from engine.config import IndustryPack
|
|
21
|
+
from engine.retrieval import DocumentLoader
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _init_readline():
|
|
25
|
+
"""Enable readline-style history if available."""
|
|
26
|
+
try:
|
|
27
|
+
import readline
|
|
28
|
+
import atexit
|
|
29
|
+
histfile = Path.home() / ".kchat_history"
|
|
30
|
+
try:
|
|
31
|
+
readline.read_history_file(histfile)
|
|
32
|
+
except (FileNotFoundError, OSError):
|
|
33
|
+
pass
|
|
34
|
+
atexit.register(readline.write_history_file, histfile)
|
|
35
|
+
except ImportError:
|
|
36
|
+
pass
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _color(text: str, code: str) -> str:
|
|
40
|
+
"""Wrap text in ANSI color code (no-op on Windows without colorama)."""
|
|
41
|
+
try:
|
|
42
|
+
import colorama
|
|
43
|
+
colorama.just_fix_windows_console()
|
|
44
|
+
except ImportError:
|
|
45
|
+
pass
|
|
46
|
+
import os
|
|
47
|
+
if os.environ.get("NO_COLOR"):
|
|
48
|
+
return text
|
|
49
|
+
codes = {"green": "32", "yellow": "33", "blue": "34", "cyan": "36", "bold": "1", "dim": "2"}
|
|
50
|
+
c = codes.get(code, "")
|
|
51
|
+
return f"\033[{c}m{text}\033[0m" if c else text
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
async def _async_chat_loop(bot) -> int:
|
|
55
|
+
_init_readline()
|
|
56
|
+
name = bot.config.name
|
|
57
|
+
print(f"{_color('K-CHAT', 'cyan')} — {_color(name, 'bold')}")
|
|
58
|
+
print(f"Persona: {bot.config.persona}")
|
|
59
|
+
print(f"LLM: {_color(bot.llm.provider_name, 'green')}")
|
|
60
|
+
n_chunks = len(bot.retriever.store._chunks) if hasattr(bot.retriever.store, '_chunks') else 'n/a'
|
|
61
|
+
print(f"Knowledge chunks: {_color(str(n_chunks), 'yellow')}")
|
|
62
|
+
print(f"Commands: {_color('/help', 'dim')} {_color('/clear', 'dim')} {_color('/quit', 'dim')}\n")
|
|
63
|
+
|
|
64
|
+
user_history = []
|
|
65
|
+
|
|
66
|
+
while True:
|
|
67
|
+
try:
|
|
68
|
+
prompt = _color(f"({name}) ", "dim") + _color("You", "green") + " > "
|
|
69
|
+
user_input = input(prompt)
|
|
70
|
+
except (EOFError, KeyboardInterrupt):
|
|
71
|
+
print(f"\n{_color('Bot', 'cyan')}: Goodbye!")
|
|
72
|
+
break
|
|
73
|
+
if not user_input.strip():
|
|
74
|
+
continue
|
|
75
|
+
|
|
76
|
+
# Multiline: if line ends with """, keep reading
|
|
77
|
+
if user_input.rstrip().endswith("\"\"\""):
|
|
78
|
+
lines = [user_input]
|
|
79
|
+
while True:
|
|
80
|
+
try:
|
|
81
|
+
line = input(_color("... ", "dim"))
|
|
82
|
+
except (EOFError, KeyboardInterrupt):
|
|
83
|
+
break
|
|
84
|
+
if line.rstrip().startswith("\"\"\""):
|
|
85
|
+
lines.append(line)
|
|
86
|
+
break
|
|
87
|
+
lines.append(line)
|
|
88
|
+
user_input = "\n".join(lines)
|
|
89
|
+
|
|
90
|
+
cmd = user_input.strip().lower()
|
|
91
|
+
if cmd in ("/quit", "/exit", "/bye", "quit", "exit", "bye"):
|
|
92
|
+
print(f"{_color('Bot', 'cyan')}: Goodbye!")
|
|
93
|
+
break
|
|
94
|
+
if cmd == "/clear":
|
|
95
|
+
user_history.clear()
|
|
96
|
+
import os as _os
|
|
97
|
+
_os.system("cls" if _os.name == "nt" else "clear")
|
|
98
|
+
continue
|
|
99
|
+
if cmd == "/help":
|
|
100
|
+
print(f"Commands:")
|
|
101
|
+
print(f" {_color('/quit', 'bold')} - Exit the chat")
|
|
102
|
+
print(f" {_color('/clear', 'bold')} - Clear screen")
|
|
103
|
+
print(f" {_color('/multiline', 'bold')} - Enter multiline mode (end line with \"\"\")")
|
|
104
|
+
print(f" {_color('/help', 'bold')} - Show this help")
|
|
105
|
+
continue
|
|
106
|
+
try:
|
|
107
|
+
r = await bot.chat(user_input, user_id="cli_user")
|
|
108
|
+
except Exception as e:
|
|
109
|
+
print(f"{_color('Error', 'yellow')}: {e}")
|
|
110
|
+
logging.exception("chat failed")
|
|
111
|
+
continue
|
|
112
|
+
user_history.append((user_input, r.answer))
|
|
113
|
+
print(f"{_color('Bot', 'cyan')}: {r.answer}")
|
|
114
|
+
extras = []
|
|
115
|
+
if r.citations:
|
|
116
|
+
extras.append(f"sources: {', '.join(r.citations[:3])}")
|
|
117
|
+
if r.confidence:
|
|
118
|
+
extras.append(f"confidence: {r.confidence} ({r.confidence_score:.2f})")
|
|
119
|
+
if r.intent:
|
|
120
|
+
extras.append(f"intent: {r.intent}")
|
|
121
|
+
if extras:
|
|
122
|
+
print(f" {_color(' | '.join(extras), 'dim')}")
|
|
123
|
+
print()
|
|
124
|
+
return 0
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def cmd_chat(args) -> int:
|
|
128
|
+
from engine import Bot
|
|
129
|
+
try:
|
|
130
|
+
bot = Bot(data=args.data)
|
|
131
|
+
except (FileNotFoundError, NotADirectoryError, ValueError) as e:
|
|
132
|
+
print(f"Error loading industry pack '{args.data}': {e}")
|
|
133
|
+
return 2
|
|
134
|
+
return asyncio.run(_async_chat_loop(bot))
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def cmd_serve(args) -> int:
|
|
138
|
+
from deployments.server import run_server
|
|
139
|
+
return run_server(data=args.data, host=args.host, port=args.port)
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def cmd_templates(args) -> int:
|
|
143
|
+
"""List available industry pack templates."""
|
|
144
|
+
from engine.templates import TEMPLATE_DESCRIPTIONS
|
|
145
|
+
print("Available templates:")
|
|
146
|
+
for name, desc in TEMPLATE_DESCRIPTIONS.items():
|
|
147
|
+
print(f" {name:15s} {desc}")
|
|
148
|
+
print("\nUsage: kchat init ./my-bot --template <name>")
|
|
149
|
+
return 0
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def cmd_init(args) -> int:
|
|
153
|
+
target = Path(args.path).resolve()
|
|
154
|
+
if target.exists() and any(target.iterdir()):
|
|
155
|
+
if not args.force:
|
|
156
|
+
print(f"'{target}' exists and is not empty. Use --force to overwrite.")
|
|
157
|
+
return 1
|
|
158
|
+
tmpl = getattr(args, "template", None)
|
|
159
|
+
pack = IndustryPack(path=target, config=None) # type: ignore[arg-type]
|
|
160
|
+
pack.init_template(template_name=tmpl)
|
|
161
|
+
print(f"Initialized industry pack at: {target}")
|
|
162
|
+
if tmpl:
|
|
163
|
+
print(f"Template: {tmpl}")
|
|
164
|
+
print("Start chatting immediately:")
|
|
165
|
+
print(f" python -m kchat chat --data {target}")
|
|
166
|
+
else:
|
|
167
|
+
print("Next steps:")
|
|
168
|
+
print(f" 1. Edit {target / 'config.json'}")
|
|
169
|
+
print(f" 2. Add documents to {target / 'knowledge'}/")
|
|
170
|
+
print(f" 3. Run: python -m kchat chat --data {target}")
|
|
171
|
+
return 0
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def cmd_validate(args) -> int:
|
|
175
|
+
"""Validate an industry pack: check files, count chunks, dry-run a query."""
|
|
176
|
+
target = Path(args.data)
|
|
177
|
+
errors: list[str] = []
|
|
178
|
+
warnings: list[str] = []
|
|
179
|
+
|
|
180
|
+
if not target.exists():
|
|
181
|
+
print(f"FAIL: path does not exist: {target}")
|
|
182
|
+
return 1
|
|
183
|
+
if not target.is_dir():
|
|
184
|
+
print(f"FAIL: not a directory: {target}")
|
|
185
|
+
return 1
|
|
186
|
+
|
|
187
|
+
# config.json
|
|
188
|
+
config_path = target / "config.json"
|
|
189
|
+
if not config_path.exists():
|
|
190
|
+
errors.append("missing config.json")
|
|
191
|
+
else:
|
|
192
|
+
try:
|
|
193
|
+
pack = IndustryPack.load(target)
|
|
194
|
+
print(f"OK: config.json loaded — bot='{pack.config.name}'")
|
|
195
|
+
except Exception as e:
|
|
196
|
+
errors.append(f"config.json invalid: {e}")
|
|
197
|
+
pack = None # type: ignore
|
|
198
|
+
|
|
199
|
+
if pack:
|
|
200
|
+
# Knowledge
|
|
201
|
+
if not pack.knowledge_path or not pack.knowledge_path.exists():
|
|
202
|
+
warnings.append("no knowledge/ directory — bot will only have general config")
|
|
203
|
+
else:
|
|
204
|
+
loader = DocumentLoader()
|
|
205
|
+
chunks = loader.load_directory(pack.knowledge_path)
|
|
206
|
+
print(f"OK: knowledge/ — {len(chunks)} chunks indexed")
|
|
207
|
+
if len(chunks) == 0:
|
|
208
|
+
warnings.append("knowledge/ is empty — add .md or .txt files")
|
|
209
|
+
# Refusal
|
|
210
|
+
if not pack.refusal:
|
|
211
|
+
warnings.append("no refusal.json — using built-in defaults")
|
|
212
|
+
# Intents
|
|
213
|
+
if not pack.intents:
|
|
214
|
+
warnings.append("no intents.json — using built-in defaults")
|
|
215
|
+
|
|
216
|
+
# Print summary
|
|
217
|
+
print()
|
|
218
|
+
if errors:
|
|
219
|
+
print("ERRORS:")
|
|
220
|
+
for e in errors:
|
|
221
|
+
print(f" - {e}")
|
|
222
|
+
if warnings:
|
|
223
|
+
print("WINTS:")
|
|
224
|
+
for w in warnings:
|
|
225
|
+
print(f" - {w}")
|
|
226
|
+
if not errors and not warnings:
|
|
227
|
+
print("All checks passed.")
|
|
228
|
+
return 1 if errors else 0
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
def cmd_curate(args) -> int:
|
|
232
|
+
"""Audit an industry pack's knowledge/ for quality issues."""
|
|
233
|
+
from engine.curator import Curator
|
|
234
|
+
target = Path(args.data)
|
|
235
|
+
knowledge = target / "knowledge"
|
|
236
|
+
if not knowledge.exists() or not knowledge.is_dir():
|
|
237
|
+
print(f"FAIL: knowledge directory not found at: {knowledge}")
|
|
238
|
+
return 1
|
|
239
|
+
curator = Curator(knowledge)
|
|
240
|
+
report = curator.audit()
|
|
241
|
+
print(report.format())
|
|
242
|
+
return 0 if report.passed else 1
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
def cmd_info(args) -> int:
|
|
246
|
+
pack = IndustryPack.load(args.data)
|
|
247
|
+
print(json.dumps(pack.config.to_dict(), indent=2, ensure_ascii=False))
|
|
248
|
+
return 0
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
def cmd_audit(args) -> int:
|
|
252
|
+
"""Run self-audit on an industry pack."""
|
|
253
|
+
from engine import Bot
|
|
254
|
+
try:
|
|
255
|
+
bot = Bot(data=args.data)
|
|
256
|
+
except (FileNotFoundError, NotADirectoryError, ValueError) as e:
|
|
257
|
+
print(f"Error loading industry pack '{args.data}': {e}")
|
|
258
|
+
return 2
|
|
259
|
+
|
|
260
|
+
report = bot.audit()
|
|
261
|
+
print(report.summary)
|
|
262
|
+
return 0 if report.status == "healthy" else (1 if report.status == "degraded" else 2)
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
def main(argv: list[str] | None = None) -> int:
|
|
266
|
+
parser = argparse.ArgumentParser(
|
|
267
|
+
prog="kchat",
|
|
268
|
+
description="K-CHAT — Universal Chatbot Engine (anti-hallucination by construction)",
|
|
269
|
+
)
|
|
270
|
+
parser.add_argument("--log-level", default="WARNING", choices=["DEBUG", "INFO", "WARNING", "ERROR"])
|
|
271
|
+
sub = parser.add_subparsers(dest="command", required=True)
|
|
272
|
+
|
|
273
|
+
p_chat = sub.add_parser("chat", help="Interactive chat in the terminal")
|
|
274
|
+
p_chat.add_argument("--data", required=True, help="Path to industry pack folder")
|
|
275
|
+
p_chat.set_defaults(func=cmd_chat)
|
|
276
|
+
|
|
277
|
+
p_serve = sub.add_parser("serve", help="Run as REST API server")
|
|
278
|
+
p_serve.add_argument("--data", required=True, help="Path to industry pack folder")
|
|
279
|
+
p_serve.add_argument("--host", default="0.0.0.0")
|
|
280
|
+
p_serve.add_argument("--port", type=int, default=8000)
|
|
281
|
+
p_serve.set_defaults(func=cmd_serve)
|
|
282
|
+
|
|
283
|
+
p_init = sub.add_parser("init", help="Initialize a new industry pack")
|
|
284
|
+
p_init.add_argument("path", help="Folder to create")
|
|
285
|
+
p_init.add_argument("--force", action="store_true")
|
|
286
|
+
p_init.add_argument("--template", choices=["government", "restaurant", "healthcare"],
|
|
287
|
+
help="Pre-built template to use (creates a working pack immediately)")
|
|
288
|
+
p_init.set_defaults(func=cmd_init)
|
|
289
|
+
|
|
290
|
+
p_tpl = sub.add_parser("templates", help="List available industry pack templates")
|
|
291
|
+
p_tpl.set_defaults(func=cmd_templates)
|
|
292
|
+
|
|
293
|
+
p_val = sub.add_parser("validate", help="Validate an industry pack")
|
|
294
|
+
p_val.add_argument("--data", required=True)
|
|
295
|
+
p_val.set_defaults(func=cmd_validate)
|
|
296
|
+
|
|
297
|
+
p_curate = sub.add_parser("curate", help="Audit knowledge/ for quality issues (duplicates, low-signal, etc.)")
|
|
298
|
+
p_curate.add_argument("--data", required=True, help="Path to industry pack folder")
|
|
299
|
+
p_curate.set_defaults(func=cmd_curate)
|
|
300
|
+
|
|
301
|
+
p_info = sub.add_parser("info", help="Print the resolved config")
|
|
302
|
+
p_info.add_argument("--data", required=True)
|
|
303
|
+
p_info.set_defaults(func=cmd_info)
|
|
304
|
+
|
|
305
|
+
p_audit = sub.add_parser("audit", help="Run self-audit (health, knowledge, skills, config)")
|
|
306
|
+
p_audit.add_argument("--data", required=True, help="Path to industry pack folder")
|
|
307
|
+
p_audit.set_defaults(func=cmd_audit)
|
|
308
|
+
|
|
309
|
+
args = parser.parse_args(argv)
|
|
310
|
+
logging.basicConfig(
|
|
311
|
+
level=getattr(logging, args.log_level),
|
|
312
|
+
format="%(asctime)s [%(levelname)s] %(name)s: %(message)s",
|
|
313
|
+
)
|
|
314
|
+
return args.func(args)
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
if __name__ == "__main__":
|
|
318
|
+
sys.exit(main())
|