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
File without changes
@@ -0,0 +1,31 @@
1
+ from whatap.trace.trace_handler import trace_handler
2
+ from whatap.trace.mod.httpc.util import transfer, \
3
+ interceptor_httpc_request
4
+
5
+
6
+ def instrument_revproxy_views(module):
7
+ def wrapper(fn):
8
+ @trace_handler(fn)
9
+ def trace(*args, **kwargs):
10
+ callback = fn(*args, **kwargs)
11
+
12
+ # set mtid header
13
+ callback = transfer(callback)
14
+ return callback
15
+ return trace
16
+
17
+ module.ProxyView.get_proxy_request_headers = wrapper(module.ProxyView.get_proxy_request_headers)
18
+
19
+
20
+ def wrapper(fn):
21
+ @trace_handler(fn)
22
+ def trace(*args, **kwargs):
23
+ # set httpc_url
24
+ httpc_url = args[0].upstream
25
+ callback = interceptor_httpc_request(fn, httpc_url, *args, **kwargs)
26
+ return callback
27
+
28
+ return trace
29
+
30
+ module.ProxyView.dispatch = wrapper(module.ProxyView.dispatch)
31
+
@@ -0,0 +1,70 @@
1
+ from whatap.trace.trace_handler import trace_handler
2
+ from whatap.trace.mod.httpc.util import transfer, \
3
+ interceptor_httpc_request, interceptor_sock_connect
4
+
5
+ request_injection_processed = False
6
+ def instrument_httplib(module):
7
+ global request_injection_processed
8
+ def wrapper(fn):
9
+ @trace_handler(fn)
10
+ def trace(*args, **kwargs):
11
+ # set mtid header
12
+ kwargs['headers'] = transfer(kwargs.get('headers', {}))
13
+ newargs = []
14
+ for arg in args:
15
+ if arg and isinstance(arg, dict) and 'host' in [x.lower() for x in arg.keys()]:
16
+ arg = transfer(arg)
17
+ newargs.append(arg)
18
+ # set httpc_url
19
+ httpc_url = args[2]
20
+ if hasattr(args[0], 'host'):
21
+ httpc_url = getattr(args[0], 'host') + httpc_url
22
+ callback = interceptor_httpc_request(fn, httpc_url, *newargs, **kwargs)
23
+ return callback
24
+
25
+ return trace
26
+ if not request_injection_processed:
27
+ module.HTTPConnection.request = wrapper(module.HTTPConnection.request)
28
+ request_injection_processed = True
29
+
30
+ def wrapper(fn):
31
+ @trace_handler(fn)
32
+ def trace(*args, **kwargs):
33
+ callback = interceptor_sock_connect(fn, *args, **kwargs)
34
+ return callback
35
+
36
+ return trace
37
+ module.HTTPConnection.connect = wrapper(module.HTTPConnection.connect)
38
+
39
+ def instrument_httplib2(module):
40
+ global request_injection_processed
41
+ def wrapper(fn):
42
+ @trace_handler(fn)
43
+ def trace(*args, **kwargs):
44
+ # set mtid header
45
+ kwargs['headers'] = transfer(kwargs.get('headers', {}))
46
+ newargs = []
47
+ for arg in args:
48
+ if arg and isinstance(arg, dict) and 'host' in [x.lower() for x in arg.keys()]:
49
+ arg = transfer(arg)
50
+ newargs.append(arg)
51
+ # set httpc_url
52
+ httpc_url = args[2]
53
+ if hasattr(args[0], 'host'):
54
+ httpc_url = getattr(args[0], 'host') + httpc_url
55
+ callback = interceptor_httpc_request(fn, httpc_url, *newargs, **kwargs)
56
+ return callback
57
+
58
+ return trace
59
+ if not request_injection_processed:
60
+ module.Http.request = wrapper(module.Http.request)
61
+ request_injection_processed = True
62
+ def wrapper(fn):
63
+ @trace_handler(fn)
64
+ def trace(*args, **kwargs):
65
+ callback = interceptor_sock_connect(fn, *args, **kwargs)
66
+ return callback
67
+ return trace
68
+
69
+ module.HTTPConnectionWithTimeout.connect = wrapper(module.HTTPConnectionWithTimeout.connect)
70
+ module.HTTPSConnectionWithTimeout.connect = wrapper(module.HTTPSConnectionWithTimeout.connect)
@@ -0,0 +1,62 @@
1
+ from whatap.trace.trace_handler import trace_handler, async_trace_handler
2
+ from whatap.trace.mod.httpc.util import transfer, \
3
+ interceptor_httpc_request, async_interceptor_httpc_request
4
+
5
+
6
+ def instrument_httpx(module):
7
+ def wrapper(fn):
8
+ @trace_handler(fn)
9
+ def trace(*args, **kwargs):
10
+ if len(args) >= 2 and hasattr(args[1], 'headers') and hasattr(args[1], 'url'):
11
+ request = args[1]
12
+ request.headers = transfer(request.headers)
13
+ httpc_url = str(request.url)
14
+
15
+ # 2. stream call: send(request=request, ...)
16
+ elif len(args) == 1 and 'request' in kwargs:
17
+ request = kwargs['request']
18
+ if hasattr(request, 'headers') and hasattr(request, 'url'):
19
+ request.headers = transfer(request.headers)
20
+ httpc_url = str(request.url)
21
+ else:
22
+ httpc_url = "invalid_request_object"
23
+
24
+ # 3. 예상치 못한 패턴
25
+ else:
26
+ httpc_url = "httpx_unknown_pattern"
27
+
28
+ return interceptor_httpc_request(fn, httpc_url, *args, **kwargs)
29
+
30
+ return trace
31
+
32
+ def async_wrapper(fn):
33
+ @async_trace_handler(fn)
34
+ async def trace(*args, **kwargs):
35
+ if len(args) >= 2 and hasattr(args[1], 'headers') and hasattr(args[1], 'url'):
36
+ request = args[1]
37
+ request.headers = transfer(request.headers)
38
+ httpc_url = str(request.url)
39
+
40
+ # 2. stream call: send(request=request, ...)
41
+ elif len(args) == 1 and 'request' in kwargs:
42
+ request = kwargs['request']
43
+ if hasattr(request, 'headers') and hasattr(request, 'url'):
44
+ request.headers = transfer(request.headers)
45
+ httpc_url = str(request.url)
46
+ else:
47
+ httpc_url = "invalid_request_object"
48
+
49
+ # 3. 예상치 못한 패턴
50
+ else:
51
+ httpc_url = "httpx_unknown_pattern"
52
+
53
+ return await async_interceptor_httpc_request(fn, httpc_url, *args, **kwargs)
54
+
55
+ return trace
56
+
57
+ if hasattr(module, 'Client') and hasattr(module.Client, 'send'):
58
+ module.Client.send = wrapper(module.Client.send)
59
+
60
+
61
+ if hasattr(module, 'AsyncClient') and hasattr(module.AsyncClient, 'send'):
62
+ module.AsyncClient.send = async_wrapper(module.AsyncClient.send)
@@ -0,0 +1,20 @@
1
+ from whatap.trace.trace_handler import trace_handler
2
+ from whatap.trace.mod.httpc.util import transfer, \
3
+ interceptor_httpc_request
4
+
5
+
6
+ def instrument_requests(module):
7
+ def wrapper(fn):
8
+ @trace_handler(fn)
9
+ def trace(*args, **kwargs):
10
+ # set mtid header
11
+ args[1].headers = transfer(args[1].headers)
12
+
13
+ # set httpc_url
14
+ httpc_url = args[1].url
15
+ callback = interceptor_httpc_request(fn, httpc_url, *args, **kwargs)
16
+ return callback
17
+
18
+ return trace
19
+
20
+ module.Session.send = wrapper(module.Session.send)
@@ -0,0 +1,27 @@
1
+ from whatap.trace.trace_handler import trace_handler
2
+ from whatap.trace.mod.httpc.util import transfer, interceptor_httpc_request, \
3
+ interceptor_sock_connect
4
+
5
+
6
+ def instrument_urllib3(module):
7
+ def wrapper(fn):
8
+ @trace_handler(fn)
9
+ def trace(*args, **kwargs):
10
+ # set mtid header
11
+ kwargs['headers'] = transfer(kwargs.get('headers', {}))
12
+ newargs = []
13
+ for arg in args:
14
+ if arg and isinstance(arg, dict) and 'host' in [x.lower() for x in arg.keys()]:
15
+ arg = transfer(arg)
16
+ newargs.append(arg)
17
+ # set httpc_url
18
+ httpc_url = args[2]
19
+ if hasattr(args[0], 'host'):
20
+ httpc_url = getattr(args[0], 'host') + httpc_url
21
+ callback = interceptor_httpc_request(fn, httpc_url, *newargs, **kwargs)
22
+ return callback
23
+
24
+ return trace
25
+ if hasattr(module, 'RequestMethods'):
26
+ module.RequestMethods.request = wrapper(module.RequestMethods.request)
27
+
@@ -0,0 +1,388 @@
1
+ import random
2
+
3
+ from whatap import logging
4
+ from whatap.conf.configure import Configure as conf
5
+ from whatap.net.packet_type_enum import PacketTypeEnum
6
+ import whatap.net.async_sender as async_sender
7
+ from whatap.trace.trace_context_manager import TraceContextManager
8
+ from whatap.trace.trace_error import interceptor_step_error
9
+ from whatap.llm.definitions import URL_OPERATION_MAP
10
+ from whatap.util.date_util import DateUtil
11
+ from whatap.util.hexa32 import Hexa32
12
+
13
+
14
+ def _update_api_status_stats(ctx, status_code):
15
+ try:
16
+ from whatap.counter.tasks.llm_stat_task import LlmStatTask
17
+ stat = LlmStatTask.get_stat('ApiStatusStat')
18
+ if not stat:
19
+ return
20
+ httpc_url = getattr(ctx, '_llm_httpc_url', '') or ''
21
+ url = ''
22
+ if httpc_url:
23
+ if '://' in httpc_url:
24
+ httpc_url = httpc_url.split('://', 1)[1]
25
+ if '/' in httpc_url:
26
+ url = '/' + httpc_url.split('/', 1)[1]
27
+ # operation_type / prompt_version 은 prompt_meta scope 우선 (사용자 라벨링).
28
+ # scope 미적용 시 default ('default', 'v1').
29
+ try:
30
+ from whatap.llm.prompt_meta import get_prompt_meta
31
+ op_type, prompt_version = get_prompt_meta()
32
+ except Exception:
33
+ op_type = getattr(ctx, '_llm_operation_type', None) or 'unknown'
34
+ prompt_version = 'v1'
35
+ stat.update_stats(
36
+ getattr(ctx, '_llm_model', None) or 'unknown',
37
+ getattr(ctx, '_llm_provider', None) or '',
38
+ op_type,
39
+ status_code,
40
+ url=url,
41
+ prompt_version=prompt_version,
42
+ )
43
+ except Exception:
44
+ pass
45
+
46
+
47
+ def _extract_host(url):
48
+ """URL에서 호스트 추출"""
49
+ host = url
50
+ if '://' in host:
51
+ host = host.split('://', 1)[1]
52
+ return host.split('/', 1)[0]
53
+
54
+
55
+ def _match_llm_api_host(httpc_url):
56
+ """whatap.conf의 llm_api_hosts 설정과 호스트 매칭"""
57
+ hosts = conf.llm_api_hosts
58
+ if not hosts:
59
+ return False
60
+ try:
61
+ host = _extract_host(httpc_url)
62
+ if not host:
63
+ return False
64
+ if isinstance(hosts, str):
65
+ hosts = [h.strip() for h in hosts.split(',') if h.strip()]
66
+ for h in hosts:
67
+ if host == h or host.endswith('.' + h):
68
+ return True
69
+ except Exception:
70
+ pass
71
+ return False
72
+
73
+
74
+ def _detect_llm_api(ctx, httpc_url):
75
+ """LLM API 감지: 플래그 기반, URL 패턴 기반, 호스트 설정 기반"""
76
+ op_type = 'unknown'
77
+ for pattern, ot in URL_OPERATION_MAP:
78
+ if pattern in httpc_url:
79
+ op_type = ot
80
+ break
81
+ if getattr(ctx, '_llm_httpc_pending', False) and (op_type != 'unknown' or _match_llm_api_host(httpc_url)):
82
+ ctx.driver = 'LLM API'
83
+ ctx.is_llm = 1
84
+ ctx._llm_httpc_pending = False
85
+ ctx._llm_operation_type = op_type
86
+ ctx._llm_httpc_url = httpc_url
87
+
88
+ # HTTPC URL에서 호스트/경로 추출 → ctx 속성 + active stat 즉시 등록
89
+ try:
90
+ host = _extract_host(httpc_url)
91
+ url_path = ''
92
+ raw = httpc_url
93
+ if '://' in raw:
94
+ raw = raw.split('://', 1)[1]
95
+ if '/' in raw:
96
+ url_path = '/' + raw.split('/', 1)[1]
97
+ if host:
98
+ ctx._llm_provider = host
99
+ model = getattr(ctx, '_llm_model', None)
100
+ if model:
101
+ from whatap.counter.tasks.llm_stat_task import LlmStatTask
102
+ stat = LlmStatTask.get_stat('ActiveStat')
103
+ if stat:
104
+ stat.set_host(model, host, url_path)
105
+ except Exception:
106
+ pass
107
+ return True
108
+ return False
109
+
110
+
111
+ def interceptor_httpc_request(fn, httpc_url, *args, **kwargs):
112
+ ctx = TraceContextManager.getLocalContext()
113
+ if not ctx or ctx.active_httpc_hash:
114
+ try:
115
+ return fn(*args, **kwargs)
116
+ except Exception as e:
117
+ interceptor_step_error(e, ctx=ctx)
118
+ raise
119
+
120
+ param = None
121
+ method = None
122
+ if httpc_url.find('?') > -1:
123
+ httpc_url, param = httpc_url.split('?')
124
+
125
+ is_llm = _detect_llm_api(ctx, httpc_url)
126
+ if not ctx.step_id:
127
+ seq = getattr(ctx, '_httpc_seq', 1)
128
+ ctx.step_id = seq
129
+ ctx._httpc_seq = seq + 1
130
+
131
+ start_time = DateUtil.nowSystem()
132
+ ctx.start_time = start_time
133
+ ctx.httpc_url = httpc_url
134
+ ctx.active_httpc_hash = ctx.httpc_url
135
+ ctx.active_httpc_start_time = DateUtil.nowSystem()
136
+
137
+ callback = None
138
+ try:
139
+ try:
140
+ callback = fn(*args, **kwargs)
141
+ return callback
142
+ except TypeError as e:
143
+ callback = fn(*args)
144
+ return callback
145
+ except Exception as e:
146
+ interceptor_step_error(e, ctx=ctx)
147
+ raise
148
+ finally:
149
+ try:
150
+ if ctx.driver == 'LLM API' and callback is not None:
151
+ status_code = getattr(callback, 'status_code', None)
152
+ if status_code:
153
+ _update_api_status_stats(ctx, status_code)
154
+ if status_code >= 400:
155
+ try:
156
+ read_fn = getattr(callback, 'read', None)
157
+ if callable(read_fn):
158
+ try:
159
+ read_fn()
160
+ except Exception:
161
+ pass
162
+ error_text = callback.text or ''
163
+ except Exception as e:
164
+ logging.warning('[HTTPC] LLM API error text read failed: %s' % e, extra={'id': 'HTTPC001'})
165
+ error_text = ''
166
+ try:
167
+ interceptor_step_error(
168
+ Exception('[LLM API Error] HTTP {} {}'.format(status_code, error_text)),
169
+ ctx=ctx
170
+ )
171
+ except Exception as e:
172
+ logging.warning('[HTTPC] interceptor_step_error failed: %s' % e, extra={'id': 'HTTPC002'})
173
+ except Exception as e:
174
+ logging.warning('[HTTPC] LLM API error handling failed: %s' % e, extra={'id': 'HTTPC003'})
175
+
176
+ datas = [ctx.httpc_url, ctx.step_id, ctx.driver]
177
+ ctx.elapsed = DateUtil.nowSystem() - start_time
178
+ async_sender.send_packet(PacketTypeEnum.TX_HTTPC, ctx, datas)
179
+
180
+ if conf.profile_http_parameter_enabled and len(args) > 1:
181
+ try:
182
+ if type(args[1]) == dict:
183
+ param = (args[1].body if 'body' in args[1] else args[1]) or param
184
+ method = args[1].method if 'method' in args[1] else args[1]
185
+
186
+ if param:
187
+ datas = ['HTTP-PARAMETERS', method, param]
188
+ ctx.start_time = DateUtil.nowSystem()
189
+ async_sender.send_packet(PacketTypeEnum.TX_SECURE_MSG, ctx, datas)
190
+ except Exception as _e:
191
+ # finally 가 어떤 일이 있어도 return 값을 exception 으로 둔갑시키지 않도록 방어
192
+ logging.warning(
193
+ '[HTTPC] profile_http_parameter_enabled extract failed: %s' % _e,
194
+ extra={'id': 'HTTPC004'}
195
+ )
196
+
197
+ if is_llm:
198
+ ctx._llm_step_id = ctx.step_id
199
+ ctx.active_httpc_hash = 0
200
+ ctx.active_httpc_start_time = 0
201
+ ctx.httpc_url = None
202
+ ctx.step_id = 0
203
+ ctx.driver = ''
204
+
205
+
206
+ async def async_interceptor_httpc_request(fn, httpc_url, *args, **kwargs):
207
+ ctx = TraceContextManager.getLocalContext()
208
+ if not ctx or ctx.active_httpc_hash:
209
+ try:
210
+ return await fn(*args, **kwargs)
211
+ except Exception as e:
212
+ interceptor_step_error(e, ctx=ctx)
213
+ raise
214
+
215
+ param = None
216
+ method = None
217
+ if httpc_url.find('?') > -1:
218
+ httpc_url, param = httpc_url.split('?')
219
+
220
+ is_llm = _detect_llm_api(ctx, httpc_url)
221
+ if not ctx.step_id:
222
+ seq = getattr(ctx, '_httpc_seq', 1)
223
+ ctx.step_id = seq
224
+ ctx._httpc_seq = seq + 1
225
+
226
+ start_time = DateUtil.nowSystem()
227
+ ctx.start_time = start_time
228
+ ctx.httpc_url = httpc_url
229
+ ctx.active_httpc_hash = ctx.httpc_url
230
+ ctx.active_httpc_start_time = DateUtil.nowSystem()
231
+
232
+ callback = None
233
+ try:
234
+ callback = await fn(*args, **kwargs)
235
+ return callback
236
+ except Exception as e:
237
+ interceptor_step_error(e, ctx=ctx)
238
+ raise
239
+ finally:
240
+ try:
241
+ if ctx.driver == 'LLM API' and callback is not None:
242
+ status_code = getattr(callback, 'status_code', None)
243
+ if status_code:
244
+ _update_api_status_stats(ctx, status_code)
245
+ if status_code >= 400:
246
+ try:
247
+ aread_fn = getattr(callback, 'aread', None)
248
+ if callable(aread_fn):
249
+ try:
250
+ await aread_fn()
251
+ except Exception:
252
+ pass
253
+ else:
254
+ read_fn = getattr(callback, 'read', None)
255
+ if callable(read_fn):
256
+ try:
257
+ read_fn()
258
+ except Exception:
259
+ pass
260
+ error_text = callback.text or ''
261
+ except Exception as e:
262
+ logging.warning('[HTTPC] LLM API error text read failed: %s' % e, extra={'id': 'HTTPC001'})
263
+ error_text = ''
264
+ try:
265
+ interceptor_step_error(
266
+ Exception('[LLM API Error] HTTP {} {}'.format(status_code, error_text)),
267
+ ctx=ctx
268
+ )
269
+ except Exception as e:
270
+ logging.warning('[HTTPC] interceptor_step_error failed: %s' % e, extra={'id': 'HTTPC002'})
271
+ except Exception as e:
272
+ logging.warning('[HTTPC] LLM API error handling failed: %s' % e, extra={'id': 'HTTPC003'})
273
+
274
+ datas = [ctx.httpc_url, ctx.step_id, ctx.driver]
275
+ ctx.elapsed = DateUtil.nowSystem() - start_time
276
+ async_sender.send_packet(PacketTypeEnum.TX_HTTPC, ctx, datas)
277
+
278
+ if conf.profile_http_parameter_enabled and len(args) > 1:
279
+ try:
280
+ if type(args[1]) == dict:
281
+ param = (args[1].body if 'body' in args[1] else args[1]) or param
282
+ method = args[1].method if 'method' in args[1] else args[1]
283
+
284
+ if param:
285
+ datas = ['HTTP-PARAMETERS', method, param]
286
+ ctx.start_time = DateUtil.nowSystem()
287
+ async_sender.send_packet(PacketTypeEnum.TX_SECURE_MSG, ctx, datas)
288
+ except Exception as _e:
289
+ # finally 가 어떤 일이 있어도 return 값을 exception 으로 둔갑시키지 않도록 방어
290
+ logging.warning(
291
+ '[HTTPC] profile_http_parameter_enabled extract failed: %s' % _e,
292
+ extra={'id': 'HTTPC004'}
293
+ )
294
+
295
+ if is_llm:
296
+ ctx._llm_step_id = ctx.step_id
297
+ ctx.active_httpc_hash = 0
298
+ ctx.active_httpc_start_time = 0
299
+ ctx.httpc_url = None
300
+ ctx.step_id = 0
301
+ ctx.driver = ''
302
+
303
+
304
+ def interceptor_sock_connect(fn, *args, **kwargs):
305
+ ctx = TraceContextManager.getLocalContext()
306
+ if not ctx:
307
+ return fn(*args, **kwargs)
308
+
309
+ try:
310
+ ctx.socket_connecting = True
311
+ callback = fn(*args, **kwargs)
312
+ return callback
313
+ except Exception as e:
314
+ interceptor_step_error(e)
315
+ finally:
316
+ ctx.socket_connecting = False
317
+
318
+
319
+ def inter_tx_trace_auto_on(ctx):
320
+ try:
321
+ if isinstance(conf.mtrace_rate, str):
322
+ conf.mtrace_rate = int(conf.mtrace_rate)
323
+ except ValueError:
324
+ conf.mtrace_rate = 0
325
+ finally:
326
+ if conf.mtrace_rate <= 0 or ctx.httpc_checked or ctx.mtid != 0:
327
+ return
328
+
329
+ ctx.httpc_checked = True
330
+
331
+ try:
332
+ inter_tx_trace_auto_on.check_seq += 1
333
+ except AttributeError:
334
+ inter_tx_trace_auto_on.check_seq = 1
335
+ finally:
336
+ check_seq = inter_tx_trace_auto_on.check_seq
337
+
338
+ rate = int(conf.mtrace_rate / 10)
339
+ if rate == 10:
340
+ ctx.mtid = TraceContextManager.getId()
341
+ elif rate == 9:
342
+ if check_seq % 10 != 0:
343
+ ctx.mtid = TraceContextManager.getId()
344
+ elif rate == 8:
345
+ if check_seq % 5 != 0:
346
+ ctx.mtid = TraceContextManager.getId()
347
+ elif rate == 7:
348
+ if check_seq % 4 != 0:
349
+ ctx.mtid = TraceContextManager.getId()
350
+ elif rate == 6:
351
+ if check_seq % 3 != 0:
352
+ ctx.mtid = TraceContextManager.getId()
353
+ elif rate == 5:
354
+ if check_seq % 2 == 0:
355
+ ctx.mtid = TraceContextManager.getId()
356
+ elif rate == 4:
357
+ if check_seq % 3 == 0 or check_seq % 5 == 0:
358
+ ctx.mtid = TraceContextManager.getId()
359
+ elif rate == 3:
360
+ if check_seq % 4 == 0 or check_seq % 5 == 0:
361
+ ctx.mtid = TraceContextManager.getId()
362
+ elif rate == 2:
363
+ if check_seq % 5 == 0:
364
+ ctx.mtid = TraceContextManager.getId()
365
+ elif rate == 1:
366
+ if check_seq % 10 == 0:
367
+ ctx.mtid = TraceContextManager.getId()
368
+
369
+
370
+ def transfer(headers):
371
+ ctx = TraceContextManager.getLocalContext()
372
+
373
+ if not ctx.mtid:
374
+ inter_tx_trace_auto_on(ctx)
375
+
376
+ if ctx.mtid:
377
+ if not ctx.step_id:
378
+ seq = getattr(ctx, '_httpc_seq', 1)
379
+ ctx.step_id = seq
380
+ ctx._httpc_seq = seq + 1
381
+ headers[conf._trace_mtrace_caller_key] = ctx.transfer()
382
+ if conf.stat_mtrace_enabled:
383
+ headers[conf._trace_mtrace_info_key] = ctx.transferInfo()
384
+
385
+ ctx.mcallee = TraceContextManager.getId()
386
+ headers[conf._trace_mtrace_callee_key] = Hexa32.toString32(ctx.mcallee)
387
+ headers[conf._trace_mtrace_caller_poid_key] = ctx.transferPOID()
388
+ return headers