aiqtoolkit 1.1.0a20250515__py3-none-any.whl → 1.1.0a20251020__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.

Potentially problematic release.


This version of aiqtoolkit might be problematic. Click here for more details.

Files changed (319) hide show
  1. aiqtoolkit-1.1.0a20251020.dist-info/METADATA +37 -0
  2. aiqtoolkit-1.1.0a20251020.dist-info/RECORD +4 -0
  3. {aiqtoolkit-1.1.0a20250515.dist-info → aiqtoolkit-1.1.0a20251020.dist-info}/WHEEL +1 -1
  4. aiqtoolkit-1.1.0a20251020.dist-info/top_level.txt +1 -0
  5. aiq/agent/__init__.py +0 -0
  6. aiq/agent/base.py +0 -76
  7. aiq/agent/dual_node.py +0 -67
  8. aiq/agent/react_agent/__init__.py +0 -0
  9. aiq/agent/react_agent/agent.py +0 -322
  10. aiq/agent/react_agent/output_parser.py +0 -104
  11. aiq/agent/react_agent/prompt.py +0 -46
  12. aiq/agent/react_agent/register.py +0 -148
  13. aiq/agent/reasoning_agent/__init__.py +0 -0
  14. aiq/agent/reasoning_agent/reasoning_agent.py +0 -224
  15. aiq/agent/register.py +0 -23
  16. aiq/agent/rewoo_agent/__init__.py +0 -0
  17. aiq/agent/rewoo_agent/agent.py +0 -410
  18. aiq/agent/rewoo_agent/prompt.py +0 -108
  19. aiq/agent/rewoo_agent/register.py +0 -158
  20. aiq/agent/tool_calling_agent/__init__.py +0 -0
  21. aiq/agent/tool_calling_agent/agent.py +0 -123
  22. aiq/agent/tool_calling_agent/register.py +0 -105
  23. aiq/builder/__init__.py +0 -0
  24. aiq/builder/builder.py +0 -223
  25. aiq/builder/component_utils.py +0 -303
  26. aiq/builder/context.py +0 -227
  27. aiq/builder/embedder.py +0 -24
  28. aiq/builder/eval_builder.py +0 -120
  29. aiq/builder/evaluator.py +0 -29
  30. aiq/builder/framework_enum.py +0 -24
  31. aiq/builder/front_end.py +0 -73
  32. aiq/builder/function.py +0 -297
  33. aiq/builder/function_base.py +0 -376
  34. aiq/builder/function_info.py +0 -627
  35. aiq/builder/intermediate_step_manager.py +0 -135
  36. aiq/builder/llm.py +0 -25
  37. aiq/builder/retriever.py +0 -25
  38. aiq/builder/user_interaction_manager.py +0 -71
  39. aiq/builder/workflow.py +0 -143
  40. aiq/builder/workflow_builder.py +0 -757
  41. aiq/cli/__init__.py +0 -14
  42. aiq/cli/cli_utils/__init__.py +0 -0
  43. aiq/cli/cli_utils/config_override.py +0 -231
  44. aiq/cli/cli_utils/validation.py +0 -37
  45. aiq/cli/commands/__init__.py +0 -0
  46. aiq/cli/commands/configure/__init__.py +0 -0
  47. aiq/cli/commands/configure/channel/__init__.py +0 -0
  48. aiq/cli/commands/configure/channel/add.py +0 -28
  49. aiq/cli/commands/configure/channel/channel.py +0 -36
  50. aiq/cli/commands/configure/channel/remove.py +0 -30
  51. aiq/cli/commands/configure/channel/update.py +0 -30
  52. aiq/cli/commands/configure/configure.py +0 -33
  53. aiq/cli/commands/evaluate.py +0 -139
  54. aiq/cli/commands/info/__init__.py +0 -14
  55. aiq/cli/commands/info/info.py +0 -39
  56. aiq/cli/commands/info/list_channels.py +0 -32
  57. aiq/cli/commands/info/list_components.py +0 -129
  58. aiq/cli/commands/info/list_mcp.py +0 -126
  59. aiq/cli/commands/registry/__init__.py +0 -14
  60. aiq/cli/commands/registry/publish.py +0 -88
  61. aiq/cli/commands/registry/pull.py +0 -118
  62. aiq/cli/commands/registry/registry.py +0 -38
  63. aiq/cli/commands/registry/remove.py +0 -108
  64. aiq/cli/commands/registry/search.py +0 -155
  65. aiq/cli/commands/start.py +0 -250
  66. aiq/cli/commands/uninstall.py +0 -83
  67. aiq/cli/commands/validate.py +0 -47
  68. aiq/cli/commands/workflow/__init__.py +0 -14
  69. aiq/cli/commands/workflow/templates/__init__.py.j2 +0 -0
  70. aiq/cli/commands/workflow/templates/config.yml.j2 +0 -16
  71. aiq/cli/commands/workflow/templates/pyproject.toml.j2 +0 -22
  72. aiq/cli/commands/workflow/templates/register.py.j2 +0 -5
  73. aiq/cli/commands/workflow/templates/workflow.py.j2 +0 -36
  74. aiq/cli/commands/workflow/workflow.py +0 -37
  75. aiq/cli/commands/workflow/workflow_commands.py +0 -313
  76. aiq/cli/entrypoint.py +0 -133
  77. aiq/cli/main.py +0 -44
  78. aiq/cli/register_workflow.py +0 -408
  79. aiq/cli/type_registry.py +0 -879
  80. aiq/data_models/__init__.py +0 -14
  81. aiq/data_models/api_server.py +0 -588
  82. aiq/data_models/common.py +0 -143
  83. aiq/data_models/component.py +0 -46
  84. aiq/data_models/component_ref.py +0 -135
  85. aiq/data_models/config.py +0 -349
  86. aiq/data_models/dataset_handler.py +0 -122
  87. aiq/data_models/discovery_metadata.py +0 -286
  88. aiq/data_models/embedder.py +0 -26
  89. aiq/data_models/evaluate.py +0 -104
  90. aiq/data_models/evaluator.py +0 -26
  91. aiq/data_models/front_end.py +0 -26
  92. aiq/data_models/function.py +0 -30
  93. aiq/data_models/function_dependencies.py +0 -64
  94. aiq/data_models/interactive.py +0 -237
  95. aiq/data_models/intermediate_step.py +0 -269
  96. aiq/data_models/invocation_node.py +0 -38
  97. aiq/data_models/llm.py +0 -26
  98. aiq/data_models/logging.py +0 -26
  99. aiq/data_models/memory.py +0 -26
  100. aiq/data_models/profiler.py +0 -53
  101. aiq/data_models/registry_handler.py +0 -26
  102. aiq/data_models/retriever.py +0 -30
  103. aiq/data_models/step_adaptor.py +0 -64
  104. aiq/data_models/streaming.py +0 -33
  105. aiq/data_models/swe_bench_model.py +0 -54
  106. aiq/data_models/telemetry_exporter.py +0 -26
  107. aiq/embedder/__init__.py +0 -0
  108. aiq/embedder/langchain_client.py +0 -41
  109. aiq/embedder/nim_embedder.py +0 -58
  110. aiq/embedder/openai_embedder.py +0 -42
  111. aiq/embedder/register.py +0 -24
  112. aiq/eval/__init__.py +0 -14
  113. aiq/eval/config.py +0 -42
  114. aiq/eval/dataset_handler/__init__.py +0 -0
  115. aiq/eval/dataset_handler/dataset_downloader.py +0 -106
  116. aiq/eval/dataset_handler/dataset_filter.py +0 -52
  117. aiq/eval/dataset_handler/dataset_handler.py +0 -169
  118. aiq/eval/evaluate.py +0 -325
  119. aiq/eval/evaluator/__init__.py +0 -14
  120. aiq/eval/evaluator/evaluator_model.py +0 -44
  121. aiq/eval/intermediate_step_adapter.py +0 -93
  122. aiq/eval/rag_evaluator/__init__.py +0 -0
  123. aiq/eval/rag_evaluator/evaluate.py +0 -138
  124. aiq/eval/rag_evaluator/register.py +0 -138
  125. aiq/eval/register.py +0 -23
  126. aiq/eval/remote_workflow.py +0 -128
  127. aiq/eval/runtime_event_subscriber.py +0 -52
  128. aiq/eval/swe_bench_evaluator/__init__.py +0 -0
  129. aiq/eval/swe_bench_evaluator/evaluate.py +0 -215
  130. aiq/eval/swe_bench_evaluator/register.py +0 -36
  131. aiq/eval/trajectory_evaluator/__init__.py +0 -0
  132. aiq/eval/trajectory_evaluator/evaluate.py +0 -118
  133. aiq/eval/trajectory_evaluator/register.py +0 -40
  134. aiq/eval/tunable_rag_evaluator/__init__.py +0 -0
  135. aiq/eval/tunable_rag_evaluator/evaluate.py +0 -263
  136. aiq/eval/tunable_rag_evaluator/register.py +0 -50
  137. aiq/eval/utils/__init__.py +0 -0
  138. aiq/eval/utils/output_uploader.py +0 -131
  139. aiq/eval/utils/tqdm_position_registry.py +0 -40
  140. aiq/front_ends/__init__.py +0 -14
  141. aiq/front_ends/console/__init__.py +0 -14
  142. aiq/front_ends/console/console_front_end_config.py +0 -32
  143. aiq/front_ends/console/console_front_end_plugin.py +0 -107
  144. aiq/front_ends/console/register.py +0 -25
  145. aiq/front_ends/cron/__init__.py +0 -14
  146. aiq/front_ends/fastapi/__init__.py +0 -14
  147. aiq/front_ends/fastapi/fastapi_front_end_config.py +0 -150
  148. aiq/front_ends/fastapi/fastapi_front_end_plugin.py +0 -103
  149. aiq/front_ends/fastapi/fastapi_front_end_plugin_worker.py +0 -607
  150. aiq/front_ends/fastapi/intermediate_steps_subscriber.py +0 -80
  151. aiq/front_ends/fastapi/job_store.py +0 -161
  152. aiq/front_ends/fastapi/main.py +0 -70
  153. aiq/front_ends/fastapi/message_handler.py +0 -279
  154. aiq/front_ends/fastapi/message_validator.py +0 -345
  155. aiq/front_ends/fastapi/register.py +0 -25
  156. aiq/front_ends/fastapi/response_helpers.py +0 -195
  157. aiq/front_ends/fastapi/step_adaptor.py +0 -320
  158. aiq/front_ends/fastapi/websocket.py +0 -148
  159. aiq/front_ends/mcp/__init__.py +0 -14
  160. aiq/front_ends/mcp/mcp_front_end_config.py +0 -32
  161. aiq/front_ends/mcp/mcp_front_end_plugin.py +0 -93
  162. aiq/front_ends/mcp/register.py +0 -27
  163. aiq/front_ends/mcp/tool_converter.py +0 -242
  164. aiq/front_ends/register.py +0 -22
  165. aiq/front_ends/simple_base/__init__.py +0 -14
  166. aiq/front_ends/simple_base/simple_front_end_plugin_base.py +0 -52
  167. aiq/llm/__init__.py +0 -0
  168. aiq/llm/nim_llm.py +0 -45
  169. aiq/llm/openai_llm.py +0 -45
  170. aiq/llm/register.py +0 -22
  171. aiq/llm/utils/__init__.py +0 -14
  172. aiq/llm/utils/env_config_value.py +0 -94
  173. aiq/llm/utils/error.py +0 -17
  174. aiq/memory/__init__.py +0 -20
  175. aiq/memory/interfaces.py +0 -183
  176. aiq/memory/models.py +0 -112
  177. aiq/meta/module_to_distro.json +0 -3
  178. aiq/meta/pypi.md +0 -58
  179. aiq/observability/__init__.py +0 -0
  180. aiq/observability/async_otel_listener.py +0 -429
  181. aiq/observability/register.py +0 -99
  182. aiq/plugins/.namespace +0 -1
  183. aiq/profiler/__init__.py +0 -0
  184. aiq/profiler/callbacks/__init__.py +0 -0
  185. aiq/profiler/callbacks/agno_callback_handler.py +0 -295
  186. aiq/profiler/callbacks/base_callback_class.py +0 -20
  187. aiq/profiler/callbacks/langchain_callback_handler.py +0 -278
  188. aiq/profiler/callbacks/llama_index_callback_handler.py +0 -205
  189. aiq/profiler/callbacks/semantic_kernel_callback_handler.py +0 -238
  190. aiq/profiler/callbacks/token_usage_base_model.py +0 -27
  191. aiq/profiler/data_frame_row.py +0 -51
  192. aiq/profiler/decorators/__init__.py +0 -0
  193. aiq/profiler/decorators/framework_wrapper.py +0 -131
  194. aiq/profiler/decorators/function_tracking.py +0 -254
  195. aiq/profiler/forecasting/__init__.py +0 -0
  196. aiq/profiler/forecasting/config.py +0 -18
  197. aiq/profiler/forecasting/model_trainer.py +0 -75
  198. aiq/profiler/forecasting/models/__init__.py +0 -22
  199. aiq/profiler/forecasting/models/forecasting_base_model.py +0 -40
  200. aiq/profiler/forecasting/models/linear_model.py +0 -196
  201. aiq/profiler/forecasting/models/random_forest_regressor.py +0 -268
  202. aiq/profiler/inference_metrics_model.py +0 -25
  203. aiq/profiler/inference_optimization/__init__.py +0 -0
  204. aiq/profiler/inference_optimization/bottleneck_analysis/__init__.py +0 -0
  205. aiq/profiler/inference_optimization/bottleneck_analysis/nested_stack_analysis.py +0 -452
  206. aiq/profiler/inference_optimization/bottleneck_analysis/simple_stack_analysis.py +0 -258
  207. aiq/profiler/inference_optimization/data_models.py +0 -386
  208. aiq/profiler/inference_optimization/experimental/__init__.py +0 -0
  209. aiq/profiler/inference_optimization/experimental/concurrency_spike_analysis.py +0 -468
  210. aiq/profiler/inference_optimization/experimental/prefix_span_analysis.py +0 -405
  211. aiq/profiler/inference_optimization/llm_metrics.py +0 -212
  212. aiq/profiler/inference_optimization/prompt_caching.py +0 -163
  213. aiq/profiler/inference_optimization/token_uniqueness.py +0 -107
  214. aiq/profiler/inference_optimization/workflow_runtimes.py +0 -72
  215. aiq/profiler/intermediate_property_adapter.py +0 -102
  216. aiq/profiler/profile_runner.py +0 -433
  217. aiq/profiler/utils.py +0 -184
  218. aiq/registry_handlers/__init__.py +0 -0
  219. aiq/registry_handlers/local/__init__.py +0 -0
  220. aiq/registry_handlers/local/local_handler.py +0 -176
  221. aiq/registry_handlers/local/register_local.py +0 -37
  222. aiq/registry_handlers/metadata_factory.py +0 -60
  223. aiq/registry_handlers/package_utils.py +0 -198
  224. aiq/registry_handlers/pypi/__init__.py +0 -0
  225. aiq/registry_handlers/pypi/pypi_handler.py +0 -251
  226. aiq/registry_handlers/pypi/register_pypi.py +0 -40
  227. aiq/registry_handlers/register.py +0 -21
  228. aiq/registry_handlers/registry_handler_base.py +0 -157
  229. aiq/registry_handlers/rest/__init__.py +0 -0
  230. aiq/registry_handlers/rest/register_rest.py +0 -56
  231. aiq/registry_handlers/rest/rest_handler.py +0 -237
  232. aiq/registry_handlers/schemas/__init__.py +0 -0
  233. aiq/registry_handlers/schemas/headers.py +0 -42
  234. aiq/registry_handlers/schemas/package.py +0 -68
  235. aiq/registry_handlers/schemas/publish.py +0 -63
  236. aiq/registry_handlers/schemas/pull.py +0 -82
  237. aiq/registry_handlers/schemas/remove.py +0 -36
  238. aiq/registry_handlers/schemas/search.py +0 -91
  239. aiq/registry_handlers/schemas/status.py +0 -47
  240. aiq/retriever/__init__.py +0 -0
  241. aiq/retriever/interface.py +0 -37
  242. aiq/retriever/milvus/__init__.py +0 -14
  243. aiq/retriever/milvus/register.py +0 -81
  244. aiq/retriever/milvus/retriever.py +0 -228
  245. aiq/retriever/models.py +0 -74
  246. aiq/retriever/nemo_retriever/__init__.py +0 -14
  247. aiq/retriever/nemo_retriever/register.py +0 -60
  248. aiq/retriever/nemo_retriever/retriever.py +0 -190
  249. aiq/retriever/register.py +0 -22
  250. aiq/runtime/__init__.py +0 -14
  251. aiq/runtime/loader.py +0 -188
  252. aiq/runtime/runner.py +0 -176
  253. aiq/runtime/session.py +0 -140
  254. aiq/runtime/user_metadata.py +0 -131
  255. aiq/settings/__init__.py +0 -0
  256. aiq/settings/global_settings.py +0 -318
  257. aiq/test/.namespace +0 -1
  258. aiq/tool/__init__.py +0 -0
  259. aiq/tool/code_execution/__init__.py +0 -0
  260. aiq/tool/code_execution/code_sandbox.py +0 -188
  261. aiq/tool/code_execution/local_sandbox/Dockerfile.sandbox +0 -60
  262. aiq/tool/code_execution/local_sandbox/__init__.py +0 -13
  263. aiq/tool/code_execution/local_sandbox/local_sandbox_server.py +0 -83
  264. aiq/tool/code_execution/local_sandbox/sandbox.requirements.txt +0 -4
  265. aiq/tool/code_execution/local_sandbox/start_local_sandbox.sh +0 -25
  266. aiq/tool/code_execution/register.py +0 -70
  267. aiq/tool/code_execution/utils.py +0 -100
  268. aiq/tool/datetime_tools.py +0 -42
  269. aiq/tool/document_search.py +0 -141
  270. aiq/tool/github_tools/__init__.py +0 -0
  271. aiq/tool/github_tools/create_github_commit.py +0 -133
  272. aiq/tool/github_tools/create_github_issue.py +0 -87
  273. aiq/tool/github_tools/create_github_pr.py +0 -106
  274. aiq/tool/github_tools/get_github_file.py +0 -106
  275. aiq/tool/github_tools/get_github_issue.py +0 -166
  276. aiq/tool/github_tools/get_github_pr.py +0 -256
  277. aiq/tool/github_tools/update_github_issue.py +0 -100
  278. aiq/tool/mcp/__init__.py +0 -14
  279. aiq/tool/mcp/mcp_client.py +0 -220
  280. aiq/tool/mcp/mcp_tool.py +0 -95
  281. aiq/tool/memory_tools/__init__.py +0 -0
  282. aiq/tool/memory_tools/add_memory_tool.py +0 -79
  283. aiq/tool/memory_tools/delete_memory_tool.py +0 -67
  284. aiq/tool/memory_tools/get_memory_tool.py +0 -72
  285. aiq/tool/nvidia_rag.py +0 -95
  286. aiq/tool/register.py +0 -37
  287. aiq/tool/retriever.py +0 -89
  288. aiq/tool/server_tools.py +0 -63
  289. aiq/utils/__init__.py +0 -0
  290. aiq/utils/data_models/__init__.py +0 -0
  291. aiq/utils/data_models/schema_validator.py +0 -58
  292. aiq/utils/debugging_utils.py +0 -43
  293. aiq/utils/exception_handlers/__init__.py +0 -0
  294. aiq/utils/exception_handlers/schemas.py +0 -114
  295. aiq/utils/io/__init__.py +0 -0
  296. aiq/utils/io/yaml_tools.py +0 -119
  297. aiq/utils/metadata_utils.py +0 -74
  298. aiq/utils/optional_imports.py +0 -142
  299. aiq/utils/producer_consumer_queue.py +0 -178
  300. aiq/utils/reactive/__init__.py +0 -0
  301. aiq/utils/reactive/base/__init__.py +0 -0
  302. aiq/utils/reactive/base/observable_base.py +0 -65
  303. aiq/utils/reactive/base/observer_base.py +0 -55
  304. aiq/utils/reactive/base/subject_base.py +0 -79
  305. aiq/utils/reactive/observable.py +0 -59
  306. aiq/utils/reactive/observer.py +0 -76
  307. aiq/utils/reactive/subject.py +0 -131
  308. aiq/utils/reactive/subscription.py +0 -49
  309. aiq/utils/settings/__init__.py +0 -0
  310. aiq/utils/settings/global_settings.py +0 -197
  311. aiq/utils/type_converter.py +0 -232
  312. aiq/utils/type_utils.py +0 -397
  313. aiq/utils/url_utils.py +0 -27
  314. aiqtoolkit-1.1.0a20250515.dist-info/METADATA +0 -331
  315. aiqtoolkit-1.1.0a20250515.dist-info/RECORD +0 -316
  316. aiqtoolkit-1.1.0a20250515.dist-info/entry_points.txt +0 -17
  317. aiqtoolkit-1.1.0a20250515.dist-info/licenses/LICENSE-3rd-party.txt +0 -3686
  318. aiqtoolkit-1.1.0a20250515.dist-info/licenses/LICENSE.md +0 -201
  319. aiqtoolkit-1.1.0a20250515.dist-info/top_level.txt +0 -1
