whatap-python 2.0.0rc1__tar.gz → 2.0.2__tar.gz
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_python-2.0.0rc1 → whatap_python-2.0.2}/PKG-INFO +5 -1
- {whatap_python-2.0.0rc1 → whatap_python-2.0.2}/setup.py +6 -0
- {whatap_python-2.0.0rc1 → whatap_python-2.0.2}/whatap/__init__.py +84 -28
- {whatap_python-2.0.0rc1 → whatap_python-2.0.2}/whatap/agent/darwin/amd64/whatap_python +0 -0
- {whatap_python-2.0.0rc1 → whatap_python-2.0.2}/whatap/agent/darwin/arm64/whatap_python +0 -0
- whatap_python-2.0.2/whatap/agent/linux/amd64/whatap_python +0 -0
- {whatap_python-2.0.0rc1/whatap/agent/linux/amd64 → whatap_python-2.0.2/whatap/agent/linux/arm64}/whatap_python +0 -0
- {whatap_python-2.0.0rc1 → whatap_python-2.0.2}/whatap/agent/windows/whatap_python.exe +0 -0
- whatap_python-2.0.2/whatap/build.py +4 -0
- {whatap_python-2.0.0rc1 → whatap_python-2.0.2}/whatap/conf/configuration.py +2 -4
- {whatap_python-2.0.0rc1 → whatap_python-2.0.2}/whatap/counter/__init__.py +6 -1
- whatap_python-2.0.2/whatap/counter/tasks/__init__.py +3 -0
- whatap_python-2.0.2/whatap/counter/tasks/llm_log_sink_task.py +244 -0
- whatap_python-2.0.2/whatap/counter/tasks/llm_stat_task.py +77 -0
- whatap_python-2.0.2/whatap/llm/__init__.py +1 -0
- whatap_python-2.0.2/whatap/llm/definitions.py +43 -0
- whatap_python-2.0.2/whatap/llm/log_sink_packs/__init__.py +8 -0
- whatap_python-2.0.2/whatap/llm/log_sink_packs/llm_input_message.py +16 -0
- whatap_python-2.0.2/whatap/llm/log_sink_packs/llm_log_sink_pack.py +64 -0
- whatap_python-2.0.2/whatap/llm/log_sink_packs/llm_output_message.py +19 -0
- whatap_python-2.0.2/whatap/llm/log_sink_packs/llm_step_status.py +114 -0
- whatap_python-2.0.2/whatap/llm/log_sink_packs/llm_system_message.py +16 -0
- whatap_python-2.0.2/whatap/llm/log_sink_packs/llm_tool_calls.py +44 -0
- whatap_python-2.0.2/whatap/llm/log_sink_packs/llm_tool_results.py +16 -0
- whatap_python-2.0.2/whatap/llm/log_sink_packs/llm_tx_status.py +102 -0
- whatap_python-2.0.2/whatap/llm/pricing.py +236 -0
- whatap_python-2.0.2/whatap/llm/providers/anthropic/__init__.py +37 -0
- whatap_python-2.0.2/whatap/llm/providers/anthropic/messages/messages.py +67 -0
- whatap_python-2.0.2/whatap/llm/providers/anthropic/messages/messages_context.py +76 -0
- whatap_python-2.0.2/whatap/llm/providers/anthropic/messages/messages_extractor.py +123 -0
- whatap_python-2.0.2/whatap/llm/providers/interceptor.py +85 -0
- {whatap_python-2.0.0rc1/whatap/trace/mod/llm → whatap_python-2.0.2/whatap/llm/providers}/openai/__init__.py +33 -4
- whatap_python-2.0.2/whatap/llm/providers/openai/chat/chat.py +79 -0
- whatap_python-2.0.2/whatap/llm/providers/openai/chat/chat_context.py +78 -0
- whatap_python-2.0.2/whatap/llm/providers/openai/chat/chat_extractor.py +126 -0
- whatap_python-2.0.2/whatap/llm/providers/openai/completions/completions.py +67 -0
- whatap_python-2.0.2/whatap/llm/providers/openai/completions/completions_context.py +32 -0
- whatap_python-2.0.2/whatap/llm/providers/openai/completions/completions_extractor.py +61 -0
- whatap_python-2.0.0rc1/whatap/trace/mod/llm/openai/util.py → whatap_python-2.0.2/whatap/llm/providers/openai/content_parser.py +9 -1
- whatap_python-2.0.2/whatap/llm/providers/openai/embeddings/embeddings.py +54 -0
- whatap_python-2.0.2/whatap/llm/providers/openai/embeddings/embeddings_context.py +26 -0
- whatap_python-2.0.2/whatap/llm/providers/openai/embeddings/embeddings_extractor.py +26 -0
- whatap_python-2.0.2/whatap/llm/providers/openai/responses/responses.py +67 -0
- whatap_python-2.0.2/whatap/llm/providers/openai/responses/responses_context.py +88 -0
- whatap_python-2.0.2/whatap/llm/providers/openai/responses/responses_extractor.py +125 -0
- whatap_python-2.0.2/whatap/llm/providers/stream_accumulator.py +73 -0
- whatap_python-2.0.2/whatap/llm/stats/__init__.py +15 -0
- whatap_python-2.0.0rc1/whatap/counter/tasks/llm_active_stat.py → whatap_python-2.0.2/whatap/llm/stats/active_stat.py +21 -20
- whatap_python-2.0.2/whatap/llm/stats/api_status_stat.py +28 -0
- whatap_python-2.0.0rc1/whatap/counter/tasks/llm_api_status_stat.py → whatap_python-2.0.2/whatap/llm/stats/base_stat.py +20 -24
- whatap_python-2.0.2/whatap/llm/stats/error_stat.py +56 -0
- whatap_python-2.0.2/whatap/llm/stats/feature_stat.py +63 -0
- whatap_python-2.0.2/whatap/llm/stats/meter.py +18 -0
- whatap_python-2.0.2/whatap/llm/stats/perf_stat.py +105 -0
- whatap_python-2.0.2/whatap/llm/stats/token_usage_stat.py +104 -0
- {whatap_python-2.0.0rc1 → whatap_python-2.0.2}/whatap/net/async_sender.py +19 -2
- {whatap_python-2.0.0rc1 → whatap_python-2.0.2}/whatap/net/udp_session.py +60 -16
- {whatap_python-2.0.0rc1 → whatap_python-2.0.2}/whatap/net/udp_thread.py +2 -4
- {whatap_python-2.0.0rc1 → whatap_python-2.0.2}/whatap/scripts/__init__.py +6 -6
- whatap_python-2.0.2/whatap/trace/mod/application/fastapi/__init__.py +31 -0
- whatap_python-2.0.2/whatap/trace/mod/application/fastapi/endpoint.py +73 -0
- whatap_python-2.0.2/whatap/trace/mod/application/fastapi/exception_log.py +63 -0
- whatap_python-2.0.2/whatap/trace/mod/application/fastapi/instrumentation.py +204 -0
- whatap_python-2.0.2/whatap/trace/mod/application/fastapi/scope.py +115 -0
- whatap_python-2.0.2/whatap/trace/mod/application/fastapi/transaction.py +67 -0
- {whatap_python-2.0.0rc1 → whatap_python-2.0.2}/whatap/trace/mod/application/wsgi.py +4 -1
- {whatap_python-2.0.0rc1 → whatap_python-2.0.2}/whatap/trace/mod/database/util.py +32 -14
- whatap_python-2.0.2/whatap/trace/mod/email/__init__.py +0 -0
- whatap_python-2.0.2/whatap/trace/mod/httpc/__init__.py +0 -0
- {whatap_python-2.0.0rc1 → whatap_python-2.0.2}/whatap/trace/mod/httpc/util.py +70 -33
- whatap_python-2.0.2/whatap/trace/mod/standalone/__init__.py +0 -0
- {whatap_python-2.0.0rc1 → whatap_python-2.0.2}/whatap/trace/mod/standalone/multiple.py +4 -1
- {whatap_python-2.0.0rc1 → whatap_python-2.0.2}/whatap/trace/mod/standalone/single.py +4 -1
- {whatap_python-2.0.0rc1 → whatap_python-2.0.2}/whatap/trace/trace_context.py +1 -0
- {whatap_python-2.0.0rc1 → whatap_python-2.0.2}/whatap/trace/trace_context_manager.py +40 -1
- {whatap_python-2.0.0rc1 → whatap_python-2.0.2}/whatap/trace/trace_handler.py +0 -5
- {whatap_python-2.0.0rc1 → whatap_python-2.0.2}/whatap/trace/trace_module_definition.py +2 -2
- whatap_python-2.0.2/whatap/util/__init__.py +0 -0
- whatap_python-2.0.2/whatap/util/cardinality/__init__.py +0 -0
- {whatap_python-2.0.0rc1 → whatap_python-2.0.2}/whatap_python.egg-info/PKG-INFO +5 -1
- {whatap_python-2.0.0rc1 → whatap_python-2.0.2}/whatap_python.egg-info/SOURCES.txt +55 -17
- {whatap_python-2.0.0rc1 → whatap_python-2.0.2}/whatap_python.egg-info/requires.txt +4 -0
- whatap_python-2.0.0rc1/whatap/agent/linux/arm64/whatap_python +0 -0
- whatap_python-2.0.0rc1/whatap/build.py +0 -4
- whatap_python-2.0.0rc1/whatap/counter/tasks/__init__.py +0 -17
- whatap_python-2.0.0rc1/whatap/counter/tasks/llm_error_stat.py +0 -88
- whatap_python-2.0.0rc1/whatap/counter/tasks/llm_feature_stat.py +0 -91
- whatap_python-2.0.0rc1/whatap/counter/tasks/llm_meter.py +0 -57
- whatap_python-2.0.0rc1/whatap/counter/tasks/llm_perf_stat.py +0 -132
- whatap_python-2.0.0rc1/whatap/counter/tasks/llm_token_usage_stat.py +0 -122
- whatap_python-2.0.0rc1/whatap/trace/mod/application/fastapi.py +0 -476
- whatap_python-2.0.0rc1/whatap/trace/mod/llm/anthropic.py +0 -488
- whatap_python-2.0.0rc1/whatap/trace/mod/llm/llm_log_sink_pack.py +0 -316
- whatap_python-2.0.0rc1/whatap/trace/mod/llm/llm_sender.py +0 -527
- whatap_python-2.0.0rc1/whatap/trace/mod/llm/openai/chat.py +0 -356
- whatap_python-2.0.0rc1/whatap/trace/mod/llm/openai/embeddings.py +0 -178
- whatap_python-2.0.0rc1/whatap/trace/mod/llm/openai/responses.py +0 -409
- {whatap_python-2.0.0rc1 → whatap_python-2.0.2}/README.md +0 -0
- {whatap_python-2.0.0rc1 → whatap_python-2.0.2}/pyproject.toml +0 -0
- {whatap_python-2.0.0rc1 → whatap_python-2.0.2}/setup.cfg +0 -0
- {whatap_python-2.0.0rc1 → whatap_python-2.0.2}/whatap/LICENSE +0 -0
- {whatap_python-2.0.0rc1 → whatap_python-2.0.2}/whatap/README.rst +0 -0
- {whatap_python-2.0.0rc1 → whatap_python-2.0.2}/whatap/__main__.py +0 -0
- {whatap_python-2.0.0rc1 → whatap_python-2.0.2}/whatap/bootstrap/__init__.py +0 -0
- {whatap_python-2.0.0rc1 → whatap_python-2.0.2}/whatap/bootstrap/sitecustomize.py +0 -0
- {whatap_python-2.0.0rc1 → whatap_python-2.0.2}/whatap/conf/__init__.py +0 -0
- {whatap_python-2.0.0rc1 → whatap_python-2.0.2}/whatap/conf/configure.py +0 -0
- {whatap_python-2.0.0rc1 → whatap_python-2.0.2}/whatap/conf/license.py +0 -0
- {whatap_python-2.0.0rc1 → whatap_python-2.0.2}/whatap/control/__init__.py +0 -0
- {whatap_python-2.0.0rc1 → whatap_python-2.0.2}/whatap/counter/counter_manager.py +0 -0
- {whatap_python-2.0.0rc1 → whatap_python-2.0.2}/whatap/counter/tasks/base_task.py +0 -0
- {whatap_python-2.0.0rc1 → whatap_python-2.0.2}/whatap/counter/tasks/openfiledescriptor.py +0 -0
- {whatap_python-2.0.0rc1 → whatap_python-2.0.2}/whatap/io/__init__.py +0 -0
- {whatap_python-2.0.0rc1 → whatap_python-2.0.2}/whatap/io/data_inputx.py +0 -0
- {whatap_python-2.0.0rc1 → whatap_python-2.0.2}/whatap/io/data_outputx.py +0 -0
- {whatap_python-2.0.0rc1/whatap/net → whatap_python-2.0.2/whatap/llm/providers}/__init__.py +0 -0
- {whatap_python-2.0.0rc1/whatap/pack → whatap_python-2.0.2/whatap/llm/providers/anthropic/messages}/__init__.py +0 -0
- {whatap_python-2.0.0rc1/whatap/trace/mod → whatap_python-2.0.2/whatap/llm/providers/openai/chat}/__init__.py +0 -0
- {whatap_python-2.0.0rc1/whatap/trace/mod/amqp → whatap_python-2.0.2/whatap/llm/providers/openai/completions}/__init__.py +0 -0
- {whatap_python-2.0.0rc1/whatap/trace/mod/application → whatap_python-2.0.2/whatap/llm/providers/openai/embeddings}/__init__.py +0 -0
- {whatap_python-2.0.0rc1/whatap/trace/mod/database → whatap_python-2.0.2/whatap/llm/providers/openai/responses}/__init__.py +0 -0
- {whatap_python-2.0.0rc1/whatap/trace/mod/email → whatap_python-2.0.2/whatap/net}/__init__.py +0 -0
- {whatap_python-2.0.0rc1 → whatap_python-2.0.2}/whatap/net/packet_enum.py +0 -0
- {whatap_python-2.0.0rc1 → whatap_python-2.0.2}/whatap/net/packet_type_enum.py +0 -0
- {whatap_python-2.0.0rc1 → whatap_python-2.0.2}/whatap/net/param_def.py +0 -0
- {whatap_python-2.0.0rc1 → whatap_python-2.0.2}/whatap/net/stackhelper.py +0 -0
- {whatap_python-2.0.0rc1/whatap/trace/mod/httpc → whatap_python-2.0.2/whatap/pack}/__init__.py +0 -0
- {whatap_python-2.0.0rc1 → whatap_python-2.0.2}/whatap/pack/logSinkPack.py +0 -0
- {whatap_python-2.0.0rc1 → whatap_python-2.0.2}/whatap/pack/pack.py +0 -0
- {whatap_python-2.0.0rc1 → whatap_python-2.0.2}/whatap/pack/pack_enum.py +0 -0
- {whatap_python-2.0.0rc1 → whatap_python-2.0.2}/whatap/pack/tagCountPack.py +0 -0
- {whatap_python-2.0.0rc1 → whatap_python-2.0.2}/whatap/trace/__init__.py +0 -0
- {whatap_python-2.0.0rc1/whatap/trace/mod/llm → whatap_python-2.0.2/whatap/trace/mod}/__init__.py +0 -0
- {whatap_python-2.0.0rc1/whatap/trace/mod/standalone → whatap_python-2.0.2/whatap/trace/mod/amqp}/__init__.py +0 -0
- {whatap_python-2.0.0rc1 → whatap_python-2.0.2}/whatap/trace/mod/amqp/kombu.py +0 -0
- {whatap_python-2.0.0rc1 → whatap_python-2.0.2}/whatap/trace/mod/amqp/pika.py +0 -0
- {whatap_python-2.0.0rc1/whatap/util → whatap_python-2.0.2/whatap/trace/mod/application}/__init__.py +0 -0
- {whatap_python-2.0.0rc1 → whatap_python-2.0.2}/whatap/trace/mod/application/bottle.py +0 -0
- {whatap_python-2.0.0rc1 → whatap_python-2.0.2}/whatap/trace/mod/application/celery.py +0 -0
- {whatap_python-2.0.0rc1 → whatap_python-2.0.2}/whatap/trace/mod/application/cherrypy.py +0 -0
- {whatap_python-2.0.0rc1 → whatap_python-2.0.2}/whatap/trace/mod/application/django.py +0 -0
- {whatap_python-2.0.0rc1 → whatap_python-2.0.2}/whatap/trace/mod/application/django_asgi.py +0 -0
- {whatap_python-2.0.0rc1 → whatap_python-2.0.2}/whatap/trace/mod/application/django_py3.py +0 -0
- {whatap_python-2.0.0rc1 → whatap_python-2.0.2}/whatap/trace/mod/application/flask.py +0 -0
- {whatap_python-2.0.0rc1 → whatap_python-2.0.2}/whatap/trace/mod/application/frappe.py +0 -0
- {whatap_python-2.0.0rc1 → whatap_python-2.0.2}/whatap/trace/mod/application/graphql.py +0 -0
- {whatap_python-2.0.0rc1 → whatap_python-2.0.2}/whatap/trace/mod/application/nameko.py +0 -0
- {whatap_python-2.0.0rc1 → whatap_python-2.0.2}/whatap/trace/mod/application/odoo.py +0 -0
- {whatap_python-2.0.0rc1 → whatap_python-2.0.2}/whatap/trace/mod/application/starlette.py +0 -0
- {whatap_python-2.0.0rc1 → whatap_python-2.0.2}/whatap/trace/mod/application/tornado.py +0 -0
- {whatap_python-2.0.0rc1/whatap/util/cardinality → whatap_python-2.0.2/whatap/trace/mod/database}/__init__.py +0 -0
- {whatap_python-2.0.0rc1 → whatap_python-2.0.2}/whatap/trace/mod/database/cxoracle.py +0 -0
- {whatap_python-2.0.0rc1 → whatap_python-2.0.2}/whatap/trace/mod/database/mongo.py +0 -0
- {whatap_python-2.0.0rc1 → whatap_python-2.0.2}/whatap/trace/mod/database/mysql.py +0 -0
- {whatap_python-2.0.0rc1 → whatap_python-2.0.2}/whatap/trace/mod/database/neo4j.py +0 -0
- {whatap_python-2.0.0rc1 → whatap_python-2.0.2}/whatap/trace/mod/database/psycopg2.py +0 -0
- {whatap_python-2.0.0rc1 → whatap_python-2.0.2}/whatap/trace/mod/database/psycopg3.py +0 -0
- {whatap_python-2.0.0rc1 → whatap_python-2.0.2}/whatap/trace/mod/database/redis.py +0 -0
- {whatap_python-2.0.0rc1 → whatap_python-2.0.2}/whatap/trace/mod/database/sqlalchemy.py +0 -0
- {whatap_python-2.0.0rc1 → whatap_python-2.0.2}/whatap/trace/mod/database/sqlite3.py +0 -0
- {whatap_python-2.0.0rc1 → whatap_python-2.0.2}/whatap/trace/mod/email/smtp.py +0 -0
- {whatap_python-2.0.0rc1 → whatap_python-2.0.2}/whatap/trace/mod/httpc/django.py +0 -0
- {whatap_python-2.0.0rc1 → whatap_python-2.0.2}/whatap/trace/mod/httpc/httplib.py +0 -0
- {whatap_python-2.0.0rc1 → whatap_python-2.0.2}/whatap/trace/mod/httpc/httpx.py +0 -0
- {whatap_python-2.0.0rc1 → whatap_python-2.0.2}/whatap/trace/mod/httpc/requests.py +0 -0
- {whatap_python-2.0.0rc1 → whatap_python-2.0.2}/whatap/trace/mod/httpc/urllib3.py +0 -0
- {whatap_python-2.0.0rc1 → whatap_python-2.0.2}/whatap/trace/mod/logging.py +0 -0
- {whatap_python-2.0.0rc1 → whatap_python-2.0.2}/whatap/trace/mod/plugin.py +0 -0
- {whatap_python-2.0.0rc1 → whatap_python-2.0.2}/whatap/trace/simple_trace_context.py +0 -0
- {whatap_python-2.0.0rc1 → whatap_python-2.0.2}/whatap/trace/trace_error.py +0 -0
- {whatap_python-2.0.0rc1 → whatap_python-2.0.2}/whatap/trace/trace_import.py +0 -0
- {whatap_python-2.0.0rc1 → whatap_python-2.0.2}/whatap/util/bit_util.py +0 -0
- {whatap_python-2.0.0rc1 → whatap_python-2.0.2}/whatap/util/cardinality/hyperloglog.py +0 -0
- {whatap_python-2.0.0rc1 → whatap_python-2.0.2}/whatap/util/cardinality/murmurhash.py +0 -0
- {whatap_python-2.0.0rc1 → whatap_python-2.0.2}/whatap/util/cardinality/registerset.py +0 -0
- {whatap_python-2.0.0rc1 → whatap_python-2.0.2}/whatap/util/compare_util.py +0 -0
- {whatap_python-2.0.0rc1 → whatap_python-2.0.2}/whatap/util/date_util.py +0 -0
- {whatap_python-2.0.0rc1 → whatap_python-2.0.2}/whatap/util/debug_util.py +0 -0
- {whatap_python-2.0.0rc1 → whatap_python-2.0.2}/whatap/util/escape_literal_sql.py +0 -0
- {whatap_python-2.0.0rc1 → whatap_python-2.0.2}/whatap/util/frame_util.py +0 -0
- {whatap_python-2.0.0rc1 → whatap_python-2.0.2}/whatap/util/hash_util.py +0 -0
- {whatap_python-2.0.0rc1 → whatap_python-2.0.2}/whatap/util/hexa32.py +0 -0
- {whatap_python-2.0.0rc1 → whatap_python-2.0.2}/whatap/util/int_set.py +0 -0
- {whatap_python-2.0.0rc1 → whatap_python-2.0.2}/whatap/util/ip_util.py +0 -0
- {whatap_python-2.0.0rc1 → whatap_python-2.0.2}/whatap/util/keygen.py +0 -0
- {whatap_python-2.0.0rc1 → whatap_python-2.0.2}/whatap/util/linked_list.py +0 -0
- {whatap_python-2.0.0rc1 → whatap_python-2.0.2}/whatap/util/linked_map.py +0 -0
- {whatap_python-2.0.0rc1 → whatap_python-2.0.2}/whatap/util/metering_util.py +0 -0
- {whatap_python-2.0.0rc1 → whatap_python-2.0.2}/whatap/util/request_double_queue.py +0 -0
- {whatap_python-2.0.0rc1 → whatap_python-2.0.2}/whatap/util/request_queue.py +0 -0
- {whatap_python-2.0.0rc1 → whatap_python-2.0.2}/whatap/util/string_util.py +0 -0
- {whatap_python-2.0.0rc1 → whatap_python-2.0.2}/whatap/util/throttle_util.py +0 -0
- {whatap_python-2.0.0rc1 → whatap_python-2.0.2}/whatap/util/userid_util.py +0 -0
- {whatap_python-2.0.0rc1 → whatap_python-2.0.2}/whatap/value/__init__.py +0 -0
- {whatap_python-2.0.0rc1 → whatap_python-2.0.2}/whatap/value/blob_value.py +0 -0
- {whatap_python-2.0.0rc1 → whatap_python-2.0.2}/whatap/value/boolean_value.py +0 -0
- {whatap_python-2.0.0rc1 → whatap_python-2.0.2}/whatap/value/decimal_value.py +0 -0
- {whatap_python-2.0.0rc1 → whatap_python-2.0.2}/whatap/value/double_summary.py +0 -0
- {whatap_python-2.0.0rc1 → whatap_python-2.0.2}/whatap/value/double_value.py +0 -0
- {whatap_python-2.0.0rc1 → whatap_python-2.0.2}/whatap/value/float_array.py +0 -0
- {whatap_python-2.0.0rc1 → whatap_python-2.0.2}/whatap/value/float_value.py +0 -0
- {whatap_python-2.0.0rc1 → whatap_python-2.0.2}/whatap/value/int_array.py +0 -0
- {whatap_python-2.0.0rc1 → whatap_python-2.0.2}/whatap/value/ip4_value.py +0 -0
- {whatap_python-2.0.0rc1 → whatap_python-2.0.2}/whatap/value/list_value.py +0 -0
- {whatap_python-2.0.0rc1 → whatap_python-2.0.2}/whatap/value/long_array.py +0 -0
- {whatap_python-2.0.0rc1 → whatap_python-2.0.2}/whatap/value/long_summary.py +0 -0
- {whatap_python-2.0.0rc1 → whatap_python-2.0.2}/whatap/value/map_value.py +0 -0
- {whatap_python-2.0.0rc1 → whatap_python-2.0.2}/whatap/value/null_value.py +0 -0
- {whatap_python-2.0.0rc1 → whatap_python-2.0.2}/whatap/value/number_value.py +0 -0
- {whatap_python-2.0.0rc1 → whatap_python-2.0.2}/whatap/value/summary_value.py +0 -0
- {whatap_python-2.0.0rc1 → whatap_python-2.0.2}/whatap/value/text_array.py +0 -0
- {whatap_python-2.0.0rc1 → whatap_python-2.0.2}/whatap/value/text_hash_value.py +0 -0
- {whatap_python-2.0.0rc1 → whatap_python-2.0.2}/whatap/value/text_value.py +0 -0
- {whatap_python-2.0.0rc1 → whatap_python-2.0.2}/whatap/value/value.py +0 -0
- {whatap_python-2.0.0rc1 → whatap_python-2.0.2}/whatap/value/value_enum.py +0 -0
- {whatap_python-2.0.0rc1 → whatap_python-2.0.2}/whatap/whatap.conf +0 -0
- {whatap_python-2.0.0rc1 → whatap_python-2.0.2}/whatap_python.egg-info/dependency_links.txt +0 -0
- {whatap_python-2.0.0rc1 → whatap_python-2.0.2}/whatap_python.egg-info/entry_points.txt +0 -0
- {whatap_python-2.0.0rc1 → whatap_python-2.0.2}/whatap_python.egg-info/not-zip-safe +0 -0
- {whatap_python-2.0.0rc1 → whatap_python-2.0.2}/whatap_python.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: whatap-python
|
|
3
|
-
Version: 2.0.
|
|
3
|
+
Version: 2.0.2
|
|
4
4
|
Summary: Monitoring and Profiling Service
|
|
5
5
|
Home-page: https://www.whatap.io
|
|
6
6
|
Author: whatap
|
|
@@ -22,12 +22,16 @@ Classifier: Programming Language :: Python :: 3.12
|
|
|
22
22
|
Classifier: Programming Language :: Python :: 3.13
|
|
23
23
|
Requires-Python: >=3.7
|
|
24
24
|
Requires-Dist: psutil>=5.0.0; platform_system == "Windows"
|
|
25
|
+
Provides-Extra: llm
|
|
26
|
+
Requires-Dist: datasketches>=5.2.0; extra == "llm"
|
|
27
|
+
Requires-Dist: genai-prices; extra == "llm"
|
|
25
28
|
Dynamic: author
|
|
26
29
|
Dynamic: author-email
|
|
27
30
|
Dynamic: classifier
|
|
28
31
|
Dynamic: description
|
|
29
32
|
Dynamic: home-page
|
|
30
33
|
Dynamic: license
|
|
34
|
+
Dynamic: provides-extra
|
|
31
35
|
Dynamic: requires-dist
|
|
32
36
|
Dynamic: requires-python
|
|
33
37
|
Dynamic: summary
|
|
@@ -35,6 +35,12 @@ setup(name=build.name,
|
|
|
35
35
|
install_requires=[
|
|
36
36
|
'psutil>=5.0.0; platform_system=="Windows"', # Required for Windows compatibility (memory/process monitoring)
|
|
37
37
|
],
|
|
38
|
+
extras_require={
|
|
39
|
+
'llm': [
|
|
40
|
+
'datasketches>=5.2.0',
|
|
41
|
+
'genai-prices',
|
|
42
|
+
],
|
|
43
|
+
},
|
|
38
44
|
classifiers=[
|
|
39
45
|
'Development Status :: 4 - Beta',
|
|
40
46
|
'Intended Audience :: Developers',
|
|
@@ -59,7 +59,7 @@ def preview_whatap_conf(option_name:str):
|
|
|
59
59
|
- standalone_enabled (False)
|
|
60
60
|
- counter_thread_enabled (False)
|
|
61
61
|
"""
|
|
62
|
-
value =
|
|
62
|
+
value = None
|
|
63
63
|
try:
|
|
64
64
|
with open(whatap_config) as f:
|
|
65
65
|
for raw in f:
|
|
@@ -71,15 +71,22 @@ def preview_whatap_conf(option_name:str):
|
|
|
71
71
|
if len(parts) == 2:
|
|
72
72
|
value = parts[1].strip()
|
|
73
73
|
break
|
|
74
|
-
return value
|
|
75
74
|
|
|
76
75
|
except FileNotFoundError:
|
|
77
|
-
|
|
76
|
+
pass
|
|
78
77
|
|
|
79
78
|
except Exception as e:
|
|
80
79
|
print(f'WHATAP: config parse error ({e!r})')
|
|
80
|
+
|
|
81
|
+
if value is not None:
|
|
81
82
|
return value
|
|
82
83
|
|
|
84
|
+
env_value = os.environ.get(option_name)
|
|
85
|
+
if env_value is not None:
|
|
86
|
+
return env_value
|
|
87
|
+
|
|
88
|
+
return 'false'
|
|
89
|
+
|
|
83
90
|
|
|
84
91
|
|
|
85
92
|
ignore_whatap_stdout = preview_whatap_conf("ignore_whatap_stdout")
|
|
@@ -298,15 +305,15 @@ def hooks(home):
|
|
|
298
305
|
try:
|
|
299
306
|
for key, value_list in DEFINITION.items():
|
|
300
307
|
for value in value_list:
|
|
301
|
-
if len(value)
|
|
308
|
+
if len(value) >= 3 and isinstance(value[2], str):
|
|
309
|
+
module_path = value[2]
|
|
310
|
+
elif len(value) == 3 and not value[2]:
|
|
302
311
|
continue
|
|
303
|
-
|
|
312
|
+
else:
|
|
313
|
+
module_path = '{0}.{1}.{2}.{3}'.format(
|
|
314
|
+
'whatap', 'trace', 'mod', key)
|
|
304
315
|
IMPORT_HOOKS[value[0]] = {'def': value[1],
|
|
305
|
-
'module':
|
|
306
|
-
'whatap',
|
|
307
|
-
'trace',
|
|
308
|
-
'mod',
|
|
309
|
-
key)}
|
|
316
|
+
'module': module_path}
|
|
310
317
|
except Exception as e:
|
|
311
318
|
logging.debug(e, extra={'id': 'MODULE ERROR'})
|
|
312
319
|
finally:
|
|
@@ -418,15 +425,6 @@ def go(batch=False, opts={}, llm=False):
|
|
|
418
425
|
if llm:
|
|
419
426
|
home = 'WHATAP_HOME'
|
|
420
427
|
file_name = AGENT_NAME + '.pid.llm'
|
|
421
|
-
# LLM Go Agent용 별도 UDP 포트
|
|
422
|
-
from whatap.conf.configure import Configure as conf
|
|
423
|
-
llm_port = int(getattr(conf, 'llm_net_udp_port', 0))
|
|
424
|
-
if not llm_port:
|
|
425
|
-
llm_port = int(conf.net_udp_port) + 100
|
|
426
|
-
conf.llm_net_udp_port = llm_port
|
|
427
|
-
newenv['WHATAP_NET_UDP_PORT'] = str(llm_port)
|
|
428
|
-
# whatap.conf에 기록하여 Go Agent가 읽을 수 있도록 함
|
|
429
|
-
update_config(home, 'llm_net_udp_port', str(llm_port))
|
|
430
428
|
elif not batch:
|
|
431
429
|
home = 'WHATAP_HOME'
|
|
432
430
|
file_name = AGENT_NAME + '.pid'
|
|
@@ -537,12 +535,47 @@ def go(batch=False, opts={}, llm=False):
|
|
|
537
535
|
label = 'LLM golang module' if llm else 'golang module'
|
|
538
536
|
|
|
539
537
|
if sys.platform == 'win32':
|
|
540
|
-
|
|
538
|
+
# SESSIONNAME 환경변수는 NSSM이 부모 세션에서 상속받아 부정확함
|
|
539
|
+
# ProcessIdToSessionId로 실제 프로세스 세션 ID 확인
|
|
540
|
+
try:
|
|
541
|
+
import ctypes
|
|
542
|
+
_sid = ctypes.c_ulong(0)
|
|
543
|
+
ctypes.windll.kernel32.ProcessIdToSessionId(
|
|
544
|
+
ctypes.windll.kernel32.GetCurrentProcessId(),
|
|
545
|
+
ctypes.byref(_sid)
|
|
546
|
+
)
|
|
547
|
+
is_session0 = (_sid.value == 0)
|
|
548
|
+
except Exception:
|
|
549
|
+
is_session0 = not os.environ.get('SESSIONNAME')
|
|
550
|
+
|
|
541
551
|
cmd_args.append('foreground')
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
552
|
+
|
|
553
|
+
if is_session0:
|
|
554
|
+
# Session 0에서만 WHATAP_FOREGROUND=1 추가 주입.
|
|
555
|
+
# IsAnInteractiveSession()이 false로 떨어지는 경로에서
|
|
556
|
+
# foreground CLI 인자를 보강하는 안전망.
|
|
557
|
+
newenv['WHATAP_FOREGROUND'] = '1'
|
|
558
|
+
|
|
559
|
+
# Session 0에서는 PIPE를 읽지 않으면 버퍼 고갈로 Go 프로세스가 블로킹됨
|
|
560
|
+
# 로그 파일로 리디렉션
|
|
561
|
+
# CREATE_NO_WINDOW: Session 0 서비스 컨텍스트에 적합한 플래그
|
|
562
|
+
CREATE_NO_WINDOW = 0x08000000
|
|
563
|
+
log_dir = os.path.join(home_path, 'logs')
|
|
564
|
+
go_log_path = os.path.join(log_dir, AGENT_NAME + '.log')
|
|
565
|
+
try:
|
|
566
|
+
go_out = open(go_log_path, 'ab')
|
|
567
|
+
except Exception:
|
|
568
|
+
go_out = subprocess.DEVNULL
|
|
569
|
+
process = subprocess.Popen(cmd_args,
|
|
570
|
+
cwd=home_path, env=newenv,
|
|
571
|
+
creationflags=CREATE_NO_WINDOW,
|
|
572
|
+
stdout=go_out, stderr=go_out)
|
|
573
|
+
else:
|
|
574
|
+
DETACHED_PROCESS = 0x00000008
|
|
575
|
+
process = subprocess.Popen(cmd_args,
|
|
576
|
+
cwd=home_path, env=newenv,
|
|
577
|
+
creationflags=DETACHED_PROCESS,
|
|
578
|
+
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
|
546
579
|
else:
|
|
547
580
|
process = subprocess.Popen(cmd_args,
|
|
548
581
|
cwd=home_path, env=newenv,
|
|
@@ -551,8 +584,11 @@ def go(batch=False, opts={}, llm=False):
|
|
|
551
584
|
|
|
552
585
|
time.sleep(0.5)
|
|
553
586
|
if process.poll() is not None:
|
|
554
|
-
|
|
555
|
-
|
|
587
|
+
if sys.platform == 'win32' and is_session0:
|
|
588
|
+
whatap_print("executed {} (exit code: {}, log: {})".format(label, process.returncode, go_log_path))
|
|
589
|
+
else:
|
|
590
|
+
stdouts, errs = process.communicate()
|
|
591
|
+
whatap_print("executed {} ".format(label), str(stdouts,"utf8"), str(errs, "utf8"))
|
|
556
592
|
else:
|
|
557
593
|
write_file(home, file_name, str(process.pid))
|
|
558
594
|
whatap_print("executed {} in background (PID: {})".format(label, process.pid))
|
|
@@ -690,8 +726,10 @@ except ImportError:
|
|
|
690
726
|
if sys.platform == 'win32':
|
|
691
727
|
import tempfile
|
|
692
728
|
default_lock_file = os.path.join(tempfile.gettempdir(), 'whatap-python.lock')
|
|
729
|
+
default_llm_lock_file = os.path.join(tempfile.gettempdir(), 'whatap-python-llm.lock')
|
|
693
730
|
else:
|
|
694
731
|
default_lock_file = '/tmp/whatap-python.lock'
|
|
732
|
+
default_llm_lock_file = '/tmp/whatap-python-llm.lock'
|
|
695
733
|
|
|
696
734
|
def openPortFile(filepath=os.environ.get('WHATAP_LOCK_FILE', default_lock_file)):
|
|
697
735
|
f = None
|
|
@@ -732,12 +770,12 @@ def openPortFile(filepath=os.environ.get('WHATAP_LOCK_FILE', default_lock_file))
|
|
|
732
770
|
whatap_print('WHATAP: file close failed: %s' % e)
|
|
733
771
|
return None
|
|
734
772
|
|
|
735
|
-
def get_port_number(port=6600, home=os.environ.get('WHATAP_HOME')):
|
|
773
|
+
def get_port_number(port=6600, home=os.environ.get('WHATAP_HOME'), lock_file=None):
|
|
736
774
|
if not home:
|
|
737
775
|
return None
|
|
738
776
|
|
|
739
777
|
for i in range(100):
|
|
740
|
-
f = openPortFile()
|
|
778
|
+
f = openPortFile(filepath=lock_file) if lock_file else openPortFile()
|
|
741
779
|
if not f:
|
|
742
780
|
if i > 50:
|
|
743
781
|
time.sleep(0.1)
|
|
@@ -798,6 +836,24 @@ def configPort():
|
|
|
798
836
|
return port
|
|
799
837
|
|
|
800
838
|
|
|
839
|
+
def configLlmPort():
|
|
840
|
+
# force_llm_net_udp_port가 설정된 경우 lock 파일 무시하고 해당 포트 강제 사용
|
|
841
|
+
force_port_str = preview_whatap_conf("force_llm_net_udp_port")
|
|
842
|
+
if force_port_str and force_port_str not in ('false', 'False'):
|
|
843
|
+
try:
|
|
844
|
+
port = int(force_port_str)
|
|
845
|
+
except ValueError:
|
|
846
|
+
whatap_print('WHATAP: force_llm_net_udp_port value is invalid: {}'.format(force_port_str))
|
|
847
|
+
port = None
|
|
848
|
+
else:
|
|
849
|
+
llm_lock_file = os.environ.get('WHATAP_LLM_LOCK_FILE', default_llm_lock_file)
|
|
850
|
+
port = get_port_number(port=6700, home=os.environ.get('WHATAP_HOME'), lock_file=llm_lock_file)
|
|
851
|
+
|
|
852
|
+
if port:
|
|
853
|
+
update_config('WHATAP_HOME', 'llm_net_udp_port', str(port))
|
|
854
|
+
return port
|
|
855
|
+
|
|
856
|
+
|
|
801
857
|
def find_whatap_conf():
|
|
802
858
|
# 1. 현재 디렉토리 검색
|
|
803
859
|
script_dir = os.path.dirname(os.path.abspath(__file__))
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -19,7 +19,6 @@ Configuration = {
|
|
|
19
19
|
"trace_ignore_url_prefix": None,
|
|
20
20
|
"trace_websocket_enabled": False,
|
|
21
21
|
|
|
22
|
-
"trace_llm_log_enabled": False,
|
|
23
22
|
"llm_api_hosts": "",
|
|
24
23
|
|
|
25
24
|
"debug": False,
|
|
@@ -222,9 +221,8 @@ Configuration = {
|
|
|
222
221
|
"open_file_descriptor_enabled": False,
|
|
223
222
|
"open_file_descriptor_interval":60,
|
|
224
223
|
"counter_thread_enabled": False,
|
|
225
|
-
"llm_net_udp_port":
|
|
226
|
-
"
|
|
227
|
-
"llm_token_usage_stat_interval" : 5,
|
|
224
|
+
"llm_net_udp_port": 6700,
|
|
225
|
+
"force_llm_net_udp_port": False,
|
|
228
226
|
"llm_model_pricing": "",
|
|
229
227
|
"llm_perf_sketch_enabled": True,
|
|
230
228
|
"llm_perf_sketch_k": 200
|
|
@@ -6,4 +6,9 @@ counter_thread_enabled = preview_whatap_conf("counter_thread_enabled")
|
|
|
6
6
|
if counter_thread_enabled != 'false':
|
|
7
7
|
mgr = CounterMgr()
|
|
8
8
|
mgr.setDaemon(True)
|
|
9
|
-
mgr.start()
|
|
9
|
+
mgr.start()
|
|
10
|
+
|
|
11
|
+
from .tasks.llm_stat_task import LlmStatTask
|
|
12
|
+
llm_stat = LlmStatTask()
|
|
13
|
+
llm_stat.setDaemon(True)
|
|
14
|
+
llm_stat.start()
|
|
@@ -0,0 +1,244 @@
|
|
|
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
|
+
_instance = None
|
|
27
|
+
_lock = threading.Lock()
|
|
28
|
+
|
|
29
|
+
def __init__(self):
|
|
30
|
+
self._q = queue.Queue(4096)
|
|
31
|
+
self._started = False
|
|
32
|
+
|
|
33
|
+
@classmethod
|
|
34
|
+
def get_instance(cls):
|
|
35
|
+
if cls._instance is None:
|
|
36
|
+
with cls._lock:
|
|
37
|
+
if cls._instance is None:
|
|
38
|
+
cls._instance = cls()
|
|
39
|
+
return cls._instance
|
|
40
|
+
|
|
41
|
+
def dispatch(self, pack):
|
|
42
|
+
if not isinstance(pack, LlmStepStatus):
|
|
43
|
+
return
|
|
44
|
+
|
|
45
|
+
ctx = getattr(pack, '_trace_ctx', None) or TraceContextManager.getLocalContext()
|
|
46
|
+
pack.set_context(ctx)
|
|
47
|
+
|
|
48
|
+
if not pack.operation_type and ctx:
|
|
49
|
+
pack.operation_type = getattr(ctx, '_llm_operation_type', '') or 'unknown'
|
|
50
|
+
|
|
51
|
+
try:
|
|
52
|
+
pack.calculate_cost()
|
|
53
|
+
except Exception as e:
|
|
54
|
+
logging.warning('[LLM] calculate_cost failed: model=%s, input_tokens=%s, output_tokens=%s, cached_tokens=%s, error=%s'
|
|
55
|
+
% (pack.model, pack.input_tokens, pack.output_tokens, pack.cached_tokens, e),
|
|
56
|
+
extra={'id': 'LLM024'})
|
|
57
|
+
|
|
58
|
+
self._accumulate_tx_summary(ctx, pack)
|
|
59
|
+
|
|
60
|
+
if ctx and not pack.success and pack.error_type:
|
|
61
|
+
ctx._llm_last_error_type = pack.error_type
|
|
62
|
+
ctx._llm_last_error_model = pack.model or 'unknown'
|
|
63
|
+
ctx._llm_last_error_provider = pack.provider or ''
|
|
64
|
+
ctx._llm_last_error_op_type = pack.operation_type or 'unknown'
|
|
65
|
+
ctx._llm_last_error_url = pack.url or ''
|
|
66
|
+
|
|
67
|
+
self._ensure_started()
|
|
68
|
+
if self._q.full():
|
|
69
|
+
logging.warning('[LLM] send queue full, pack dropped: model=%s' % pack.model,
|
|
70
|
+
extra={'id': 'LLM025'})
|
|
71
|
+
return
|
|
72
|
+
self._q.put(pack)
|
|
73
|
+
|
|
74
|
+
def send_tx_status(self, ctx):
|
|
75
|
+
if not ctx:
|
|
76
|
+
return
|
|
77
|
+
if not getattr(conf, 'llm_enabled', False):
|
|
78
|
+
return
|
|
79
|
+
tx = getattr(ctx, '_llm_tx_status', None)
|
|
80
|
+
if not tx or tx.call_count == 0:
|
|
81
|
+
return
|
|
82
|
+
|
|
83
|
+
try:
|
|
84
|
+
self._send_log_sink(tx)
|
|
85
|
+
# 트랜잭션 경계에서 LLM 버퍼에 남아있는 데이터 flush (큐를 거쳐 순서 보장)
|
|
86
|
+
async_sender.flush_llm_relaypack()
|
|
87
|
+
except Exception as e:
|
|
88
|
+
logging.warning('[LLM] send_llm_tx_status failed: %s' % e, extra={'id': 'LLM022'})
|
|
89
|
+
|
|
90
|
+
# ── internal ──
|
|
91
|
+
|
|
92
|
+
def _ensure_started(self):
|
|
93
|
+
if self._started:
|
|
94
|
+
return
|
|
95
|
+
with self._lock:
|
|
96
|
+
if self._started:
|
|
97
|
+
return
|
|
98
|
+
self._started = True
|
|
99
|
+
t = threading.Thread(target=self._run, daemon=True)
|
|
100
|
+
t.start()
|
|
101
|
+
|
|
102
|
+
def _run(self):
|
|
103
|
+
while True:
|
|
104
|
+
pack = self._q.get()
|
|
105
|
+
if not pack:
|
|
106
|
+
continue
|
|
107
|
+
try:
|
|
108
|
+
self._process_pack(pack)
|
|
109
|
+
except Exception as e:
|
|
110
|
+
logging.warning('[LLM] process failed: %s' % e, extra={'id': 'LLM003'})
|
|
111
|
+
|
|
112
|
+
def _process_pack(self, pack):
|
|
113
|
+
# llm_step_status
|
|
114
|
+
self._send_log_sink(pack)
|
|
115
|
+
|
|
116
|
+
# system_message
|
|
117
|
+
for text in getattr(pack, 'system_texts', None) or []:
|
|
118
|
+
self._send_log_sink(LlmSystemMessage.from_pack(pack, system_text=text))
|
|
119
|
+
|
|
120
|
+
# input_message
|
|
121
|
+
prompt_text = getattr(pack, 'prompt_text', '') or ''
|
|
122
|
+
self._send_log_sink(LlmInputMessage.from_pack(pack, prompt_text=prompt_text))
|
|
123
|
+
|
|
124
|
+
# output_message
|
|
125
|
+
completion_text = getattr(pack, 'completion_text', '') or ''
|
|
126
|
+
reasoning_text = getattr(pack, 'reasoning_text', '') or ''
|
|
127
|
+
self._send_log_sink(LlmOutputMessage.from_pack(pack,
|
|
128
|
+
completion_text=completion_text, reasoning_text=reasoning_text))
|
|
129
|
+
|
|
130
|
+
# tool
|
|
131
|
+
tool_calls_text = getattr(pack, 'tool_calls_text', '') or ''
|
|
132
|
+
if tool_calls_text:
|
|
133
|
+
self._send_log_sink(LlmToolCalls.from_pack(pack, tool_calls_text=tool_calls_text))
|
|
134
|
+
|
|
135
|
+
# tool_result
|
|
136
|
+
tool_results_text = getattr(pack, 'tool_results_text', '') or ''
|
|
137
|
+
if tool_results_text:
|
|
138
|
+
self._send_log_sink(LlmToolResults.from_pack(pack, tool_results_text=tool_results_text))
|
|
139
|
+
|
|
140
|
+
def _accumulate_tx_summary(self, ctx, pack):
|
|
141
|
+
if not ctx:
|
|
142
|
+
return
|
|
143
|
+
tx = getattr(ctx, '_llm_tx_status', None)
|
|
144
|
+
if tx is None:
|
|
145
|
+
tx = LlmTxStatus()
|
|
146
|
+
tx.txid = str(ctx.id)
|
|
147
|
+
ctx._llm_tx_status = tx
|
|
148
|
+
tx.accumulate(pack)
|
|
149
|
+
|
|
150
|
+
def _send_log_sink(self, data):
|
|
151
|
+
tags = data.tags()
|
|
152
|
+
fields = data.fields()
|
|
153
|
+
content = data.content()
|
|
154
|
+
if content:
|
|
155
|
+
self._send_chunked(tags, fields, content, data.index)
|
|
156
|
+
else:
|
|
157
|
+
self._send_pack(tags, fields, '', data.index)
|
|
158
|
+
|
|
159
|
+
def _send_pack(self, tags, fields, content, index):
|
|
160
|
+
try:
|
|
161
|
+
tags = {k: v for k, v in tags.items() if v is not None}
|
|
162
|
+
fields = {k: v for k, v in fields.items() if v is not None}
|
|
163
|
+
fields['index'] = index
|
|
164
|
+
if content:
|
|
165
|
+
encoded = content.encode('utf-8', errors='replace')
|
|
166
|
+
if len(encoded) > _MAX_CONTENT_BYTES:
|
|
167
|
+
content = encoded[:_MAX_CONTENT_BYTES].decode('utf-8', errors='replace')
|
|
168
|
+
p = logSinkPack.getLogSinkPack(
|
|
169
|
+
t=DateUtil.now(),
|
|
170
|
+
category=LlmLogSinkPack.CATEGORY,
|
|
171
|
+
tags=tags,
|
|
172
|
+
fields={},
|
|
173
|
+
line=DateUtil.now(),
|
|
174
|
+
content=content
|
|
175
|
+
)
|
|
176
|
+
for k, v in fields.items():
|
|
177
|
+
if isinstance(v, (int, float)):
|
|
178
|
+
p.fields.putAuto(k, v)
|
|
179
|
+
else:
|
|
180
|
+
p.fields.putString(k, str(v))
|
|
181
|
+
|
|
182
|
+
p.pcode = conf.PCODE
|
|
183
|
+
bout = whatapio.DataOutputX()
|
|
184
|
+
bout.writePack(p, None)
|
|
185
|
+
packbytes = bout.toByteArray()
|
|
186
|
+
async_sender.send_llm_relaypack(packbytes)
|
|
187
|
+
except Exception as e:
|
|
188
|
+
logging.warning('[LLM] send_pack failed: %s' % e, extra={'id': 'LLM001'})
|
|
189
|
+
|
|
190
|
+
def _send_chunked(self, tags, fields, content, index):
|
|
191
|
+
try:
|
|
192
|
+
if not content:
|
|
193
|
+
self._send_pack(tags, fields, '', index)
|
|
194
|
+
return
|
|
195
|
+
|
|
196
|
+
tags = {k: v for k, v in tags.items() if v is not None}
|
|
197
|
+
fields = {k: v for k, v in fields.items() if v is not None}
|
|
198
|
+
|
|
199
|
+
content_bytes = content.encode('utf-8', errors='replace')
|
|
200
|
+
total_bytes = len(content_bytes)
|
|
201
|
+
|
|
202
|
+
if total_bytes <= _MAX_CONTENT_BYTES:
|
|
203
|
+
chunk_fields = dict(fields)
|
|
204
|
+
chunk_fields["chunk_index"] = 0
|
|
205
|
+
chunk_fields["chunk_total"] = 1
|
|
206
|
+
self._send_pack(tags, chunk_fields, content, index)
|
|
207
|
+
return
|
|
208
|
+
|
|
209
|
+
raw_chunks = []
|
|
210
|
+
i = 0
|
|
211
|
+
while i < total_bytes:
|
|
212
|
+
end = min(i + _MAX_CONTENT_BYTES, total_bytes)
|
|
213
|
+
if end < total_bytes:
|
|
214
|
+
while end > i and (content_bytes[end] & 0xC0) == 0x80:
|
|
215
|
+
end -= 1
|
|
216
|
+
raw_chunks.append(content_bytes[i:end].decode('utf-8', errors='replace'))
|
|
217
|
+
i = end
|
|
218
|
+
|
|
219
|
+
chunk_total = len(raw_chunks)
|
|
220
|
+
for idx, chunk in enumerate(raw_chunks):
|
|
221
|
+
chunk_fields = dict(fields)
|
|
222
|
+
chunk_fields["chunk_index"] = idx
|
|
223
|
+
chunk_fields["chunk_total"] = chunk_total
|
|
224
|
+
self._send_pack(tags, chunk_fields, chunk, index)
|
|
225
|
+
except Exception as e:
|
|
226
|
+
logging.warning('[LLM] send_chunked failed: %s' % e, extra={'id': 'LLM002'})
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
# ── module-level API ──
|
|
230
|
+
|
|
231
|
+
def dispatch_llm_pack(pack):
|
|
232
|
+
LlmLogSinkTask.get_instance().dispatch(pack)
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
def send_llm_tx_status(ctx):
|
|
236
|
+
LlmLogSinkTask.get_instance().send_tx_status(ctx)
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
def send_llm_pack(metadata):
|
|
240
|
+
pack = LlmStepStatus()
|
|
241
|
+
for key, val in metadata.items():
|
|
242
|
+
if hasattr(pack, key):
|
|
243
|
+
setattr(pack, key, val)
|
|
244
|
+
dispatch_llm_pack(pack)
|
|
@@ -0,0 +1,77 @@
|
|
|
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
|
+
)
|
|
72
|
+
|
|
73
|
+
@classmethod
|
|
74
|
+
def get_stat(cls, name):
|
|
75
|
+
if cls._instance is None:
|
|
76
|
+
return None
|
|
77
|
+
return cls._instance._stat_map.get(name)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from whatap.llm.log_sink_packs.llm_step_status import LlmStepStatus
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"""LLM 패키지에서 사용하는 상수 및 매핑 데이터 정의."""
|
|
2
|
+
LOG_SINK_CATEGORY = '#LlmCallLog'
|
|
3
|
+
|
|
4
|
+
PROVIDER_TOKEN_FIELDS = {
|
|
5
|
+
'openai': [
|
|
6
|
+
'input_tokens', 'output_tokens', 'total_tokens_count',
|
|
7
|
+
'cached_tokens', 'reasoning_tokens',
|
|
8
|
+
'audio_input_tokens', 'audio_output_tokens',
|
|
9
|
+
'accepted_prediction_tokens', 'rejected_prediction_tokens',
|
|
10
|
+
'embedding_count', 'dimensions', 'similarity',
|
|
11
|
+
],
|
|
12
|
+
'anthropic': [
|
|
13
|
+
'input_tokens', 'output_tokens', 'total_tokens_count',
|
|
14
|
+
'cache_creation_input_tokens', 'cache_read_input_tokens',
|
|
15
|
+
],
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
DEFAULT_TOKEN_FIELDS = ['input_tokens', 'output_tokens', 'total_tokens_count']
|
|
19
|
+
|
|
20
|
+
OPENAI_URL_OPERATION_MAP = [
|
|
21
|
+
("/v1/chat/completions", "chat"),
|
|
22
|
+
("/v1/responses", "response"),
|
|
23
|
+
("/v1/completions", "completion"),
|
|
24
|
+
("/v1/embeddings", "embedding"),
|
|
25
|
+
("/v1/images/generations", "image_generation"),
|
|
26
|
+
("/v1/images/edits", "image_edit"),
|
|
27
|
+
("/v1/audio/transcriptions", "audio_transcription"),
|
|
28
|
+
("/v1/audio/translations", "audio_translation"),
|
|
29
|
+
("/v1/audio/speech", "audio_speech"),
|
|
30
|
+
("/v1/moderations", "moderation"),
|
|
31
|
+
("/v1/fine_tuning", "fine_tuning"),
|
|
32
|
+
("/v1/files", "file"),
|
|
33
|
+
("/v1/assistants", "assistant"),
|
|
34
|
+
("/v1/threads", "thread"),
|
|
35
|
+
("/v1/vector_stores", "vector_store"),
|
|
36
|
+
("/v1/batches", "batch"),
|
|
37
|
+
]
|
|
38
|
+
|
|
39
|
+
ANTHROPIC_URL_OPERATION_MAP = [
|
|
40
|
+
("/v1/messages", "chat"),
|
|
41
|
+
]
|
|
42
|
+
|
|
43
|
+
URL_OPERATION_MAP = OPENAI_URL_OPERATION_MAP + ANTHROPIC_URL_OPERATION_MAP
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
from whatap.llm.log_sink_packs.llm_log_sink_pack import LlmLogSinkPack
|
|
2
|
+
from whatap.llm.log_sink_packs.llm_step_status import LlmStepStatus
|
|
3
|
+
from whatap.llm.log_sink_packs.llm_system_message import LlmSystemMessage
|
|
4
|
+
from whatap.llm.log_sink_packs.llm_input_message import LlmInputMessage
|
|
5
|
+
from whatap.llm.log_sink_packs.llm_output_message import LlmOutputMessage
|
|
6
|
+
from whatap.llm.log_sink_packs.llm_tool_calls import LlmToolCalls
|
|
7
|
+
from whatap.llm.log_sink_packs.llm_tool_results import LlmToolResults
|
|
8
|
+
from whatap.llm.log_sink_packs.llm_tx_status import LlmTxStatus
|
|
@@ -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
|