whatap-python 2.1.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 (227) hide show
  1. whatap/LICENSE +0 -0
  2. whatap/README.rst +49 -0
  3. whatap/__init__.py +923 -0
  4. whatap/__main__.py +4 -0
  5. whatap/agent/darwin/amd64/whatap_python +0 -0
  6. whatap/agent/darwin/arm64/whatap_python +0 -0
  7. whatap/agent/linux/amd64/whatap_python +0 -0
  8. whatap/agent/linux/arm64/whatap_python +0 -0
  9. whatap/agent/windows/whatap_python.exe +0 -0
  10. whatap/bootstrap/__init__.py +0 -0
  11. whatap/bootstrap/sitecustomize.py +19 -0
  12. whatap/build.py +4 -0
  13. whatap/conf/__init__.py +0 -0
  14. whatap/conf/configuration.py +280 -0
  15. whatap/conf/configure.py +105 -0
  16. whatap/conf/license.py +49 -0
  17. whatap/control/__init__.py +0 -0
  18. whatap/counter/__init__.py +14 -0
  19. whatap/counter/counter_manager.py +45 -0
  20. whatap/counter/tasks/__init__.py +3 -0
  21. whatap/counter/tasks/base_task.py +26 -0
  22. whatap/counter/tasks/llm_evaluator_task.py +501 -0
  23. whatap/counter/tasks/llm_log_sink_task.py +309 -0
  24. whatap/counter/tasks/llm_stat_task.py +78 -0
  25. whatap/counter/tasks/openfiledescriptor.py +67 -0
  26. whatap/io/__init__.py +1 -0
  27. whatap/io/data_inputx.py +161 -0
  28. whatap/io/data_outputx.py +262 -0
  29. whatap/llm/__init__.py +17 -0
  30. whatap/llm/definitions.py +43 -0
  31. whatap/llm/evaluators/__init__.py +136 -0
  32. whatap/llm/evaluators/base.py +114 -0
  33. whatap/llm/evaluators/builtins/__init__.py +91 -0
  34. whatap/llm/evaluators/builtins/answer_relevance.py +46 -0
  35. whatap/llm/evaluators/builtins/combined_judge.py +271 -0
  36. whatap/llm/evaluators/builtins/factuality.py +71 -0
  37. whatap/llm/evaluators/builtins/hallucination.py +97 -0
  38. whatap/llm/evaluators/builtins/llm_judge.py +516 -0
  39. whatap/llm/evaluators/builtins/pii_leak.py +214 -0
  40. whatap/llm/evaluators/builtins/prompt_injection.py +71 -0
  41. whatap/llm/evaluators/builtins/toxicity.py +53 -0
  42. whatap/llm/evaluators/builtins/url_scan.py +194 -0
  43. whatap/llm/evaluators/registry.py +192 -0
  44. whatap/llm/evaluators/sampler.py +83 -0
  45. whatap/llm/evaluators/scope.py +334 -0
  46. whatap/llm/features.py +66 -0
  47. whatap/llm/log_sink_packs/__init__.py +9 -0
  48. whatap/llm/log_sink_packs/llm_input_message.py +16 -0
  49. whatap/llm/log_sink_packs/llm_log_sink_pack.py +72 -0
  50. whatap/llm/log_sink_packs/llm_output_message.py +19 -0
  51. whatap/llm/log_sink_packs/llm_step_eval_status.py +94 -0
  52. whatap/llm/log_sink_packs/llm_step_status.py +118 -0
  53. whatap/llm/log_sink_packs/llm_system_message.py +16 -0
  54. whatap/llm/log_sink_packs/llm_tool_calls.py +44 -0
  55. whatap/llm/log_sink_packs/llm_tool_results.py +16 -0
  56. whatap/llm/log_sink_packs/llm_tx_status.py +108 -0
  57. whatap/llm/pricing.py +236 -0
  58. whatap/llm/prompt_meta.py +288 -0
  59. whatap/llm/providers/__init__.py +0 -0
  60. whatap/llm/providers/anthropic/__init__.py +37 -0
  61. whatap/llm/providers/anthropic/messages/__init__.py +0 -0
  62. whatap/llm/providers/anthropic/messages/messages.py +70 -0
  63. whatap/llm/providers/anthropic/messages/messages_context.py +76 -0
  64. whatap/llm/providers/anthropic/messages/messages_extractor.py +126 -0
  65. whatap/llm/providers/interceptor.py +182 -0
  66. whatap/llm/providers/openai/__init__.py +133 -0
  67. whatap/llm/providers/openai/chat/__init__.py +0 -0
  68. whatap/llm/providers/openai/chat/chat.py +82 -0
  69. whatap/llm/providers/openai/chat/chat_context.py +78 -0
  70. whatap/llm/providers/openai/chat/chat_extractor.py +127 -0
  71. whatap/llm/providers/openai/completions/__init__.py +0 -0
  72. whatap/llm/providers/openai/completions/completions.py +70 -0
  73. whatap/llm/providers/openai/completions/completions_context.py +31 -0
  74. whatap/llm/providers/openai/completions/completions_extractor.py +61 -0
  75. whatap/llm/providers/openai/content_parser.py +41 -0
  76. whatap/llm/providers/openai/embeddings/__init__.py +0 -0
  77. whatap/llm/providers/openai/embeddings/embeddings.py +59 -0
  78. whatap/llm/providers/openai/embeddings/embeddings_context.py +25 -0
  79. whatap/llm/providers/openai/embeddings/embeddings_extractor.py +26 -0
  80. whatap/llm/providers/openai/responses/__init__.py +0 -0
  81. whatap/llm/providers/openai/responses/responses.py +70 -0
  82. whatap/llm/providers/openai/responses/responses_context.py +88 -0
  83. whatap/llm/providers/openai/responses/responses_extractor.py +126 -0
  84. whatap/llm/providers/stream_accumulator.py +73 -0
  85. whatap/llm/stats/__init__.py +35 -0
  86. whatap/llm/stats/active_stat.py +86 -0
  87. whatap/llm/stats/answer_relevance_eval_stat.py +10 -0
  88. whatap/llm/stats/api_status_stat.py +35 -0
  89. whatap/llm/stats/base_stat.py +107 -0
  90. whatap/llm/stats/combined_judge_eval_stat.py +11 -0
  91. whatap/llm/stats/error_stat.py +59 -0
  92. whatap/llm/stats/eval_stat.py +225 -0
  93. whatap/llm/stats/factuality_eval_stat.py +10 -0
  94. whatap/llm/stats/feature_stat.py +104 -0
  95. whatap/llm/stats/finish_stat.py +105 -0
  96. whatap/llm/stats/hallucination_eval_stat.py +10 -0
  97. whatap/llm/stats/meter.py +18 -0
  98. whatap/llm/stats/perf_stat.py +117 -0
  99. whatap/llm/stats/pii_leak_eval_stat.py +12 -0
  100. whatap/llm/stats/prompt_injection_eval_stat.py +10 -0
  101. whatap/llm/stats/token_usage_stat.py +133 -0
  102. whatap/llm/stats/toxicity_eval_stat.py +10 -0
  103. whatap/llm/stats/url_scan_eval_stat.py +12 -0
  104. whatap/net/__init__.py +0 -0
  105. whatap/net/async_sender.py +107 -0
  106. whatap/net/packet_enum.py +44 -0
  107. whatap/net/packet_type_enum.py +31 -0
  108. whatap/net/param_def.py +69 -0
  109. whatap/net/stackhelper.py +87 -0
  110. whatap/net/udp_session.py +394 -0
  111. whatap/net/udp_thread.py +54 -0
  112. whatap/pack/__init__.py +0 -0
  113. whatap/pack/logSinkPack.py +77 -0
  114. whatap/pack/pack.py +34 -0
  115. whatap/pack/pack_enum.py +41 -0
  116. whatap/pack/tagCountPack.py +61 -0
  117. whatap/scripts/__init__.py +208 -0
  118. whatap/trace/__init__.py +12 -0
  119. whatap/trace/mod/__init__.py +0 -0
  120. whatap/trace/mod/amqp/__init__.py +0 -0
  121. whatap/trace/mod/amqp/kombu.py +122 -0
  122. whatap/trace/mod/amqp/pika.py +62 -0
  123. whatap/trace/mod/application/__init__.py +0 -0
  124. whatap/trace/mod/application/bottle.py +34 -0
  125. whatap/trace/mod/application/celery.py +81 -0
  126. whatap/trace/mod/application/cherrypy.py +30 -0
  127. whatap/trace/mod/application/django.py +287 -0
  128. whatap/trace/mod/application/django_asgi.py +266 -0
  129. whatap/trace/mod/application/django_py3.py +251 -0
  130. whatap/trace/mod/application/fastapi/__init__.py +31 -0
  131. whatap/trace/mod/application/fastapi/endpoint.py +73 -0
  132. whatap/trace/mod/application/fastapi/exception_log.py +63 -0
  133. whatap/trace/mod/application/fastapi/instrumentation.py +204 -0
  134. whatap/trace/mod/application/fastapi/scope.py +115 -0
  135. whatap/trace/mod/application/fastapi/transaction.py +67 -0
  136. whatap/trace/mod/application/flask.py +52 -0
  137. whatap/trace/mod/application/frappe.py +224 -0
  138. whatap/trace/mod/application/graphql.py +170 -0
  139. whatap/trace/mod/application/nameko.py +39 -0
  140. whatap/trace/mod/application/odoo.py +63 -0
  141. whatap/trace/mod/application/starlette.py +126 -0
  142. whatap/trace/mod/application/tornado.py +163 -0
  143. whatap/trace/mod/application/wsgi.py +195 -0
  144. whatap/trace/mod/database/__init__.py +0 -0
  145. whatap/trace/mod/database/cxoracle.py +49 -0
  146. whatap/trace/mod/database/mongo.py +169 -0
  147. whatap/trace/mod/database/mysql.py +80 -0
  148. whatap/trace/mod/database/neo4j.py +90 -0
  149. whatap/trace/mod/database/psycopg2.py +45 -0
  150. whatap/trace/mod/database/psycopg3.py +359 -0
  151. whatap/trace/mod/database/redis.py +122 -0
  152. whatap/trace/mod/database/sqlalchemy.py +213 -0
  153. whatap/trace/mod/database/sqlite3.py +130 -0
  154. whatap/trace/mod/database/util.py +630 -0
  155. whatap/trace/mod/email/__init__.py +0 -0
  156. whatap/trace/mod/email/smtp.py +78 -0
  157. whatap/trace/mod/httpc/__init__.py +0 -0
  158. whatap/trace/mod/httpc/django.py +31 -0
  159. whatap/trace/mod/httpc/httplib.py +70 -0
  160. whatap/trace/mod/httpc/httpx.py +62 -0
  161. whatap/trace/mod/httpc/requests.py +20 -0
  162. whatap/trace/mod/httpc/urllib3.py +27 -0
  163. whatap/trace/mod/httpc/util.py +388 -0
  164. whatap/trace/mod/logging.py +161 -0
  165. whatap/trace/mod/plugin.py +84 -0
  166. whatap/trace/mod/standalone/__init__.py +0 -0
  167. whatap/trace/mod/standalone/multiple.py +293 -0
  168. whatap/trace/mod/standalone/single.py +135 -0
  169. whatap/trace/simple_trace_context.py +18 -0
  170. whatap/trace/trace_context.py +212 -0
  171. whatap/trace/trace_context_manager.py +244 -0
  172. whatap/trace/trace_error.py +84 -0
  173. whatap/trace/trace_handler.py +89 -0
  174. whatap/trace/trace_import.py +91 -0
  175. whatap/trace/trace_module_definition.py +156 -0
  176. whatap/util/__init__.py +0 -0
  177. whatap/util/bit_util.py +49 -0
  178. whatap/util/cardinality/__init__.py +0 -0
  179. whatap/util/cardinality/hyperloglog.py +84 -0
  180. whatap/util/cardinality/murmurhash.py +20 -0
  181. whatap/util/cardinality/registerset.py +60 -0
  182. whatap/util/compare_util.py +19 -0
  183. whatap/util/date_util.py +55 -0
  184. whatap/util/debug_util.py +73 -0
  185. whatap/util/escape_literal_sql.py +233 -0
  186. whatap/util/frame_util.py +20 -0
  187. whatap/util/hash_util.py +103 -0
  188. whatap/util/hexa32.py +66 -0
  189. whatap/util/int_set.py +199 -0
  190. whatap/util/ip_util.py +63 -0
  191. whatap/util/keygen.py +11 -0
  192. whatap/util/linked_list.py +113 -0
  193. whatap/util/linked_map.py +359 -0
  194. whatap/util/metering_util.py +103 -0
  195. whatap/util/request_double_queue.py +68 -0
  196. whatap/util/request_queue.py +60 -0
  197. whatap/util/string_util.py +20 -0
  198. whatap/util/throttle_util.py +99 -0
  199. whatap/util/userid_util.py +134 -0
  200. whatap/value/__init__.py +1 -0
  201. whatap/value/blob_value.py +38 -0
  202. whatap/value/boolean_value.py +33 -0
  203. whatap/value/decimal_value.py +36 -0
  204. whatap/value/double_summary.py +86 -0
  205. whatap/value/double_value.py +33 -0
  206. whatap/value/float_array.py +42 -0
  207. whatap/value/float_value.py +34 -0
  208. whatap/value/int_array.py +42 -0
  209. whatap/value/ip4_value.py +50 -0
  210. whatap/value/list_value.py +105 -0
  211. whatap/value/long_array.py +44 -0
  212. whatap/value/long_summary.py +83 -0
  213. whatap/value/map_value.py +154 -0
  214. whatap/value/null_value.py +21 -0
  215. whatap/value/number_value.py +33 -0
  216. whatap/value/summary_value.py +39 -0
  217. whatap/value/text_array.py +58 -0
  218. whatap/value/text_hash_value.py +37 -0
  219. whatap/value/text_value.py +43 -0
  220. whatap/value/value.py +26 -0
  221. whatap/value/value_enum.py +80 -0
  222. whatap/whatap.conf +14 -0
  223. whatap_python-2.1.0.dist-info/METADATA +87 -0
  224. whatap_python-2.1.0.dist-info/RECORD +227 -0
  225. whatap_python-2.1.0.dist-info/WHEEL +5 -0
  226. whatap_python-2.1.0.dist-info/entry_points.txt +6 -0
  227. whatap_python-2.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,309 @@
