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.
Files changed (49) hide show
  1. deployments/cli.py +318 -0
  2. deployments/server.py +491 -0
  3. deployments/start.py +22 -0
  4. engine/__init__.py +57 -0
  5. engine/__main__.py +5 -0
  6. engine/audit.py +809 -0
  7. engine/bot.py +632 -0
  8. engine/config.py +255 -0
  9. engine/curator.py +225 -0
  10. engine/extension_base.py +48 -0
  11. engine/extensions/__init__.py +28 -0
  12. engine/extensions/adaptive.py +272 -0
  13. engine/extensions/conversational_memory.py +285 -0
  14. engine/extensions/live.py +420 -0
  15. engine/extensions/multiagent.py +231 -0
  16. engine/extensions/multimodal.py +218 -0
  17. engine/extensions/predictive.py +187 -0
  18. engine/extensions/rlhf.py +308 -0
  19. engine/extensions/voice.py +312 -0
  20. engine/llm.py +236 -0
  21. engine/memory.py +253 -0
  22. engine/modules/__init__.py +6 -0
  23. engine/modules/cultural.py +78 -0
  24. engine/modules/empathy.py +56 -0
  25. engine/modules/ethics.py +96 -0
  26. engine/modules/intent.py +85 -0
  27. engine/modules/wcce.py +112 -0
  28. engine/modules/xai.py +55 -0
  29. engine/pack_context.py +110 -0
  30. engine/personality.py +102 -0
  31. engine/provider_runtime.py +163 -0
  32. engine/retrieval.py +347 -0
  33. engine/session.py +190 -0
  34. engine/skills.py +559 -0
  35. engine/templates.py +189 -0
  36. engine/tools.py +227 -0
  37. engine/utils.py +120 -0
  38. engine/verification.py +204 -0
  39. kstudiochat-1.0.0.dist-info/METADATA +243 -0
  40. kstudiochat-1.0.0.dist-info/RECORD +49 -0
  41. kstudiochat-1.0.0.dist-info/WHEEL +5 -0
  42. kstudiochat-1.0.0.dist-info/entry_points.txt +2 -0
  43. kstudiochat-1.0.0.dist-info/top_level.txt +3 -0
  44. widgets/integrations/__init__.py +26 -0
  45. widgets/integrations/adapters.py +933 -0
  46. widgets/integrations/base.py +112 -0
  47. widgets/integrations/slack.py +144 -0
  48. widgets/integrations/telegram.py +149 -0
  49. 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())