@@ -1,468 +0,0 @@
1
- # SPDX-FileCopyrightText: Copyright (c) 2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2
- # SPDX-License-Identifier: Apache-2.0
3
- #
4
- # Licensed under the Apache License, Version 2.0 (the "License");
5
- # you may not use this file except in compliance with the License.
6
- # You may obtain a copy of the License at
7
- #
8
- # http://www.apache.org/licenses/LICENSE-2.0
9
- #
10
- # Unless required by applicable law or agreed to in writing, software
11
- # distributed under the License is distributed on an "AS IS" BASIS,
12
- # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
- # See the License for the specific language governing permissions and
14
- # limitations under the License.
15
- """
16
- An enhanced script that:
17
-
18
- 1. Groups workflow events by example_number to build a per-example call tree (no cross-example nesting).
19
- 2. Tracks concurrency globally across *all* examples.
20
- 3. Identifies concurrency "spikes" (concurrency >= a threshold).
21
- 4. Correlates concurrency spikes with token usage and call metadata.
22
- 5. Computes average call latency by concurrency level, using midpoint concurrency as an approximation.
23
- 6. Returns a Pydantic result containing concurrency distribution, spike intervals, correlation stats, etc.,
24
- along with a textual report containing the real call count, active calls in spikes, etc.
25
-
26
- Changes from previous version:
27
-
28
- - Now shows the actual total calls in the dataset.
29
- - Displays the real number of active calls for each spike interval.
30
- - Computes and reports average latency by concurrency (no visualization).
31
-
32
- """
33
-
34
- import numpy as np
35
- import pandas as pd
36
-
37
- from aiq.data_models.intermediate_step import IntermediateStep
38
- from aiq.profiler.inference_optimization.data_models import ConcurrencyAnalysisResult
39
- from aiq.profiler.inference_optimization.data_models import ConcurrencyCallNode
40
- from aiq.profiler.inference_optimization.data_models import ConcurrencyCorrelationStats
41
- from aiq.profiler.inference_optimization.data_models import ConcurrencySpikeInfo
42
- from aiq.profiler.utils import create_standardized_dataframe
43
-
44
- # --------------------------------------------------------------------------------
45
- # 1) Building the Per-Example Call Trees
46
- # --------------------------------------------------------------------------------
47
-
48
-
49
- def build_call_tree_for_example(example_df: pd.DataFrame) -> list[ConcurrencyCallNode]:
50
- """
51
- Sort events by time, push on `*_START`, pop on `*_END`, build stack-based calls for a single example.
52
- """
53
- stack: list[ConcurrencyCallNode] = []
54
- top_level: dict[str, ConcurrencyCallNode] = {}
55
- partial_map: dict[str, ConcurrencyCallNode] = {}
56
-
57
- def parse_op_type(et: str) -> str | None:
58
- et = et.upper()
59
- if et.startswith("LLM_"):
60
- return "LLM"
61
- if et.startswith("TOOL_"):
62
- return "TOOL"
63
- return None
64
-
65
- def get_op_name(row: pd.Series, op_type: str) -> str:
66
- if op_type == "LLM":
67
- return row.get("llm_name") or "unknown_llm"
68
- if op_type == "TOOL":
69
- return row.get("tool_name") or "unknown_tool"
70
- return "unknown_op"
71
-
72
- example_num = int(example_df["example_number"].iloc[0])
73
-
74
- for _, row in example_df.iterrows():
75
- et = row["event_type"].value.upper()
76
- uuid = str(row["UUID"])
77
- ts = float(row["event_timestamp"])
78
- op_type = parse_op_type(et)
79
- if not op_type:
80
- continue
81
-
82
- if et.endswith("_START"):
83
- op_name = get_op_name(row, op_type)
84
- node = ConcurrencyCallNode(
85
- uuid=uuid,
86
- example_number=example_num,
87
- operation_type=op_type,
88
- operation_name=op_name,
89
- start_time=ts,
90
- end_time=ts, # updated on END
91
- duration=0.0)
92
- if stack:
93
- parent = stack[-1]
94
- node.parent = parent
95
- parent.children.append(node)
96
- else:
97
- top_level[uuid] = node
98
-
99
- stack.append(node)
100
- partial_map[uuid] = node
101
-
102
- elif et.endswith("_END"):
103
- if uuid not in partial_map:
104
- continue
105
- node = partial_map[uuid]
106
- node.end_time = ts
107
- node.duration = max(0.0, node.end_time - node.start_time)
108
- node.prompt_tokens = row.get("prompt_tokens")
109
- node.completion_tokens = row.get("completion_tokens")
110
- node.total_tokens = row.get("total_tokens")
111
- node.tool_outputs = row.get("metadata").get("tool_outputs") if (
112
- row.get("metadata") and row.get("metadata").get("tool_outputs")) else None
113
- node.llm_text_output = row.get("llm_text_output")
114
-
115
- if stack and stack[-1].uuid == uuid:
116
- stack.pop()
117
- del partial_map[uuid]
118
-
119
- # gather top-level
120
- roots = []
121
- for _, nd in top_level.items():
122
- if nd.parent is None:
123
- roots.append(nd)
124
- return roots
125
-
126
-
127
- def build_call_tree_per_example(df: pd.DataFrame) -> list[ConcurrencyCallNode]:
128
- """
129
- Groups by example_number, builds separate call trees, returns combined list of top-level calls.
130
- """
131
- req_cols = {"example_number", "event_type", "UUID", "event_timestamp"}
132
- missing = req_cols - set(df.columns)
133
- if missing:
134
- raise ValueError(f"DataFrame missing required columns: {missing}")
135
-
136
- dfc = df.copy()
137
- dfc.sort_values(["example_number", "event_timestamp"], inplace=True)
138
-
139
- all_roots: list[ConcurrencyCallNode] = []
140
- for _, grp in dfc.groupby("example_number"):
141
- r = build_call_tree_for_example(grp)
142
- all_roots.extend(r)
143
- return all_roots
144
-
145
-
146
- def flatten_calls(roots: list[ConcurrencyCallNode]) -> list[ConcurrencyCallNode]:
147
- """
148
- DFS to produce a flat list of all calls (including nested).
149
- """
150
- all_nodes = []
151
-
152
- def dfs(n: ConcurrencyCallNode):
153
- all_nodes.append(n)
154
- for c in n.children:
155
- dfs(c)
156
-
157
- for rt in roots:
158
- dfs(rt)
159
- return all_nodes
160
-
161
-
162
- # --------------------------------------------------------------------------------
163
- # 2) Global Concurrency Distribution & Segments
164
- # --------------------------------------------------------------------------------
165
-
166
-
167
- def compute_concurrency_distribution(roots: list[ConcurrencyCallNode]) -> dict[int, float]:
168
- """
169
- Flatten calls, produce (start, +1)/(end, -1), accumulate total time at each concurrency level.
170
- """
171
- all_nodes = flatten_calls(roots)
172
- if not all_nodes:
173
- return {}
174
-
175
- events = []
176
- for n in all_nodes:
177
- if n.start_time <= n.end_time:
178
- events.append((n.start_time, +1))
179
- events.append((n.end_time, -1))
180
-
181
- events.sort(key=lambda x: x[0])
182
- dist_map: dict[int, float] = {}
183
- curr_conc = 0
184
- prev_time = events[0][0]
185
-
186
- for (time_val, delta) in events:
187
- if time_val > prev_time:
188
- length = time_val - prev_time
189
- dist_map[curr_conc] = dist_map.get(curr_conc, 0.0) + length
190
- curr_conc += delta
191
- prev_time = time_val
192
-
193
- return dist_map
194
-
195
-
196
- def build_concurrency_segments(roots: list[ConcurrencyCallNode]) -> list[tuple[float, float, int]]:
197
- """
198
- Return piecewise segments of (start, end, concurrency) across all calls.
199
- """
200
- all_nodes = flatten_calls(roots)
201
- if not all_nodes:
202
- return []
203
-
204
- events = []
205
- for n in all_nodes:
206
- if n.start_time <= n.end_time:
207
- events.append((n.start_time, +1))
208
- events.append((n.end_time, -1))
209
-
210
- events.sort(key=lambda x: x[0])
211
- segments: list[tuple[float, float, int]] = []
212
- curr_conc = 0
213
- prev_time = events[0][0]
214
-
215
- for (t, delta) in events:
216
- if t > prev_time:
217
- segments.append((prev_time, t, curr_conc))
218
- curr_conc += delta
219
- prev_time = t
220
-
221
- return segments
222
-
223
-
224
- def find_percentile_concurrency(dist_map: dict[int, float], percentile: float) -> float:
225
- """
226
- concurrency => total_time -> find concurrency level at given percentile of total time.
227
- """
228
- total_time = sum(dist_map.values())
229
- if total_time <= 0:
230
- return 0.0
231
-
232
- items = sorted(dist_map.items(), key=lambda x: x[0]) # ascending concurrency
233
- threshold = percentile * 0.01 * total_time
234
- accum = 0.0
235
- last_c = 0
236
-
237
- for c_val, dur in items:
238
- accum += dur
239
- if accum >= threshold:
240
- return float(c_val)
241
- last_c = c_val
242
- return float(last_c)
243
-
244
-
245
- # --------------------------------------------------------------------------------
246
- # 3) Spike Detection & Active Calls
247
- # --------------------------------------------------------------------------------
248
-
249
-
250
- def detect_concurrency_spikes(segments: list[tuple[float, float, int]], threshold: int) -> list[ConcurrencySpikeInfo]:
251
- """
252
- If concurrency >= threshold, label that segment a 'spike'.
253
- """
254
- spikes = []
255
- for (s, e, c_val) in segments:
256
- if c_val >= threshold and e > s:
257
- sp = ConcurrencySpikeInfo(start_time=s, end_time=e, concurrency=c_val)
258
- spikes.append(sp)
259
- return spikes
260
-
261
-
262
- def find_calls_active_in_interval(roots: list[ConcurrencyCallNode], start_t: float,
263
- end_t: float) -> list[ConcurrencyCallNode]:
264
- """
265
- Return all calls overlapping [start_t, end_t).
266
- Overlap => not (call.end_time <= start_t or call.start_time >= end_t).
267
- """
268
- results = []
269
- all_nodes = flatten_calls(roots)
270
- for n in all_nodes:
271
- if not (n.end_time <= start_t or n.start_time >= end_t):
272
- results.append(n)
273
- return results
274
-
275
-
276
- # --------------------------------------------------------------------------------
277
- # 4) Correlations & Average Latency by Concurrency
278
- # --------------------------------------------------------------------------------
279
-
280
- def correlate_spike_calls(spikes: list[ConcurrencySpikeInfo], roots: list[ConcurrencyCallNode]) \
281
- -> ConcurrencyCorrelationStats:
282
- """
283
- For each spike, gather calls that overlap, compute average prompt_tokens, total_tokens across them.
284
- """
285
- p_tokens = []
286
- t_tokens = []
287
-
288
- for sp in spikes:
289
- active = find_calls_active_in_interval(roots, sp.start_time, sp.end_time)
290
- # record the active call uuids for each spike
291
- sp.active_uuids = list({c.uuid for c in active})
292
-
293
- for c in active:
294
- if c.prompt_tokens and c.prompt_tokens > 0:
295
- p_tokens.append(c.prompt_tokens)
296
- if c.total_tokens and c.total_tokens > 0:
297
- t_tokens.append(c.total_tokens)
298
-
299
- def safe_avg(lst):
300
- return float(np.mean(lst)) if lst else 0.0
301
-
302
- return ConcurrencyCorrelationStats(
303
- avg_prompt_tokens=safe_avg(p_tokens),
304
- avg_total_tokens=safe_avg(t_tokens),
305
- )
306
-
307
-
308
- def compute_midpoint_concurrency(n: ConcurrencyCallNode, segments: list[tuple[float, float, int]]) -> float:
309
- """
310
- Approx concurrency at the midpoint of this call.
311
- """
312
- if n.start_time >= n.end_time:
313
- return 0.0
314
- mid = 0.5 * (n.start_time + n.end_time)
315
-
316
- # binary or linear search
317
- left, right = 0, len(segments) - 1
318
- while left <= right:
319
- mid_idx = (left + right) // 2
320
- seg_start, seg_end, seg_conc = segments[mid_idx]
321
- if seg_start <= mid < seg_end:
322
- return float(seg_conc)
323
- if mid < seg_start:
324
- right = mid_idx - 1
325
- else:
326
- left = mid_idx + 1
327
- return 0.0
328
-
329
-
330
- def average_latency_by_midpoint_concurrency(roots: list[ConcurrencyCallNode]) -> dict[int, float]:
331
- """
332
- For each call, find concurrency at midpoint, then bucket durations by concurrency, compute avg.
333
- """
334
- segs = build_concurrency_segments(roots)
335
- all_nodes = flatten_calls(roots)
336
-
337
- # concurrency => list of durations
338
- from collections import defaultdict
339
- calls_by_conc = defaultdict(list)
340
-
341
- for c in all_nodes:
342
- mc = compute_midpoint_concurrency(c, segs)
343
- # round or cast to int
344
- c_level = int(mc)
345
- calls_by_conc[c_level].append(c.duration)
346
-
347
- result = {}
348
- for c_level, durations in calls_by_conc.items():
349
- if durations:
350
- result[c_level] = float(np.mean(durations))
351
- else:
352
- result[c_level] = 0.0
353
- return result
354
-
355
-
356
- # --------------------------------------------------------------------------------
357
- # 5) Main Analysis Function
358
- # --------------------------------------------------------------------------------
359
-
360
-
361
- def concurrency_spike_analysis(
362
- all_steps: list[list[IntermediateStep]],
363
- concurrency_spike_threshold: int | None = None,
364
- ) -> ConcurrencyAnalysisResult:
365
- """
366
- 1) Build per-example call trees (no cross-example nesting).
367
- 2) Compute concurrency distribution & concurrency segments across *all* calls.
368
- 3) Derive concurrency percentiles (p50, p90, p95, p99).
369
- 4) If threshold not provided, pick e.g. ceil of p90 concurrency.
370
- 5) Detect spikes, gather calls in those intervals => correlation stats.
371
- 6) Also compute average latency by concurrency and add to report.
372
- 7) Return a Pydantic object with everything, plus a textual report.
373
- """
374
- df = create_standardized_dataframe(all_steps)
375
- required_cols = {
376
- "framework",
377
- "llm_name",
378
- "llm_text_input",
379
- "llm_text_output",
380
- "event_timestamp",
381
- "event_type",
382
- "UUID",
383
- "example_number",
384
- "prompt_tokens",
385
- "completion_tokens",
386
- "total_tokens"
387
- }
388
- missing = required_cols - set(df.columns)
389
- if missing:
390
- raise ValueError(f"DataFrame missing required columns: {missing}")
391
-
392
- # Build global forest
393
- roots = build_call_tree_per_example(df)
394
- all_calls = flatten_calls(roots)
395
- num_calls = len(all_calls)
396
-
397
- # Concurrency distribution
398
- dist_map = compute_concurrency_distribution(roots)
399
- total_time = sum(dist_map.values())
400
-
401
- p50_c = find_percentile_concurrency(dist_map, 50)
402
- p90_c = find_percentile_concurrency(dist_map, 90)
403
- p95_c = find_percentile_concurrency(dist_map, 95)
404
- p99_c = find_percentile_concurrency(dist_map, 99)
405
-
406
- # Threshold
407
- if concurrency_spike_threshold is None:
408
- concurrency_spike_threshold = max(1, int(np.ceil(p90_c)))
409
-
410
- # Build concurrency segments, detect spikes
411
- segments = build_concurrency_segments(roots)
412
- spike_intervals = detect_concurrency_spikes(segments, concurrency_spike_threshold)
413
-
414
- # Correlate
415
- corr_stats = correlate_spike_calls(spike_intervals, roots)
416
-
417
- # Average latency by concurrency
418
- avg_lat_by_conc = average_latency_by_midpoint_concurrency(roots)
419
-
420
- # Build textual report
421
- lines = []
422
- lines.append("=== Concurrency Spike Analysis ===")
423
- lines.append(f"Total calls in dataset: {num_calls}")
424
- lines.append(f"Total time observed: {total_time:.2f} units (sum of concurrency timeline)")
425
-
426
- lines.append("\n-- Concurrency Distribution --")
427
- for c_val in sorted(dist_map.keys()):
428
- dur = dist_map[c_val]
429
- lines.append(f" concurrency={c_val}: {dur:.2f} time")
430
-
431
- lines.append(f"\nPercentiles => p50={p50_c:.1f}, p90={p90_c:.1f}, p95={p95_c:.1f}, p99={p99_c:.1f}")
432
- lines.append(f"Spike threshold chosen: {concurrency_spike_threshold}")
433
-
434
- lines.append("\n-- Detected Spike Intervals --")
435
- if not spike_intervals:
436
- lines.append("No intervals exceed concurrency spike threshold.")
437
- else:
438
- for i, sp in enumerate(spike_intervals, start=1):
439
- length = sp.end_time - sp.start_time
440
- active_count = len(sp.active_uuids)
441
- lines.append(f"{i}) {sp.start_time:.2f}-{sp.end_time:.2f}, concurrency={sp.concurrency}, "
442
- f"length={length:.2f}, #active_calls={active_count}")
443
-
444
- lines.append("\n-- Correlation Stats for Spiked Calls --")
445
- lines.append(f"Avg prompt_tokens in spike calls: {corr_stats.avg_prompt_tokens:.1f}")
446
- lines.append(f"Avg total_tokens in spike calls : {corr_stats.avg_total_tokens:.1f}")
447
-
448
- lines.append("\n-- Average Latency by Midpoint Concurrency --")
449
- if not avg_lat_by_conc:
450
- lines.append("No calls or no concurrency data.")
451
- else:
452
- for c_level in sorted(avg_lat_by_conc.keys()):
453
- lat = avg_lat_by_conc[c_level]
454
- lines.append(f" concurrency={c_level} => avg_latency={lat:.2f}")
455
-
456
- final_report = "\n".join(lines)
457
-
458
- # Build result object
459
- return ConcurrencyAnalysisResult(concurrency_distribution=dist_map,
460
- p50_concurrency=p50_c,
461
- p90_concurrency=p90_c,
462
- p95_concurrency=p95_c,
463
- p99_concurrency=p99_c,
464
- spike_threshold=concurrency_spike_threshold,
465
- spike_intervals=spike_intervals,
466
- correlation_stats=corr_stats,
467
- textual_report=final_report,
468
- average_latency_by_concurrency=avg_lat_by_conc)