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,16 @@
1
+ """LLM 입력 메시지 데이터 모델 정의."""
2
+ from whatap.llm.log_sink_packs.llm_log_sink_pack import LlmLogSinkPack
3
+
4
+
5
+ class LlmInputMessage(LlmLogSinkPack):
6
+ """사용자 입력 프롬프트 메시지를 담는 팩."""
7
+
8
+ LLM_LOG_TYPE = 'input_message'
9
+
10
+ def __init__(self):
11
+ super().__init__()
12
+ self.prompt_text = ''
13
+
14
+ def content(self):
15
+ """입력 프롬프트 텍스트를 반환한다."""
16
+ return self.prompt_text
@@ -0,0 +1,72 @@
1
+ """LLM LogSink 팩의 기본 클래스 정의."""
2
+ from whatap.llm.definitions import LOG_SINK_CATEGORY
3
+
4
+
5
+ class LlmLogSinkPack(object):
6
+ """LLM LogSink 팩 기본 클래스로, 공통 태그와 컨텍스트 설정을 제공한다."""
7
+
8
+ CATEGORY = LOG_SINK_CATEGORY
9
+ LLM_LOG_TYPE = None
10
+
11
+ def __init__(self):
12
+ """공통 필드(txid, step_id, index, provider, url, operation_type, prompt_version)."""
13
+ self.txid = None
14
+ self.step_id = None
15
+ self.index = 0
16
+ self.provider = ''
17
+ self.url = ''
18
+ # operation_type 태그는 ``prompt_meta`` 데코레이터/스코프 의 prompt name 을 담는다.
19
+ # default: 'default' — 데코레이터 미적용 시.
20
+ self.operation_type = 'default'
21
+ # prompt 버전. ``prompt_meta(version='v2')`` 등으로 지정. default 'v1'.
22
+ self.prompt_version = 'v1'
23
+
24
+ @classmethod
25
+ def from_pack(cls, pack, **kwargs):
26
+ """기존 팩으로부터 새 인스턴스를 생성한다."""
27
+ obj = cls()
28
+ obj.txid = pack.txid
29
+ obj.step_id = pack.step_id
30
+ obj.provider = pack.provider
31
+ obj.index = pack.index
32
+ obj.operation_type = getattr(pack, 'operation_type', 'default')
33
+ obj.prompt_version = getattr(pack, 'prompt_version', 'v1')
34
+ for key, val in kwargs.items():
35
+ setattr(obj, key, val)
36
+ return obj
37
+
38
+ def set_context(self, ctx):
39
+ """트랜잭션 컨텍스트에서 txid, step_id, provider 정보를 설정한다."""
40
+ if ctx:
41
+ if not self.txid:
42
+ self.txid = str(ctx.id)
43
+ if not self.step_id:
44
+ self.step_id = str(getattr(ctx, '_llm_step_id', 0))
45
+ if not self.provider:
46
+ httpc_url = getattr(ctx, '_llm_httpc_url', '') or ''
47
+ if httpc_url:
48
+ url = httpc_url
49
+ if '://' in url:
50
+ url = url.split('://', 1)[1]
51
+ self.provider = url.split('/', 1)[0]
52
+ if '/' in url:
53
+ self.url = '/' + url.split('/', 1)[1]
54
+
55
+ def tags(self):
56
+ """공통 태그 딕셔너리를 반환한다 (모든 sub-pack 이 상속)."""
57
+ return {
58
+ 'llm_log_type': self.LLM_LOG_TYPE,
59
+ '@txid': self.txid,
60
+ '@step_id': self.step_id,
61
+ 'provider': self.provider,
62
+ 'operation_type': self.operation_type,
63
+ 'prompt_version': self.prompt_version,
64
+ }
65
+
66
+ def fields(self):
67
+ """필드 딕셔너리를 반환한다."""
68
+ return {}
69
+
70
+ def content(self):
71
+ """로그 콘텐츠 문자열을 반환한다."""
72
+ return ''
@@ -0,0 +1,19 @@
1
+ """LLM 출력 메시지 데이터 모델 정의."""
2
+ from whatap.llm.log_sink_packs.llm_log_sink_pack import LlmLogSinkPack
3
+
4
+
5
+ class LlmOutputMessage(LlmLogSinkPack):
6
+ """LLM 응답(completion + reasoning) 메시지를 담는 팩."""
7
+
8
+ LLM_LOG_TYPE = 'output_message'
9
+
10
+ def __init__(self):
11
+ super().__init__()
12
+ self.completion_text = ''
13
+ self.reasoning_text = ''
14
+
15
+ def content(self):
16
+ """reasoning + completion 텍스트를 합쳐서 반환한다."""
17
+ if self.reasoning_text and self.completion_text:
18
+ return self.reasoning_text + '\n' + self.completion_text
19
+ return self.reasoning_text or self.completion_text or ''
@@ -0,0 +1,94 @@
1
+ """LLM 평가 결과 데이터 모델.
2
+
3
+ ``LlmStepStatus`` 와 동일한 구조 (모델/토큰/비용/지연시간/결합키 등) 를 그대로 갖되
4
+ ``llm_log_type`` 만 ``llm_step_eval_status`` 로 다른 pack. 평가 결과 점수 필드 추가.
5
+
6
+ 평가 워커가 evaluator.evaluate() 결과를 받으면 원본 ``LlmStepStatus`` pack 을 복제해
7
+ 이 클래스로 만들고 (txid, step_id, index 결합 키 동일) 점수를 채워 송출한다.
8
+ 백엔드에서는 같은 ``(txid, step_id)`` 로 원본 ``llm_step_status`` 와 짝지어 본다.
9
+
10
+ 점수 필드 (0.0 ~ 1.0, evaluator label 별):
11
+ eval_hallucination — HallucinationEvaluator
12
+ eval_answer_relevance — AnswerRelevanceEvaluator
13
+ eval_toxicity — ToxicityEvaluator
14
+ eval_prompt_injection — PromptInjectionEvaluator
15
+ eval_factuality — FactualityEvaluator
16
+ eval_pii_leak — PIILeakEvaluator (규칙 기반)
17
+ eval_url_scan — URLScanEvaluator (규칙 기반)
18
+ eval_combined_judge — CombinedJudgeEvaluator (worst-aspect risk)
19
+ eval_success — 평가 자체 성공/실패 (bool → 1/0)
20
+
21
+ 대응되는 evaluator-label → 필드 매핑은 ``llm_evaluator_task._LABEL_TO_FIELD`` 에 있고,
22
+ 대응되는 metric (score histogram) 카테고리 매핑은 ``stats.eval_stat._LABEL_TO_STAT_NAME``
23
+ 에 있다. 신규 evaluator 추가 시 세 곳 (이 attr / _LABEL_TO_FIELD / _LABEL_TO_STAT_NAME)
24
+ 모두 갱신 필요.
25
+ """
26
+ import copy
27
+
28
+ from whatap.llm.log_sink_packs.llm_step_status import LlmStepStatus
29
+
30
+
31
+ class LlmStepEvalStatus(LlmStepStatus):
32
+ """LlmStepStatus 와 동일한 구조 + 평가 점수 필드들."""
33
+
34
+ LLM_LOG_TYPE = 'llm_step_eval_status'
35
+
36
+ def __init__(self):
37
+ super().__init__()
38
+ # 평가 점수 (0.0 ~ 1.0). evaluator 가 산출 안 했으면 None — 송출 시 누락.
39
+ self.eval_hallucination = None
40
+ self.eval_answer_relevance = None
41
+ self.eval_toxicity = None
42
+ self.eval_prompt_injection = None
43
+ self.eval_factuality = None
44
+ self.eval_pii_leak = None
45
+ self.eval_url_scan = None
46
+ self.eval_combined_judge = None
47
+ # 평가 자체가 성공/실패 (judge 호출 + parse 모두 정상이면 True). bool.
48
+ self.eval_success = None
49
+
50
+ @classmethod
51
+ def from_step_status(cls, step):
52
+ """원본 ``LlmStepStatus`` pack 의 모든 attr 을 복제한 새 EvalStatus 인스턴스 반환.
53
+
54
+ 평가 워커가 원본 pack 을 받아 이 메서드로 복제 → 점수 채움 → 송출.
55
+ 원본 pack 은 LlmLogSinkTask 가 이미 즉시 송출했고 변경 없이 그대로 둔다.
56
+
57
+ mutable 컨테이너 (list/dict — e.g. ``system_texts``) 는 얕은 복사로
58
+ 분리해 원본과 별도 인스턴스가 되도록 한다. evaluator 가 metadata 를 list
59
+ 에 append 하거나 future 코드가 eval pack 의 필드를 변형해도 원본에 leak
60
+ 되지 않도록 방어. trace ctx / client / event loop 같은 외부 객체는 공유
61
+ (reference 만 들고있음).
62
+ """
63
+ new = cls()
64
+ for k, v in step.__dict__.items():
65
+ if isinstance(v, (list, dict, set)):
66
+ # 동일 컨테이너 reference 공유 차단. 안의 원소는 그대로.
67
+ try:
68
+ v = copy.copy(v)
69
+ except Exception:
70
+ pass
71
+ setattr(new, k, v)
72
+ return new
73
+
74
+ def fields(self):
75
+ f = super().fields()
76
+ if self.eval_hallucination is not None:
77
+ f['eval_hallucination.n'] = float(self.eval_hallucination)
78
+ if self.eval_answer_relevance is not None:
79
+ f['eval_answer_relevance.n'] = float(self.eval_answer_relevance)
80
+ if self.eval_toxicity is not None:
81
+ f['eval_toxicity.n'] = float(self.eval_toxicity)
82
+ if self.eval_prompt_injection is not None:
83
+ f['eval_prompt_injection.n'] = float(self.eval_prompt_injection)
84
+ if self.eval_factuality is not None:
85
+ f['eval_factuality.n'] = float(self.eval_factuality)
86
+ if self.eval_pii_leak is not None:
87
+ f['eval_pii_leak.n'] = float(self.eval_pii_leak)
88
+ if self.eval_url_scan is not None:
89
+ f['eval_url_scan.n'] = float(self.eval_url_scan)
90
+ if self.eval_combined_judge is not None:
91
+ f['eval_combined_judge.n'] = float(self.eval_combined_judge)
92
+ if self.eval_success is not None:
93
+ f['eval_success.n'] = 1 if self.eval_success else 0
94
+ return f
@@ -0,0 +1,118 @@
1
+ """LLM 스텝 상태 데이터 모델 정의."""
2
+ from whatap.llm.definitions import PROVIDER_TOKEN_FIELDS, DEFAULT_TOKEN_FIELDS
3
+ from whatap.llm.log_sink_packs.llm_log_sink_pack import LlmLogSinkPack
4
+
5
+
6
+ class LlmStepStatus(LlmLogSinkPack):
7
+ """LLM API 호출 단위의 상태, 토큰, 비용, 지연시간 데이터를 담는 팩.
8
+
9
+ 평가 결과는 별도 ``LlmStepEvalStatus`` pack 으로 송출되며 (txid, step_id) 결합 키로
10
+ 백엔드에서 사후 결합. LlmStepStatus 자체는 평가 결과를 담지 않는다.
11
+ """
12
+
13
+ LLM_LOG_TYPE = 'llm_step_status'
14
+
15
+ def __init__(self):
16
+ """상태, 토큰, 비용, 지연시간 필드를 초기화한다."""
17
+ super().__init__()
18
+ # status
19
+ self.model = None
20
+ self.stream = False
21
+ self.success = False
22
+ self.finish_reason = None
23
+ self.features = ''
24
+ self.temperature = None
25
+ self.error = None
26
+ self.error_type = None
27
+
28
+ # tokens
29
+ self.input_tokens = None
30
+ self.output_tokens = None
31
+ self.total_tokens_count = None
32
+ self.cached_tokens = None
33
+ self.reasoning_tokens = None
34
+ self.audio_input_tokens = None
35
+ self.audio_output_tokens = None
36
+ self.accepted_prediction_tokens = None
37
+ self.rejected_prediction_tokens = None
38
+ self.embedding_count = None
39
+ self.dimensions = None
40
+ self.similarity = None
41
+ self.cache_creation_input_tokens = None
42
+ self.cache_read_input_tokens = None
43
+
44
+ # cost (USD)
45
+ self.cost = None
46
+ self.input_cost = None
47
+ self.output_cost = None
48
+ self.cached_cost = None
49
+ self.cache_creation_cost = None
50
+
51
+ # latency (ms)
52
+ self._start_time = 0
53
+ self.ttft = None
54
+ self.latency = None
55
+
56
+ def set_error(self, error, error_type):
57
+ """에러 정보를 설정한다."""
58
+ self.error = str(error)
59
+ self.error_type = error_type
60
+
61
+ def set_tokens(self, token_dict):
62
+ """토큰 사용량 딕셔너리로부터 값을 설정한다."""
63
+ for key, val in token_dict.items():
64
+ if hasattr(self, key) and val is not None:
65
+ setattr(self, key, val)
66
+
67
+ def calculate_cost(self):
68
+ """모델 가격 정보를 기반으로 비용을 계산한다."""
69
+ from whatap.llm.pricing import calculate_cost
70
+ calculate_cost(self)
71
+
72
+ def tags(self):
73
+ """스텝 상태 태그(모델, 스트림, 성공 여부 등)."""
74
+ t = super().tags()
75
+ t['model'] = self.model
76
+ t['stream'] = str(self.stream)
77
+ t['finish_reason'] = self.finish_reason or ''
78
+ t['operation_type'] = self.operation_type
79
+ t['features'] = self.features
80
+ return t
81
+
82
+ def fields(self):
83
+ """온도, 토큰, 에러 등 수치 필드."""
84
+ f = {}
85
+ if self.temperature is not None:
86
+ f['temperature.n'] = self.temperature
87
+ f.update(self._token_fields())
88
+ if self.error_type in ('api_error', 'program_error'):
89
+ f['error'] = self.error
90
+ f['error_type'] = self.error_type
91
+ return f
92
+
93
+ def _token_fields(self):
94
+ keys = DEFAULT_TOKEN_FIELDS
95
+ for provider_key, token_fields in PROVIDER_TOKEN_FIELDS.items():
96
+ if provider_key in self.provider:
97
+ keys = token_fields
98
+ break
99
+ fields = {}
100
+ for key in keys:
101
+ val = getattr(self, key, None)
102
+ if val is not None:
103
+ fields[key + '.n'] = val
104
+ if self.cost is not None:
105
+ fields['cost.n'] = self.cost
106
+ if self.input_cost:
107
+ fields['input_cost.n'] = self.input_cost
108
+ if self.output_cost:
109
+ fields['output_cost.n'] = self.output_cost
110
+ if self.cached_cost:
111
+ fields['cached_cost.n'] = self.cached_cost
112
+ if self.cache_creation_cost:
113
+ fields['cache_creation_cost.n'] = self.cache_creation_cost
114
+ if self.latency is not None:
115
+ fields['latency.n'] = self.latency
116
+ if self.ttft is not None:
117
+ fields['ttft.n'] = self.ttft
118
+ return fields
@@ -0,0 +1,16 @@
1
+ """LLM 시스템 메시지 데이터 모델 정의."""
2
+ from whatap.llm.log_sink_packs.llm_log_sink_pack import LlmLogSinkPack
3
+
4
+
5
+ class LlmSystemMessage(LlmLogSinkPack):
6
+ """시스템 프롬프트 메시지를 담는 팩."""
7
+
8
+ LLM_LOG_TYPE = 'system_message'
9
+
10
+ def __init__(self):
11
+ super().__init__()
12
+ self.system_text = ''
13
+
14
+ def content(self):
15
+ """시스템 메시지 텍스트를 반환한다."""
16
+ return self.system_text
@@ -0,0 +1,44 @@
1
+ """LLM 도구 호출 데이터 모델 정의."""
2
+ from whatap import logging
3
+ from whatap.llm.log_sink_packs.llm_log_sink_pack import LlmLogSinkPack
4
+
5
+
6
+ class LlmToolCalls(LlmLogSinkPack):
7
+ """LLM 도구(tool) 호출 정보를 담는 팩."""
8
+
9
+ LLM_LOG_TYPE = 'tool'
10
+
11
+ def __init__(self):
12
+ super().__init__()
13
+ self.tool_calls_text = ''
14
+
15
+ def fields(self):
16
+ """도구 호출 JSON을 파싱하여 함수명과 인자를 필드로 반환한다."""
17
+ return self._parse(self.tool_calls_text)
18
+
19
+ def content(self):
20
+ """도구 호출 원본 텍스트를 반환한다."""
21
+ return self.tool_calls_text
22
+
23
+ @staticmethod
24
+ def _parse(tool_calls_text):
25
+ import json
26
+ fields = {}
27
+ try:
28
+ tc_list = json.loads(tool_calls_text)
29
+ if isinstance(tc_list, list):
30
+ names = []
31
+ args = []
32
+ for tc in tc_list:
33
+ fn = tc.get("function", "")
34
+ if isinstance(fn, dict):
35
+ names.append(fn.get("name", ""))
36
+ args.append(fn.get("arguments", ""))
37
+ else:
38
+ names.append(fn)
39
+ args.append(tc.get("arguments", ""))
40
+ fields["function"] = ",".join(n for n in names if n)
41
+ fields["arguments"] = ",".join(a for a in args if a)
42
+ except Exception as e:
43
+ logging.warning('[LLM] tool_calls parse failed: %s' % e, extra={'id': 'LLM017'})
44
+ return fields
@@ -0,0 +1,16 @@
1
+ """LLM 도구 실행 결과 데이터 모델 정의."""
2
+ from whatap.llm.log_sink_packs.llm_log_sink_pack import LlmLogSinkPack
3
+
4
+
5
+ class LlmToolResults(LlmLogSinkPack):
6
+ """도구(tool) 실행 결과를 담는 팩."""
7
+
8
+ LLM_LOG_TYPE = 'tool_result'
9
+
10
+ def __init__(self):
11
+ super().__init__()
12
+ self.tool_results_text = ''
13
+
14
+ def content(self):
15
+ """도구 실행 결과 텍스트를 반환한다."""
16
+ return self.tool_results_text
@@ -0,0 +1,108 @@
1
+ """LLM 트랜잭션 요약 상태 데이터 모델 정의."""
2
+ from whatap.llm.log_sink_packs.llm_log_sink_pack import LlmLogSinkPack
3
+
4
+ TX_SUMMARY_TOKEN_FIELDS = [
5
+ 'input_tokens', 'output_tokens', 'total_tokens_count',
6
+ 'cached_tokens', 'reasoning_tokens',
7
+ 'audio_input_tokens', 'audio_output_tokens',
8
+ 'accepted_prediction_tokens', 'rejected_prediction_tokens',
9
+ 'cache_creation_input_tokens', 'cache_read_input_tokens',
10
+ 'embedding_count',
11
+ ]
12
+
13
+
14
+ class LlmTxStatus(LlmLogSinkPack):
15
+ """트랜잭션 단위의 LLM 호출 요약 정보를 집계하는 팩."""
16
+
17
+ LLM_LOG_TYPE = 'llm_tx_status'
18
+
19
+ def __init__(self):
20
+ """호출 횟수, 에러, 토큰, 비용, 모델 등 집계 필드를 초기화한다."""
21
+ super().__init__()
22
+ self.first_step_id = None
23
+ self.last_step_id = None
24
+ self.call_count = 0
25
+ self.error_count = 0
26
+ self.latency = 0.0
27
+ self.cost = 0.0
28
+ self.input_cost = 0.0
29
+ self.output_cost = 0.0
30
+ self.models = set()
31
+ self.operation_types = set()
32
+ self.prompt_versions = set()
33
+ self.providers = set()
34
+ for f in TX_SUMMARY_TOKEN_FIELDS:
35
+ setattr(self, f, 0)
36
+
37
+ def accumulate(self, pack):
38
+ """스텝 상태 팩의 데이터를 트랜잭션 요약에 누적한다."""
39
+ self.call_count += 1
40
+ if not pack.success:
41
+ self.error_count += 1
42
+
43
+ for f in TX_SUMMARY_TOKEN_FIELDS:
44
+ val = getattr(pack, f, None)
45
+ if val:
46
+ setattr(self, f, getattr(self, f) + val)
47
+
48
+ if pack.latency:
49
+ self.latency += pack.latency
50
+ if pack.cost:
51
+ self.cost += pack.cost
52
+ if pack.input_cost:
53
+ self.input_cost += pack.input_cost
54
+ if pack.output_cost:
55
+ self.output_cost += pack.output_cost
56
+
57
+ if pack.step_id:
58
+ if self.first_step_id is None:
59
+ self.first_step_id = pack.step_id
60
+ self.last_step_id = pack.step_id
61
+
62
+ if pack.provider:
63
+ self.providers.add(pack.provider)
64
+ if pack.model:
65
+ self.models.add(pack.model)
66
+ if pack.operation_type and pack.operation_type not in ('unknown', ''):
67
+ self.operation_types.add(pack.operation_type)
68
+ pv = getattr(pack, 'prompt_version', None)
69
+ if pv:
70
+ self.prompt_versions.add(pv)
71
+
72
+ def tags(self):
73
+ """트랜잭션 요약 태그(provider, model, operation_type, prompt_version 등)."""
74
+ tags = {
75
+ 'llm_log_type': self.LLM_LOG_TYPE,
76
+ '@txid': self.txid,
77
+ '@first_step_id': self.first_step_id or '',
78
+ '@last_step_id': self.last_step_id or '',
79
+ }
80
+ if self.providers:
81
+ tags['provider'] = ','.join(sorted(self.providers))
82
+ if self.models:
83
+ tags['model'] = ','.join(sorted(self.models))
84
+ if self.operation_types:
85
+ tags['operation_type'] = ','.join(sorted(self.operation_types))
86
+ if self.prompt_versions:
87
+ tags['prompt_version'] = ','.join(sorted(self.prompt_versions))
88
+ return tags
89
+
90
+ def fields(self):
91
+ """호출 횟수, 토큰, 비용 등 집계 수치 필드를 반환한다."""
92
+ fields = {
93
+ 'call_count.n': self.call_count,
94
+ 'error_count.n': self.error_count,
95
+ }
96
+ for f in TX_SUMMARY_TOKEN_FIELDS:
97
+ val = getattr(self, f, 0)
98
+ if val:
99
+ fields[f + '.n'] = val
100
+ if self.latency:
101
+ fields['latency.n'] = self.latency
102
+ if self.cost:
103
+ fields['cost.n'] = round(self.cost, 6)
104
+ if self.input_cost:
105
+ fields['input_cost.n'] = round(self.input_cost, 6)
106
+ if self.output_cost:
107
+ fields['output_cost.n'] = round(self.output_cost, 6)
108
+ return fields