1
+ import queue
2
+ import threading
3
+
4
+ from whatap import DateUtil
5
+ from whatap import logging
6
+ from whatap.pack import logSinkPack
7
+ from whatap.conf.configure import Configure as conf
8
+ from whatap.trace.trace_context_manager import TraceContextManager
9
+
10
+ import whatap.io as whatapio
11
+ import whatap.net.async_sender as async_sender
12
+
13
+ from whatap.llm.log_sink_packs.llm_log_sink_pack import LlmLogSinkPack
14
+ from whatap.llm.log_sink_packs.llm_step_status import LlmStepStatus
15
+ from whatap.llm.log_sink_packs.llm_system_message import LlmSystemMessage
16
+ from whatap.llm.log_sink_packs.llm_input_message import LlmInputMessage
17
+ from whatap.llm.log_sink_packs.llm_output_message import LlmOutputMessage
18
+ from whatap.llm.log_sink_packs.llm_tool_calls import LlmToolCalls
19
+ from whatap.llm.log_sink_packs.llm_tool_results import LlmToolResults
20
+ from whatap.llm.log_sink_packs.llm_tx_status import LlmTxStatus
21
+
22
+ _MAX_CONTENT_BYTES = 20000
23
+
24
+
25
+ class LlmLogSinkTask(object):
26
+ """LLM logsink pack 송출 파이프라인.
27
+
28
+ LLM 호출 결과 pack 을 받아 즉시 송출. 평가 (LLM-judge 등) 는 별도 파이프라인
29
+ (``LlmEvaluatorTask``) 에서 비동기로 처리되며, 평가 결과는 ``LlmStepEvalStatus``
30
+ pack (``llm_log_type=llm_step_eval_status``) 으로 송출된다 — 원본 LlmStepStatus 와
31
+ 동일한 fields/tags + 평가 점수 5 필드. (txid, step_id) 결합 키로 백엔드 사후 결합.
32
+
33
+ 이 단순한 모델 덕분에 hold/timeout/callback 등의 복잡도가 모두 제거됨.
34
+ """
35
+
36
+ _instance = None
37
+ _lock = threading.Lock()
38
+
39
+ def __init__(self):
40
+ self._q = queue.Queue(4096)
41
+ self._started = False
42
+
43
+ @classmethod
44
+ def get_instance(cls):
45
+ if cls._instance is None:
46
+ with cls._lock:
47
+ if cls._instance is None:
48
+ cls._instance = cls()
49
+ return cls._instance
50
+
51
+ def dispatch(self, pack):
52
+ """LLM 호출 결과 pack 을 즉시 송출 + 평가 큐 트리거.
53
+
54
+ 평가 워커 안에서 호출된 judge LLM call 인 경우 (intercept 가 fire 한 케이스):
55
+ - operation_type='whatap_evaluation' / prompt_version='v1' 고정
56
+ - parent (user) step 의 (txid, step_id, index) 그대로 propagate
57
+ → dashboard 에서 같은 transaction 으로 묶여 보임
58
+ - eval enqueue 는 재귀 가드가 자동 차단
59
+ """
60
+ if not isinstance(pack, LlmStepStatus):
61
+ return
62
+
63
+ ctx = getattr(pack, '_trace_ctx', None) or TraceContextManager.getLocalContext()
64
+ pack.set_context(ctx)
65
+
66
+ if not pack.operation_type and ctx:
67
+ pack.operation_type = getattr(ctx, '_llm_operation_type', '') or 'unknown'
68
+
69
+ try:
70
+ pack.calculate_cost()
71
+ except Exception as e:
72
+ logging.warning('[LLM] calculate_cost failed: model=%s, input_tokens=%s, output_tokens=%s, cached_tokens=%s, error=%s'
73
+ % (pack.model, pack.input_tokens, pack.output_tokens, pack.cached_tokens, e),
74
+ extra={'id': 'LLM024'})
75
+
76
+ # ── judge LLM call (eval 워커 안에서 발생) — 고정 라벨 + parent step ids ──
77
+ try:
78
+ from whatap.counter.tasks.llm_evaluator_task import (
79
+ _is_in_evaluator_worker, _get_eval_worker_state,
80
+ )
81
+ if _is_in_evaluator_worker():
82
+ state = _get_eval_worker_state() or {}
83
+ pack.operation_type = 'whatap_evaluation'
84
+ pack.prompt_version = 'v1'
85
+ if state.get('parent_txid'):
86
+ pack.txid = state['parent_txid']
87
+ if state.get('parent_step_id'):
88
+ pack.step_id = state['parent_step_id']
89
+ if state.get('parent_index') is not None:
90
+ pack.index = state['parent_index']
91
+ except Exception:
92
+ pass
93
+
94
+ # tx_summary 누적
95
+ self._accumulate_tx_summary(ctx, pack)
96
+
97
+ if ctx and not pack.success and pack.error_type:
98
+ ctx._llm_last_error_type = pack.error_type
99
+ ctx._llm_last_error_model = pack.model or 'unknown'
100
+ ctx._llm_last_error_provider = pack.provider or ''
101
+ ctx._llm_last_error_op_type = pack.operation_type or 'unknown'
102
+ ctx._llm_last_error_url = pack.url or ''
103
+ ctx._llm_last_error_prompt_version = (
104
+ getattr(pack, 'prompt_version', 'v1') or 'v1')
105
+
106
+ # pack 즉시 송출 — eval 결과 기다리지 않음
107
+ self._enqueue_for_send(pack)
108
+
109
+ # 평가 큐 트리거 — worker 가 pack 을 복제해 LlmStepEvalStatus 로 만들어 점수 채워 송출
110
+ # judge 호출 인 경우 enqueue_evaluation 의 재귀 가드가 차단
111
+ try:
112
+ from whatap.counter.tasks.llm_evaluator_task import enqueue_evaluation
113
+ enqueue_evaluation(pack)
114
+ except Exception as e:
115
+ logging.warning('[LLM] eval enqueue failed: %s' % e, extra={'id': 'LLM034'})
116
+
117
+ def _enqueue_for_send(self, pack):
118
+ """완성된 pack 을 송출 큐에 적재. queue full 이면 drop + LLM025."""
119
+ self._ensure_started()
120
+ if self._q.full():
121
+ logging.warning('[LLM] send queue full, pack dropped: model=%s' % pack.model,
122
+ extra={'id': 'LLM025'})
123
+ return
124
+ self._q.put(pack)
125
+
126
+ def send_tx_status(self, ctx):
127
+ if not ctx:
128
+ return
129
+ if not getattr(conf, 'llm_enabled', False):
130
+ return
131
+ tx = getattr(ctx, '_llm_tx_status', None)
132
+ if not tx or tx.call_count == 0:
133
+ return
134
+
135
+ try:
136
+ self._send_log_sink(tx)
137
+ # 트랜잭션 경계에서 LLM 버퍼에 남아있는 데이터 flush (큐를 거쳐 순서 보장)
138
+ async_sender.flush_llm_relaypack()
139
+ except Exception as e:
140
+ logging.warning('[LLM] send_llm_tx_status failed: %s' % e, extra={'id': 'LLM022'})
141
+
142
+ # ── internal ──
143
+
144
+ def _ensure_started(self):
145
+ if self._started:
146
+ return
147
+ with self._lock:
148
+ if self._started:
149
+ return
150
+ self._started = True
151
+ t = threading.Thread(target=self._run, daemon=True)
152
+ t.start()
153
+
154
+ def _run(self):
155
+ while True:
156
+ pack = self._q.get()
157
+ if not pack:
158
+ continue
159
+ try:
160
+ self._process_pack(pack)
161
+ except Exception as e:
162
+ logging.warning('[LLM] process failed: %s' % e, extra={'id': 'LLM003'})
163
+
164
+ def _process_pack(self, pack):
165
+ # llm_step_status
166
+ self._send_log_sink(pack)
167
+
168
+ # system_message
169
+ for text in getattr(pack, 'system_texts', None) or []:
170
+ self._send_log_sink(LlmSystemMessage.from_pack(pack, system_text=text))
171
+
172
+ # input_message
173
+ prompt_text = getattr(pack, 'prompt_text', '') or ''
174
+ self._send_log_sink(LlmInputMessage.from_pack(pack, prompt_text=prompt_text))
175
+
176
+ # output_message
177
+ completion_text = getattr(pack, 'completion_text', '') or ''
178
+ reasoning_text = getattr(pack, 'reasoning_text', '') or ''
179
+ self._send_log_sink(LlmOutputMessage.from_pack(pack,
180
+ completion_text=completion_text, reasoning_text=reasoning_text))
181
+
182
+ # tool
183
+ tool_calls_text = getattr(pack, 'tool_calls_text', '') or ''
184
+ if tool_calls_text:
185
+ self._send_log_sink(LlmToolCalls.from_pack(pack, tool_calls_text=tool_calls_text))
186
+
187
+ # tool_result
188
+ tool_results_text = getattr(pack, 'tool_results_text', '') or ''
189
+ if tool_results_text:
190
+ self._send_log_sink(LlmToolResults.from_pack(pack, tool_results_text=tool_results_text))
191
+
192
+ def _accumulate_tx_summary(self, ctx, pack):
193
+ if not ctx:
194
+ return
195
+ tx = getattr(ctx, '_llm_tx_status', None)
196
+ if tx is None:
197
+ tx = LlmTxStatus()
198
+ tx.txid = str(ctx.id)
199
+ ctx._llm_tx_status = tx
200
+ tx.accumulate(pack)
201
+
202
+ def _send_log_sink(self, data):
203
+ tags = data.tags()
204
+ fields = data.fields()
205
+ content = data.content()
206
+ if content:
207
+ self._send_chunked(tags, fields, content, data.index)
208
+ else:
209
+ self._send_pack(tags, fields, '', data.index)
210
+
211
+ def _send_pack(self, tags, fields, content, index):
212
+ try:
213
+ tags = {k: v for k, v in tags.items() if v is not None}
214
+ fields = {k: v for k, v in fields.items() if v is not None}
215
+ fields['index'] = index
216
+ if content:
217
+ encoded = content.encode('utf-8', errors='replace')
218
+ if len(encoded) > _MAX_CONTENT_BYTES:
219
+ content = encoded[:_MAX_CONTENT_BYTES].decode('utf-8', errors='replace')
220
+ p = logSinkPack.getLogSinkPack(
221
+ t=DateUtil.now(),
222
+ category=LlmLogSinkPack.CATEGORY,
223
+ tags=tags,
224
+ fields={},
225
+ line=DateUtil.now(),
226
+ content=content
227
+ )
228
+ for k, v in fields.items():
229
+ if isinstance(v, (int, float)):
230
+ p.fields.putAuto(k, v)
231
+ else:
232
+ p.fields.putString(k, str(v))
233
+
234
+ p.pcode = conf.PCODE
235
+ bout = whatapio.DataOutputX()
236
+ bout.writePack(p, None)
237
+ packbytes = bout.toByteArray()
238
+ async_sender.send_llm_relaypack(packbytes)
239
+ except Exception as e:
240
+ logging.warning('[LLM] send_pack failed: %s' % e, extra={'id': 'LLM001'})
241
+
242
+ def _send_chunked(self, tags, fields, content, index):
243
+ try:
244
+ if not content:
245
+ self._send_pack(tags, fields, '', index)
246
+ return
247
+
248
+ tags = {k: v for k, v in tags.items() if v is not None}
249
+ fields = {k: v for k, v in fields.items() if v is not None}
250
+
251
+ content_bytes = content.encode('utf-8', errors='replace')
252
+ total_bytes = len(content_bytes)
253
+
254
+ if total_bytes <= _MAX_CONTENT_BYTES:
255
+ chunk_fields = dict(fields)
256
+ chunk_fields["chunk_index"] = 0
257
+ chunk_fields["chunk_total"] = 1
258
+ self._send_pack(tags, chunk_fields, content, index)
259
+ return
260
+
261
+ raw_chunks = []
262
+ i = 0
263
+ while i < total_bytes:
264
+ end = min(i + _MAX_CONTENT_BYTES, total_bytes)
265
+ if end < total_bytes:
266
+ while end > i and (content_bytes[end] & 0xC0) == 0x80:
267
+ end -= 1
268
+ raw_chunks.append(content_bytes[i:end].decode('utf-8', errors='replace'))
269
+ i = end
270
+
271
+ chunk_total = len(raw_chunks)
272
+ for idx, chunk in enumerate(raw_chunks):
273
+ chunk_fields = dict(fields)
274
+ chunk_fields["chunk_index"] = idx
275
+ chunk_fields["chunk_total"] = chunk_total
276
+ self._send_pack(tags, chunk_fields, chunk, index)
277
+ except Exception as e:
278
+ logging.warning('[LLM] send_chunked failed: %s' % e, extra={'id': 'LLM002'})
279
+
280
+
281
+ # ── module-level API ──
282
+
283
+ def dispatch_llm_pack(pack):
284
+ LlmLogSinkTask.get_instance().dispatch(pack)
285
+
286
+
287
+ def send_llm_tx_status(ctx):
288
+ LlmLogSinkTask.get_instance().send_tx_status(ctx)
289
+
290
+
291
+ def send_llm_pack(metadata):
292
+ pack = LlmStepStatus()
293
+ for key, val in metadata.items():
294
+ if hasattr(pack, key):
295
+ setattr(pack, key, val)
296
+ dispatch_llm_pack(pack)
297
+
298
+
299
+ def dispatch_llm_evaluation_pack(pack):
300
+ """LlmStepEvalStatus pack 을 LogSink 인프라로 송출.
301
+
302
+ LlmEvaluatorTask._run_one 이 원본 LlmStepStatus 를 복제해 만든 LlmStepEvalStatus 를
303
+ 이 함수로 송출. (txid, step_id) 결합 키로 백엔드에서 LlmStepStatus 와 사후 결합.
304
+ """
305
+ try:
306
+ LlmLogSinkTask.get_instance()._send_log_sink(pack)
307
+ except Exception as e:
308
+ logging.warning('[LLM] dispatch_llm_evaluation_pack failed: %s' % e,
309
+ extra={'id': 'LLM037'})
@@ -0,0 +1,78 @@
1
+ import time
2
+ import logging
3
+ from threading import Thread
4
+
5
+ from whatap.llm.stats import LLM_STAT_CLASSES
6
+
7
+
8
+ class LlmStatTask(Thread):
9
+ _instance = None
10
+
11
+ def __init__(self):
12
+ super(LlmStatTask, self).__init__()
13
+ self.stats = list()
14
+ self._stat_map = {}
15
+ self.last_executed = {}
16
+ LlmStatTask._instance = self
17
+
18
+ def _register_stats(self):
19
+ for cls in LLM_STAT_CLASSES:
20
+ stat = cls()
21
+ self.stats.append(stat)
22
+ self._stat_map[stat.name()] = stat
23
+ self.last_executed[stat.name()] = 0
24
+
25
+ def run(self):
26
+ self._register_stats()
27
+
28
+ while True:
29
+ current_time = time.time()
30
+ time.sleep(1)
31
+ for stat in self.stats:
32
+ last_executed_time = self.last_executed[stat.name()]
33
+ interval = stat.interval()
34
+
35
+ if current_time - last_executed_time >= interval:
36
+ try:
37
+ self.last_executed[stat.name()] = current_time
38
+ stat.process()
39
+ except Exception as e:
40
+ logging.debug(e, extra={'id': 'WA182'}, exc_info=True)
41
+
42
+ def notify(self, pack):
43
+ from whatap.conf.configure import Configure as conf
44
+ if not getattr(conf, 'llm_enabled', False):
45
+ return
46
+ for stat in self.stats:
47
+ try:
48
+ stat.update_from_pack(pack)
49
+ except Exception as e:
50
+ logging.debug(e, extra={'id': 'WA183'}, exc_info=True)
51
+
52
+ if pack.success:
53
+ from whatap.llm.stats.meter import Meter
54
+ Meter.increment()
55
+
56
+ def flush_last_error(self, ctx):
57
+ last_error_type = getattr(ctx, '_llm_last_error_type', None)
58
+ if not last_error_type:
59
+ return
60
+ from whatap.conf.configure import Configure as conf
61
+ if not getattr(conf, 'llm_enabled', False):
62
+ return
63
+ stat = self._stat_map.get('ErrorStat')
64
+ if stat:
65
+ stat.update_last_error(
66
+ getattr(ctx, '_llm_last_error_model', 'unknown'),
67
+ getattr(ctx, '_llm_last_error_provider', ''),
68
+ getattr(ctx, '_llm_last_error_op_type', 'unknown'),
69
+ url=getattr(ctx, '_llm_last_error_url', ''),
70
+ error_type=last_error_type,
71
+ prompt_version=getattr(ctx, '_llm_last_error_prompt_version', 'v1'),
72
+ )
73
+
74
+ @classmethod
75
+ def get_stat(cls, name):
76
+ if cls._instance is None:
77
+ return None
78
+ return cls._instance._stat_map.get(name)
@@ -0,0 +1,67 @@
1
+ from whatap.counter.tasks.base_task import BaseTask
2
+ from whatap.pack import tagCountPack
3
+ from whatap import DateUtil
4
+ import os
5
+
6
+ try:
7
+ import resource
8
+ HAS_RESOURCE = True
9
+ except ImportError:
10
+ HAS_RESOURCE = False
11
+
12
+ currentpid = os.getpid()
13
+
14
+ if HAS_RESOURCE:
15
+ soft_limit, _= resource.getrlimit(resource.RLIMIT_NOFILE)
16
+ else:
17
+ soft_limit = None
18
+
19
+
20
+ class OpenFileDescriptorTask(BaseTask):
21
+
22
+ def interval(self):
23
+ from whatap.conf.configure import Configure as conf
24
+ return conf.open_file_descriptor_interval
25
+
26
+ def process(self):
27
+ from whatap.conf.configure import Configure as conf
28
+ if not conf.open_file_descriptor_enabled:
29
+ return
30
+
31
+ currentnofile = self.currentNofile()
32
+ if soft_limit:
33
+ current_nofile_pct = float(currentnofile) / float(soft_limit) * float(100)
34
+ else:
35
+ current_nofile_pct = "N/A"
36
+ category = "app_filedescriptor"
37
+ tags = dict(pid=currentpid)
38
+ fields = dict(max_nofile = soft_limit,
39
+ currnet_nofile=currentnofile,
40
+ current_nofile_pct=current_nofile_pct )
41
+
42
+ p = tagCountPack.getTagCountPack(
43
+ t=DateUtil.now(),
44
+ category=f"{category}",
45
+ tags=tags,
46
+ fields=fields
47
+ )
48
+
49
+ self.send_pack(p)
50
+
51
+ def currentNofile(self):
52
+ if not HAS_RESOURCE:
53
+ try:
54
+ import psutil
55
+ process = psutil.Process(currentpid)
56
+ return len(process.open_files())
57
+ except ImportError:
58
+ return "N/A"
59
+ except Exception:
60
+ return "N/A"
61
+
62
+ fd_directory = f'/proc/{currentpid}/fd'
63
+ try:
64
+ fd_count = len(os.listdir(fd_directory))
65
+ return fd_count
66
+ except FileNotFoundError:
67
+ return "N/A"
whatap/io/__init__.py ADDED
@@ -0,0 +1 @@
1
+ from whatap.io.data_outputx import DataOutputX
@@ -0,0 +1,161 @@
1
+ import io
2
+ import struct
3
+ from whatap.pack.pack_enum import PackEnum
4
+
5
+ from whatap.value.value_enum import ValueEnum
6
+
7
+
8
+ class DataInputX(object):
9
+ def __init__(self, v):
10
+ self.buffer = io.BytesIO(v)
11
+
12
+ @staticmethod
13
+ def toInt(buf, pos):
14
+ ch1 = buf[pos] & 0xff
15
+ ch2 = buf[pos + 1] & 0xff
16
+ ch3 = buf[pos + 2] & 0xff
17
+ ch4 = buf[pos + 3] & 0xff
18
+ return ((ch1 << 24) + (ch2 << 16) + (ch3 << 8) + (ch4 << 0))
19
+
20
+ def readPack(self):
21
+ type = self.readShort()
22
+ return PackEnum.create(type).read(self)
23
+
24
+ def readValue(self):
25
+ type = self.readByte()
26
+ return ValueEnum.create(type).read(self)
27
+
28
+ # TODO
29
+ # def available(self):
30
+ # return len(self.buffer.getvalue())- self.offset > 0
31
+
32
+ # TODO
33
+ # def readStep(self):
34
+ # raise
35
+ # # byte type = this.inner.readByte();
36
+ # # return StepEnum.create(type).read(this);
37
+ # return None
38
+
39
+ def readIntBytes(self, max):
40
+ ln = self.readInt()
41
+ if ln < 0 or ln > max:
42
+ raise Exception('error')
43
+ return self.buffer.read(ln)
44
+
45
+ def readBoolean(self):
46
+ v = self.buffer.read(1)
47
+ return struct.unpack('>?', v)[0]
48
+
49
+ def readByte(self):
50
+ v = self.buffer.read(1)
51
+ return struct.unpack('>b', v)[0]
52
+
53
+ def readShort(self):
54
+ v = self.buffer.read(2)
55
+ return struct.unpack('>h', v)[0]
56
+
57
+ def readInt3(self):
58
+ s = self.buffer.read(3)
59
+ chrs = struct.unpack('>bBB', s)
60
+ ch1 = (chrs[0])
61
+ ch2 = (chrs[1])
62
+ ch3 = (chrs[2])
63
+ return (ch1 << 16) + (ch2 << 8) + ch3
64
+
65
+ def readInt(self):
66
+ v = self.buffer.read(4)
67
+
68
+ return struct.unpack('>i', v)[0]
69
+
70
+ def readLong5(self):
71
+ s = self.buffer.read(5)
72
+ chrs = struct.unpack('>bBBBB', s)
73
+ ch1 = (chrs[0])
74
+ ch2 = (chrs[1])
75
+ ch3 = (chrs[2])
76
+ ch4 = (chrs[3])
77
+ ch5 = (chrs[4])
78
+ return ((ch1 << 32) + (ch2 << 24) + (ch3 << 16) + (ch4 << 8) + (ch5))
79
+
80
+ def readLong(self):
81
+ v = self.buffer.read(8)
82
+ return struct.unpack('>q', v)[0]
83
+
84
+ def readFloat(self):
85
+ v = self.buffer.read(4)
86
+ return struct.unpack('>f', v)[0]
87
+
88
+ def readDouble(self):
89
+ v = self.buffer.read(8)
90
+ return struct.unpack('>d', v)[0]
91
+
92
+ def readDecimal(self):
93
+ ln = self.readByte()
94
+ if ln == 0:
95
+ return 0
96
+ elif ln == 1:
97
+ return self.readByte()
98
+ elif ln == 2:
99
+ return self.readShort()
100
+ elif ln == 3:
101
+ return self.readInt3()
102
+ elif ln == 4:
103
+ return self.readInt()
104
+ elif ln == 5:
105
+ return self.readLong5()
106
+ else:
107
+ return self.readLong()
108
+
109
+ def read(self, ln):
110
+ return self.buffer.read(ln)
111
+
112
+ def readBlob(self):
113
+ baselen = self.readByte() & 0xff
114
+ if baselen == 255:
115
+ ln = self.readShort() & 0xffff
116
+ return self.buffer.read(ln)
117
+ elif baselen == 254:
118
+ ln = self.readInt()
119
+ return self.buffer.read(ln)
120
+ elif baselen == 0:
121
+ return []
122
+ else:
123
+ return self.buffer.read(baselen)
124
+
125
+ def readText(self):
126
+ arr = self.readBlob()
127
+ if not len(arr):
128
+ return ''
129
+ else:
130
+ return arr.decode("utf-8")
131
+
132
+ def toByteArray(self):
133
+ return self.buffer.getvalue()
134
+
135
+ def readFloatArray(self):
136
+ ln = self.readShort()
137
+ data = []
138
+ for _ in range(ln):
139
+ data.append(self.readFloat())
140
+ return data
141
+
142
+ def readIntArray(self):
143
+ ln = self.readShort()
144
+ data = []
145
+ for _ in range(ln):
146
+ data.append(self.readInt())
147
+ return data
148
+
149
+ def readLongArray(self):
150
+ ln = self.readShort()
151
+ data = []
152
+ for _ in range(ln):
153
+ data.append(self.readLong())
154
+ return data
155
+
156
+ def readDecimalArray(self):
157
+ ln = self.readShort()
158
+ data = []
159
+ for _ in range(ln):
160
+ data.append(self.readDecimal())
161
+ return data