whatap-python 2.2.0__py3-none-any.whl → 2.2.1__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.
- whatap/__init__.py +180 -33
- whatap/build.py +2 -2
- whatap/conf/configuration.py +72 -1
- whatap/counter/tasks/llm_stat_task.py +10 -0
- whatap/doctor.py +402 -0
- whatap/llm/control/__init__.py +59 -0
- whatap/llm/control/cut.py +519 -0
- whatap/llm/control/guard.py +217 -0
- whatap/llm/control/trajectory.py +524 -0
- whatap/llm/definitions.py +21 -1
- whatap/llm/instrument/_boundary.py +136 -22
- whatap/llm/log_sink_packs/llm_step_status.py +9 -0
- whatap/llm/pricing.py +16 -1
- whatap/llm/providers/anthropic/messages/messages.py +3 -2
- whatap/llm/providers/anthropic/messages/messages_stream.py +3 -2
- whatap/llm/providers/bedrock/__init__.py +92 -0
- whatap/llm/providers/bedrock/_content.py +81 -0
- whatap/llm/providers/bedrock/converse/__init__.py +2 -0
- whatap/llm/providers/bedrock/converse/converse_context.py +155 -0
- whatap/llm/providers/bedrock/converse/converse_extractor.py +196 -0
- whatap/llm/providers/bedrock/event_stream.py +254 -0
- whatap/llm/providers/bedrock/http.py +395 -0
- whatap/llm/providers/bedrock/invoke_model/__init__.py +2 -0
- whatap/llm/providers/bedrock/invoke_model/invoke_model_context.py +126 -0
- whatap/llm/providers/bedrock/invoke_model/invoke_model_extractor.py +763 -0
- whatap/llm/providers/bedrock/runtime.py +291 -0
- whatap/llm/providers/bedrock/step.py +76 -0
- whatap/llm/providers/interceptor.py +10 -1
- whatap/llm/providers/openai/chat/chat.py +71 -14
- whatap/llm/providers/openai/chat/chat_extractor.py +8 -1
- whatap/llm/providers/openai/completions/completions.py +3 -2
- whatap/llm/providers/openai/embeddings/embeddings.py +3 -2
- whatap/llm/providers/openai/responses/responses.py +3 -2
- whatap/llm/providers/stream_accumulator.py +11 -1
- whatap/llm/providers/workflow.py +87 -5
- whatap/llm/stats/traj_actx_stat.py +50 -1
- whatap/net/async_sender.py +22 -9
- whatap/net/param_def.py +6 -0
- whatap/net/udp_session.py +33 -0
- whatap/scripts/__init__.py +89 -0
- whatap/skills/whatap-python-install/AGENTS.md +29 -0
- whatap/skills/whatap-python-install/INSTALL.md +96 -0
- whatap/skills/whatap-python-install/SKILL.md +42 -0
- whatap/skills/whatap-python-install/references/config-keys.md +59 -0
- whatap/skills/whatap-python-install/references/frameworks.md +55 -0
- whatap/skills/whatap-python-install/references/llm-api-usage.md +81 -0
- whatap/skills/whatap-python-install/references/llm-evaluation.md +114 -0
- whatap/skills/whatap-python-install/references/llm-observability.md +123 -0
- whatap/skills/whatap-python-install/references/troubleshooting.md +41 -0
- whatap/trace/mod/httpc/httpx.py +26 -3
- whatap/trace/mod/httpc/httpx2.py +23 -0
- whatap/trace/mod/httpc/requests.py +16 -3
- whatap/trace/mod/httpc/stream_end.py +136 -0
- whatap/trace/mod/httpc/util.py +231 -13
- whatap/trace/trace_error.py +9 -2
- whatap/trace/trace_module_definition.py +15 -0
- whatap/util/native_process.py +458 -0
- {whatap_python-2.2.0.dist-info → whatap_python-2.2.1.dist-info}/METADATA +1 -1
- {whatap_python-2.2.0.dist-info → whatap_python-2.2.1.dist-info}/RECORD +62 -33
- {whatap_python-2.2.0.dist-info → whatap_python-2.2.1.dist-info}/entry_points.txt +2 -0
- {whatap_python-2.2.0.dist-info → whatap_python-2.2.1.dist-info}/WHEEL +0 -0
- {whatap_python-2.2.0.dist-info → whatap_python-2.2.1.dist-info}/top_level.txt +0 -0
whatap/doctor.py
ADDED
|
@@ -0,0 +1,402 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""whatap-doctor -- 에이전트-중립 설치 진단/검증 도구.
|
|
3
|
+
|
|
4
|
+
이 도구는 특정 AI 에이전트에 묶이지 않는다. Claude Code, Codex, 사람이 직접
|
|
5
|
+
치는 터미널, CI 스크립트 어디서든 똑같이 실행되며 표준 라이브러리만 쓴다.
|
|
6
|
+
|
|
7
|
+
호출 방법(둘 다 동일):
|
|
8
|
+
$ whatap-doctor
|
|
9
|
+
$ python -m whatap.doctor
|
|
10
|
+
|
|
11
|
+
하는 일:
|
|
12
|
+
- Python / OS / 프레임워크 / 앱 기동 방식 탐지
|
|
13
|
+
- whatap.conf 존재 및 필수 키(license, host, app_name) 확인
|
|
14
|
+
- 에이전트 프로세스 기동 여부 확인
|
|
15
|
+
- "지금 상태 → 다음에 할 명령" 을 스스로 안내
|
|
16
|
+
|
|
17
|
+
종료코드:
|
|
18
|
+
0 설치가 완료되어 데이터 수집 중(또는 검증 통과)
|
|
19
|
+
1 다음 조치가 필요함(설치/설정/기동)
|
|
20
|
+
"""
|
|
21
|
+
from __future__ import print_function
|
|
22
|
+
|
|
23
|
+
import os
|
|
24
|
+
import platform
|
|
25
|
+
import re
|
|
26
|
+
import subprocess
|
|
27
|
+
import sys
|
|
28
|
+
|
|
29
|
+
try:
|
|
30
|
+
from importlib.util import find_spec as _find_spec
|
|
31
|
+
except Exception: # pragma: no cover - 매우 오래된 파이썬
|
|
32
|
+
_find_spec = None
|
|
33
|
+
|
|
34
|
+
CONF_NAME = "whatap.conf"
|
|
35
|
+
AGENT_NAME = "whatap_python"
|
|
36
|
+
APM_REQUIRED_KEYS = ("license", "whatap.server.host", "app_name")
|
|
37
|
+
KNOWN_FRAMEWORKS = (
|
|
38
|
+
"django", "flask", "fastapi", "starlette", "sanic",
|
|
39
|
+
"tornado", "bottle", "pyramid", "falcon", "aiohttp",
|
|
40
|
+
)
|
|
41
|
+
# 계측이 자동으로 LLM 트랜잭션/스텝을 수집하는 대상 SDK/프레임워크.
|
|
42
|
+
KNOWN_LLM_SDKS = ("openai", "anthropic", "langgraph", "langchain")
|
|
43
|
+
|
|
44
|
+
OK, WARN, FAIL = "ok", "warn", "fail"
|
|
45
|
+
_MARK = {OK: "✓", WARN: "!", FAIL: "✗"} # ✓ ! ✗
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _color(text, status):
|
|
49
|
+
if os.environ.get("NO_COLOR") or not sys.stdout.isatty():
|
|
50
|
+
return text
|
|
51
|
+
code = {OK: "32", WARN: "33", FAIL: "31"}.get(status, "0")
|
|
52
|
+
return "\033[{0}m{1}\033[0m".format(code, text)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class Result(object):
|
|
56
|
+
__slots__ = ("status", "label", "detail", "hint")
|
|
57
|
+
|
|
58
|
+
def __init__(self, status, label, detail="", hint=""):
|
|
59
|
+
self.status = status
|
|
60
|
+
self.label = label
|
|
61
|
+
self.detail = detail
|
|
62
|
+
self.hint = hint
|
|
63
|
+
|
|
64
|
+
def line(self):
|
|
65
|
+
mark = _color("[{0}]".format(_MARK[self.status]), self.status)
|
|
66
|
+
text = "{0} {1}".format(mark, self.label)
|
|
67
|
+
if self.detail:
|
|
68
|
+
text += ": {0}".format(self.detail)
|
|
69
|
+
return text
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
# ---------------------------------------------------------------------------
|
|
73
|
+
# 탐지 (detect)
|
|
74
|
+
# ---------------------------------------------------------------------------
|
|
75
|
+
|
|
76
|
+
def resolve_home():
|
|
77
|
+
"""whatap.conf 가 있어야 할 디렉토리. 규약상 WHATAP_HOME, 없으면 CWD."""
|
|
78
|
+
return os.environ.get("WHATAP_HOME", os.getcwd())
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def read_conf(home):
|
|
82
|
+
"""whatap.conf 를 평문으로 파싱한다(패키지 내부 API에 의존하지 않음)."""
|
|
83
|
+
path = os.path.join(home, CONF_NAME)
|
|
84
|
+
conf = {}
|
|
85
|
+
if not os.path.isfile(path):
|
|
86
|
+
return None, path
|
|
87
|
+
try:
|
|
88
|
+
with open(path) as f:
|
|
89
|
+
for raw in f:
|
|
90
|
+
line = raw.strip()
|
|
91
|
+
if not line or line.startswith("#") or "=" not in line:
|
|
92
|
+
continue
|
|
93
|
+
key, val = line.split("=", 1)
|
|
94
|
+
conf[key.strip()] = val.strip()
|
|
95
|
+
except Exception as e:
|
|
96
|
+
return {"__error__": str(e)}, path
|
|
97
|
+
return conf, path
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def _spec_exists(name):
|
|
101
|
+
if _find_spec is None:
|
|
102
|
+
return False
|
|
103
|
+
try:
|
|
104
|
+
return _find_spec(name) is not None
|
|
105
|
+
except Exception:
|
|
106
|
+
return False
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def detect_python():
|
|
110
|
+
v = sys.version_info
|
|
111
|
+
ver = "{0}.{1}.{2}".format(v[0], v[1], v[2])
|
|
112
|
+
if v[0] < 3 or (v[0] == 3 and v[1] < 8):
|
|
113
|
+
return Result(
|
|
114
|
+
WARN, "Python", ver,
|
|
115
|
+
hint="3.8 미만은 콘솔 스크립트가 깨질 수 있음 → auto-attach 방식 권장 "
|
|
116
|
+
"(INSTALL.md '기동 연결' 참고)")
|
|
117
|
+
return Result(OK, "Python", ver)
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def detect_os():
|
|
121
|
+
return Result(OK, "OS/Arch", "{0} {1}".format(platform.system(), platform.machine()))
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def _read_version(spec):
|
|
125
|
+
"""whatap/build.py 의 version 을 __init__ 실행 없이 텍스트로 읽는다."""
|
|
126
|
+
try:
|
|
127
|
+
locs = list(getattr(spec, "submodule_search_locations", None) or [])
|
|
128
|
+
for base in locs:
|
|
129
|
+
build_py = os.path.join(base, "build.py")
|
|
130
|
+
if os.path.isfile(build_py):
|
|
131
|
+
with open(build_py) as f:
|
|
132
|
+
for line in f:
|
|
133
|
+
if line.strip().startswith("version"):
|
|
134
|
+
return line.split("=", 1)[1].strip().strip("'\"")
|
|
135
|
+
except Exception:
|
|
136
|
+
pass
|
|
137
|
+
return "unknown"
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def detect_package():
|
|
141
|
+
"""whatap 패키지 존재만 확인한다 -- import 하지 않아 부작용/무거운 초기화가 없다."""
|
|
142
|
+
if _find_spec is None:
|
|
143
|
+
return Result(WARN, "whatap-python", "확인 불가(importlib 없음)"), True
|
|
144
|
+
try:
|
|
145
|
+
spec = _find_spec("whatap")
|
|
146
|
+
except Exception:
|
|
147
|
+
spec = None
|
|
148
|
+
if spec is None:
|
|
149
|
+
return Result(
|
|
150
|
+
FAIL, "whatap-python", "미설치",
|
|
151
|
+
hint="pip install whatap-python (LLM 관측이면 'whatap-python[llm]')"), False
|
|
152
|
+
return Result(OK, "whatap-python", "v{0}".format(_read_version(spec))), True
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def detect_framework():
|
|
156
|
+
found = [fw for fw in KNOWN_FRAMEWORKS if _spec_exists(fw)]
|
|
157
|
+
signals = []
|
|
158
|
+
cwd = os.getcwd()
|
|
159
|
+
if os.path.isfile(os.path.join(cwd, "manage.py")):
|
|
160
|
+
signals.append("manage.py(Django)")
|
|
161
|
+
if _spec_exists("gunicorn"):
|
|
162
|
+
signals.append("gunicorn")
|
|
163
|
+
if _spec_exists("uvicorn"):
|
|
164
|
+
signals.append("uvicorn")
|
|
165
|
+
if _spec_exists("celery"):
|
|
166
|
+
signals.append("celery")
|
|
167
|
+
parts = found + signals
|
|
168
|
+
if not parts:
|
|
169
|
+
return Result(WARN, "프레임워크", "자동 판별 실패",
|
|
170
|
+
hint="INSTALL.md 의 프레임워크별 기동 연결법을 직접 골라 적용")
|
|
171
|
+
return Result(OK, "프레임워크", ", ".join(parts))
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def detect_start_method():
|
|
175
|
+
cwd = os.getcwd()
|
|
176
|
+
hits = []
|
|
177
|
+
for name in ("Procfile", "Dockerfile", "docker-compose.yml", "manage.py"):
|
|
178
|
+
if os.path.isfile(os.path.join(cwd, name)):
|
|
179
|
+
hits.append(name)
|
|
180
|
+
if not hits:
|
|
181
|
+
return Result(WARN, "기동 지점", "판별 실패",
|
|
182
|
+
hint="앱 실행 명령을 확인해 whatap-start-agent 프리픽스 또는 "
|
|
183
|
+
"PYTHONPATH+sitecustomize auto-attach 로 연결")
|
|
184
|
+
return Result(OK, "기동 지점", ", ".join(hits))
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
# ---------------------------------------------------------------------------
|
|
188
|
+
# 설정/기동 확인 (verify)
|
|
189
|
+
# ---------------------------------------------------------------------------
|
|
190
|
+
|
|
191
|
+
def check_conf(conf, path):
|
|
192
|
+
"""APM/LLM 어느 모드로 켜졌는지에 따라 필수 키를 다르게 검증한다."""
|
|
193
|
+
if conf is None:
|
|
194
|
+
return Result(FAIL, "whatap.conf", "없음 ({0})".format(path),
|
|
195
|
+
hint="whatap-llm-setting-config --host <LLM 수집서버> --license <LLM 키> "
|
|
196
|
+
"--app_name <앱이름> --app_process_name <프로세스명> "
|
|
197
|
+
"(APM 지표도 함께면 whatap-setting-config)")
|
|
198
|
+
if "__error__" in conf:
|
|
199
|
+
return Result(FAIL, "whatap.conf", "읽기 실패: {0}".format(conf["__error__"]))
|
|
200
|
+
|
|
201
|
+
apm_on = conf.get("apm_enabled", "true") != "false"
|
|
202
|
+
llm_on = conf.get("llm_enabled", "false") == "true"
|
|
203
|
+
|
|
204
|
+
missing = []
|
|
205
|
+
if apm_on:
|
|
206
|
+
missing += [k for k in APM_REQUIRED_KEYS if not conf.get(k)]
|
|
207
|
+
if llm_on:
|
|
208
|
+
if not (conf.get("llm_accesskey") or conf.get("license")):
|
|
209
|
+
missing.append("llm_accesskey|license")
|
|
210
|
+
if not (conf.get("llm.whatap.server.host") or conf.get("whatap.server.host")):
|
|
211
|
+
missing.append("llm.whatap.server.host|whatap.server.host")
|
|
212
|
+
if not conf.get("app_name"):
|
|
213
|
+
missing.append("app_name")
|
|
214
|
+
|
|
215
|
+
seen = set()
|
|
216
|
+
missing = [m for m in missing if not (m in seen or seen.add(m))]
|
|
217
|
+
mode = "+".join([m for m, on in (("APM", apm_on), ("LLM", llm_on)) if on]) or "none"
|
|
218
|
+
|
|
219
|
+
if missing:
|
|
220
|
+
return Result(FAIL, "whatap.conf", "필수 키 누락({0}): {1}".format(mode, ", ".join(missing)),
|
|
221
|
+
hint="whatap-setting-config / whatap-llm-setting-config 로 누락 값을 채우세요")
|
|
222
|
+
return Result(OK, "whatap.conf", "{0} 설정됨".format(mode))
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def _llm_sdk_result(found):
|
|
226
|
+
if not found:
|
|
227
|
+
return Result(OK, "LLM SDK", "감지 안 됨(LLM 관측 대상 아님)")
|
|
228
|
+
return Result(OK, "LLM SDK", ", ".join(found))
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
def check_llm_features(conf, sdk_present):
|
|
232
|
+
"""LLM 관측 on/off 와 Evaluation 상태를 요약한다(비용 경고 포함)."""
|
|
233
|
+
if conf is None or "__error__" in conf:
|
|
234
|
+
return None
|
|
235
|
+
llm_on = conf.get("llm_enabled", "false") == "true"
|
|
236
|
+
if not llm_on:
|
|
237
|
+
if sdk_present:
|
|
238
|
+
return Result(WARN, "LLM 관측", "off (LLM SDK 감지됨)",
|
|
239
|
+
hint="whatap-llm-setting-config 로 켜면 LLM 트랜잭션/스텝이 자동 수집됨 "
|
|
240
|
+
"(references/llm-observability.md)")
|
|
241
|
+
return Result(OK, "LLM 관측", "off")
|
|
242
|
+
|
|
243
|
+
if conf.get("llm_eval_enabled", "false") == "true":
|
|
244
|
+
evs = conf.get("llm_eval_evaluators", "combined_judge, pii_leak, url_scan")
|
|
245
|
+
return Result(WARN, "LLM Evaluation", "on / evaluators={0}".format(evs),
|
|
246
|
+
hint="judge LLM 호출만큼 토큰·비용이 추가 발생. combined_judge 권장, "
|
|
247
|
+
"필요시 llm_eval_sample_rate 로 비율 조절 (references/llm-evaluation.md)")
|
|
248
|
+
return Result(OK, "LLM 관측", "on (Evaluation off)",
|
|
249
|
+
hint="응답 품질 평가가 필요하면 llm_eval_enabled=true (references/llm-evaluation.md)")
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
def check_process():
|
|
253
|
+
if platform.system() == "Windows":
|
|
254
|
+
cmd = ["tasklist"]
|
|
255
|
+
else:
|
|
256
|
+
cmd = ["ps", "-e"]
|
|
257
|
+
try:
|
|
258
|
+
out = subprocess.check_output(cmd, stderr=subprocess.STDOUT)
|
|
259
|
+
out = out.decode("utf-8", "replace")
|
|
260
|
+
except Exception:
|
|
261
|
+
return Result(WARN, "에이전트 프로세스", "확인 불가(ps/tasklist 실행 실패)")
|
|
262
|
+
if AGENT_NAME in out:
|
|
263
|
+
return Result(OK, "에이전트 프로세스", "{0} 실행 중".format(AGENT_NAME))
|
|
264
|
+
return Result(WARN, "에이전트 프로세스", "미기동",
|
|
265
|
+
hint="앱을 whatap-start-agent 로 기동하거나 auto-attach 로 앱과 함께 뜨게 하세요")
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
# ---------------------------------------------------------------------------
|
|
269
|
+
# 계측 인벤토리 (--scan): 고객 소스에 들어간 LLM API 사용/편집을 전수 추적
|
|
270
|
+
# ---------------------------------------------------------------------------
|
|
271
|
+
|
|
272
|
+
_SCAN_SKIP_DIRS = set([
|
|
273
|
+
"node_modules", ".venv", "venv", "env", "__pycache__", "site-packages",
|
|
274
|
+
".tox", "build", "dist", ".mypy_cache", ".pytest_cache", ".eggs",
|
|
275
|
+
])
|
|
276
|
+
|
|
277
|
+
_SCAN_PATTERNS = [
|
|
278
|
+
("import", re.compile(r"(from\s+whatap\.llm[\w.]*\s+import|import\s+whatap\.llm)")),
|
|
279
|
+
("workflow/agent", re.compile(r"@(workflow|agent|agent_name)\b|with\s+(workflow|agent|agent_name)\s*\(")),
|
|
280
|
+
("session/user", re.compile(r"set_(session|user)_id\s*\(|with\s+(session|user)\s*\(")),
|
|
281
|
+
("operation/prompt", re.compile(r"@prompt_meta\b|with\s+prompt_meta_scope\s*\(")),
|
|
282
|
+
("evaluation", re.compile(r"@evaluate_with\b|evaluate_with\s*\(|with\s+evaluation_scope\s*\(")),
|
|
283
|
+
("marker", re.compile(r"#\s*whatap")),
|
|
284
|
+
]
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
def inventory_scan(root):
|
|
288
|
+
"""프로젝트 .py 파일에서 whatap.llm API 사용과 '# whatap' 마커를 찾아 나열한다."""
|
|
289
|
+
hits = []
|
|
290
|
+
for cur, dirs, files in os.walk(root):
|
|
291
|
+
dirs[:] = [d for d in dirs if d not in _SCAN_SKIP_DIRS and not d.startswith(".")]
|
|
292
|
+
for name in files:
|
|
293
|
+
if not name.endswith(".py"):
|
|
294
|
+
continue
|
|
295
|
+
path = os.path.join(cur, name)
|
|
296
|
+
try:
|
|
297
|
+
with open(path) as f:
|
|
298
|
+
lines = f.readlines()
|
|
299
|
+
except Exception:
|
|
300
|
+
continue
|
|
301
|
+
for i, line in enumerate(lines, 1):
|
|
302
|
+
for cat, rx in _SCAN_PATTERNS:
|
|
303
|
+
if rx.search(line):
|
|
304
|
+
hits.append((os.path.relpath(path, root), i, cat, line.strip()))
|
|
305
|
+
break
|
|
306
|
+
return hits
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
def _scan_main(root):
|
|
310
|
+
hits = inventory_scan(root)
|
|
311
|
+
if not hits:
|
|
312
|
+
print("WhaTap LLM 계측 인벤토리 — 소스에서 whatap.llm API 사용을 찾지 못함.")
|
|
313
|
+
print("(자동 계측/conf 기반이면 코드 편집이 없는 것이 정상입니다.)")
|
|
314
|
+
return 0
|
|
315
|
+
print("WhaTap LLM 계측 인벤토리 (root={0}, {1}건)".format(root, len(hits)))
|
|
316
|
+
print("-" * 60)
|
|
317
|
+
cur_file = None
|
|
318
|
+
for path, lineno, cat, text in hits:
|
|
319
|
+
if path != cur_file:
|
|
320
|
+
print(" {0}".format(path))
|
|
321
|
+
cur_file = path
|
|
322
|
+
print(" {0}: [{1}] {2}".format(lineno, cat, text))
|
|
323
|
+
print("-" * 60)
|
|
324
|
+
print("위가 고객 소스에 들어간 LLM 계측의 전부입니다. '# whatap' 마커로도 grep 가능.")
|
|
325
|
+
return 0
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
# ---------------------------------------------------------------------------
|
|
329
|
+
# main
|
|
330
|
+
# ---------------------------------------------------------------------------
|
|
331
|
+
|
|
332
|
+
def run_checks():
|
|
333
|
+
home = resolve_home()
|
|
334
|
+
pkg_result, installed = detect_package()
|
|
335
|
+
llm_sdks = [s for s in KNOWN_LLM_SDKS if _spec_exists(s)]
|
|
336
|
+
checks = [
|
|
337
|
+
detect_python(),
|
|
338
|
+
detect_os(),
|
|
339
|
+
pkg_result,
|
|
340
|
+
detect_framework(),
|
|
341
|
+
detect_start_method(),
|
|
342
|
+
_llm_sdk_result(llm_sdks),
|
|
343
|
+
]
|
|
344
|
+
if installed:
|
|
345
|
+
conf, path = read_conf(home)
|
|
346
|
+
checks.append(check_conf(conf, path))
|
|
347
|
+
checks.append(check_process())
|
|
348
|
+
feat = check_llm_features(conf, bool(llm_sdks))
|
|
349
|
+
if feat is not None:
|
|
350
|
+
checks.append(feat)
|
|
351
|
+
return checks
|
|
352
|
+
|
|
353
|
+
|
|
354
|
+
def _next_action(checks):
|
|
355
|
+
for c in checks:
|
|
356
|
+
if c.status == FAIL and c.hint:
|
|
357
|
+
return "다음: {0}".format(c.hint)
|
|
358
|
+
warn = [c for c in checks if c.status == WARN and c.hint]
|
|
359
|
+
if warn:
|
|
360
|
+
return "다음: {0}".format(warn[0].hint)
|
|
361
|
+
return None
|
|
362
|
+
|
|
363
|
+
|
|
364
|
+
def main(argv=None):
|
|
365
|
+
argv = list(sys.argv[1:] if argv is None else argv)
|
|
366
|
+
as_json = "--json" in argv
|
|
367
|
+
|
|
368
|
+
if "--scan" in argv:
|
|
369
|
+
return _scan_main(os.getcwd())
|
|
370
|
+
|
|
371
|
+
checks = run_checks()
|
|
372
|
+
|
|
373
|
+
if as_json:
|
|
374
|
+
import json
|
|
375
|
+
payload = {
|
|
376
|
+
"home": resolve_home(),
|
|
377
|
+
"checks": [
|
|
378
|
+
{"status": c.status, "label": c.label, "detail": c.detail, "hint": c.hint}
|
|
379
|
+
for c in checks
|
|
380
|
+
],
|
|
381
|
+
}
|
|
382
|
+
print(json.dumps(payload, ensure_ascii=False, indent=2))
|
|
383
|
+
else:
|
|
384
|
+
print("WhaTap Python 에이전트 진단 (WHATAP_HOME={0})".format(resolve_home()))
|
|
385
|
+
print("-" * 60)
|
|
386
|
+
for c in checks:
|
|
387
|
+
print(" " + c.line())
|
|
388
|
+
if c.hint and c.status != OK:
|
|
389
|
+
print(" → {0}".format(c.hint))
|
|
390
|
+
print("-" * 60)
|
|
391
|
+
nxt = _next_action(checks)
|
|
392
|
+
if nxt:
|
|
393
|
+
print(nxt)
|
|
394
|
+
else:
|
|
395
|
+
print("모든 항목 통과 — 데이터 수집 준비 완료.")
|
|
396
|
+
|
|
397
|
+
failed = any(c.status == FAIL for c in checks)
|
|
398
|
+
return 1 if failed else 0
|
|
399
|
+
|
|
400
|
+
|
|
401
|
+
if __name__ == "__main__":
|
|
402
|
+
sys.exit(main())
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""LLM 트래젝토리 제어(ParamPack 905) — 실행 중 LLM 호출 끊기.
|
|
3
|
+
|
|
4
|
+
계층은 셋이다.
|
|
5
|
+
|
|
6
|
+
trajectory.py 가족(mtid) 상태와 905 실행부 — 무엇을 끊을지, 어떤 status 를 답할지
|
|
7
|
+
cut.py 끊는 수단 — sync 소켓 shutdown / async 태스크 cancel
|
|
8
|
+
guard.py 프로바이더 호출 지점 seam — 인터셉터가 SDK 를 부르는 한 줄
|
|
9
|
+
|
|
10
|
+
바깥에서 쓰는 것은 여기 re-export 된 이름들뿐이다:
|
|
11
|
+
|
|
12
|
+
stop_trajectory(mtid) 905 진입점 (udp_session)
|
|
13
|
+
guard_sync/guard_async 프로바이더 인터셉터
|
|
14
|
+
block_reason 집행 지점 공용 판정자 — 막을 이유(문구) 또는 None
|
|
15
|
+
enforce_blocked_users 차단 사용자의 진행 중 호출 끊기 (LlmStatTask 1초 틱)
|
|
16
|
+
"""
|
|
17
|
+
from whatap.llm.control.trajectory import (
|
|
18
|
+
STATUS_OK,
|
|
19
|
+
STATUS_IN_PROGRESS,
|
|
20
|
+
STATUS_UNSUPPORTED,
|
|
21
|
+
STATUS_NO_TARGET,
|
|
22
|
+
STATUS_ERROR,
|
|
23
|
+
stop_trajectory,
|
|
24
|
+
block_reason,
|
|
25
|
+
has_any_block,
|
|
26
|
+
stop_exception,
|
|
27
|
+
emit_stop_step,
|
|
28
|
+
should_terminate,
|
|
29
|
+
is_stopped_ctx,
|
|
30
|
+
is_blocked_user,
|
|
31
|
+
blocked_users,
|
|
32
|
+
enforce_blocked_users,
|
|
33
|
+
is_enabled,
|
|
34
|
+
stop_message,
|
|
35
|
+
)
|
|
36
|
+
from whatap.llm.control.guard import guard_sync, guard_async, stopped_error
|
|
37
|
+
|
|
38
|
+
__all__ = [
|
|
39
|
+
'STATUS_OK',
|
|
40
|
+
'STATUS_IN_PROGRESS',
|
|
41
|
+
'STATUS_UNSUPPORTED',
|
|
42
|
+
'STATUS_NO_TARGET',
|
|
43
|
+
'STATUS_ERROR',
|
|
44
|
+
'stop_trajectory',
|
|
45
|
+
'block_reason',
|
|
46
|
+
'has_any_block',
|
|
47
|
+
'stop_exception',
|
|
48
|
+
'emit_stop_step',
|
|
49
|
+
'should_terminate',
|
|
50
|
+
'is_stopped_ctx',
|
|
51
|
+
'is_blocked_user',
|
|
52
|
+
'blocked_users',
|
|
53
|
+
'enforce_blocked_users',
|
|
54
|
+
'is_enabled',
|
|
55
|
+
'stop_message',
|
|
56
|
+
'guard_sync',
|
|
57
|
+
'guard_async',
|
|
58
|
+
'stopped_error',
|
|
59
|
+
]
|