agentfluent 0.1.0__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.
- agentfluent-0.1.0/LICENSE +21 -0
- agentfluent-0.1.0/PKG-INFO +52 -0
- agentfluent-0.1.0/README.md +22 -0
- agentfluent-0.1.0/pyproject.toml +78 -0
- agentfluent-0.1.0/src/agentfluent/__init__.py +8 -0
- agentfluent-0.1.0/src/agentfluent/agents/__init__.py +0 -0
- agentfluent-0.1.0/src/agentfluent/agents/extractor.py +78 -0
- agentfluent-0.1.0/src/agentfluent/agents/models.py +71 -0
- agentfluent-0.1.0/src/agentfluent/analytics/__init__.py +0 -0
- agentfluent-0.1.0/src/agentfluent/analytics/agent_metrics.py +127 -0
- agentfluent-0.1.0/src/agentfluent/analytics/pipeline.py +278 -0
- agentfluent-0.1.0/src/agentfluent/analytics/pricing.py +96 -0
- agentfluent-0.1.0/src/agentfluent/analytics/tokens.py +137 -0
- agentfluent-0.1.0/src/agentfluent/analytics/tools.py +93 -0
- agentfluent-0.1.0/src/agentfluent/cli/__init__.py +0 -0
- agentfluent-0.1.0/src/agentfluent/cli/commands/__init__.py +0 -0
- agentfluent-0.1.0/src/agentfluent/cli/commands/analyze.py +162 -0
- agentfluent-0.1.0/src/agentfluent/cli/commands/config_check.py +110 -0
- agentfluent-0.1.0/src/agentfluent/cli/commands/list_cmd.py +165 -0
- agentfluent-0.1.0/src/agentfluent/cli/exit_codes.py +18 -0
- agentfluent-0.1.0/src/agentfluent/cli/formatters/__init__.py +0 -0
- agentfluent-0.1.0/src/agentfluent/cli/formatters/helpers.py +59 -0
- agentfluent-0.1.0/src/agentfluent/cli/formatters/json_output.py +60 -0
- agentfluent-0.1.0/src/agentfluent/cli/formatters/table.py +358 -0
- agentfluent-0.1.0/src/agentfluent/cli/main.py +63 -0
- agentfluent-0.1.0/src/agentfluent/config/__init__.py +31 -0
- agentfluent-0.1.0/src/agentfluent/config/models.py +118 -0
- agentfluent-0.1.0/src/agentfluent/config/scanner.py +146 -0
- agentfluent-0.1.0/src/agentfluent/config/scoring.py +299 -0
- agentfluent-0.1.0/src/agentfluent/core/__init__.py +0 -0
- agentfluent-0.1.0/src/agentfluent/core/discovery.py +158 -0
- agentfluent-0.1.0/src/agentfluent/core/parser.py +251 -0
- agentfluent-0.1.0/src/agentfluent/core/session.py +133 -0
- agentfluent-0.1.0/src/agentfluent/diagnostics/__init__.py +51 -0
- agentfluent-0.1.0/src/agentfluent/diagnostics/correlator.py +248 -0
- agentfluent-0.1.0/src/agentfluent/diagnostics/models.py +75 -0
- agentfluent-0.1.0/src/agentfluent/diagnostics/signals.py +162 -0
- agentfluent-0.1.0/src/agentfluent/py.typed +0 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Fred Pearce
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: agentfluent
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Local-first agent analytics with prompt diagnostics
|
|
5
|
+
Keywords: claude,agent,analytics,cli,llm,diagnostics
|
|
6
|
+
Author: Fred Pearce
|
|
7
|
+
Author-email: Fred Pearce <fpearce@gmail.com>
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Environment :: Console
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Operating System :: OS Independent
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
17
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
18
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
19
|
+
Classifier: Typing :: Typed
|
|
20
|
+
Requires-Dist: typer>=0.15
|
|
21
|
+
Requires-Dist: rich>=14.0
|
|
22
|
+
Requires-Dist: pydantic>=2.0
|
|
23
|
+
Requires-Dist: pyyaml>=6.0
|
|
24
|
+
Requires-Python: >=3.12
|
|
25
|
+
Project-URL: Homepage, https://github.com/frederick-douglas-pearce/agentfluent
|
|
26
|
+
Project-URL: Repository, https://github.com/frederick-douglas-pearce/agentfluent
|
|
27
|
+
Project-URL: Issues, https://github.com/frederick-douglas-pearce/agentfluent/issues
|
|
28
|
+
Project-URL: Changelog, https://github.com/frederick-douglas-pearce/agentfluent/blob/main/CHANGELOG.md
|
|
29
|
+
Description-Content-Type: text/markdown
|
|
30
|
+
|
|
31
|
+
# AgentFluent
|
|
32
|
+
|
|
33
|
+
Local-first agent analytics with prompt diagnostics. The tools that exist tell you what your agent did. This tool tells you what to change.
|
|
34
|
+
|
|
35
|
+
## What Is This?
|
|
36
|
+
|
|
37
|
+
AgentFluent analyzes AI agent session data (Claude Code subagents + Agent SDK) to provide:
|
|
38
|
+
|
|
39
|
+
- **Agent execution analytics** -- token usage, cost tracking, tool call patterns, error rates
|
|
40
|
+
- **Agent prompt diagnostics** -- score system prompts against best practices, correlate agent behavior to prompt quality issues, generate specific improvement recommendations
|
|
41
|
+
- **Prompt regression detection** -- compare agent behavior across prompt versions
|
|
42
|
+
- **Agent configuration assessment** -- tool access audit, model selection analysis, hook coverage
|
|
43
|
+
|
|
44
|
+
## Background
|
|
45
|
+
|
|
46
|
+
Born from [CodeFluent](https://github.com/frederick-douglas-pearce/codefluent) research identifying a gap in the agent observability market: existing tools monitor agent execution (traces, latency, cost) but none evaluate agent *quality* or diagnose *prompt-level issues* from local session data.
|
|
47
|
+
|
|
48
|
+
See `docs/AGENT_ANALYTICS_RESEARCH.md` for the full market analysis and technical feasibility study.
|
|
49
|
+
|
|
50
|
+
## Status
|
|
51
|
+
|
|
52
|
+
Early stage -- defining scope, architecture, and initial backlog.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# AgentFluent
|
|
2
|
+
|
|
3
|
+
Local-first agent analytics with prompt diagnostics. The tools that exist tell you what your agent did. This tool tells you what to change.
|
|
4
|
+
|
|
5
|
+
## What Is This?
|
|
6
|
+
|
|
7
|
+
AgentFluent analyzes AI agent session data (Claude Code subagents + Agent SDK) to provide:
|
|
8
|
+
|
|
9
|
+
- **Agent execution analytics** -- token usage, cost tracking, tool call patterns, error rates
|
|
10
|
+
- **Agent prompt diagnostics** -- score system prompts against best practices, correlate agent behavior to prompt quality issues, generate specific improvement recommendations
|
|
11
|
+
- **Prompt regression detection** -- compare agent behavior across prompt versions
|
|
12
|
+
- **Agent configuration assessment** -- tool access audit, model selection analysis, hook coverage
|
|
13
|
+
|
|
14
|
+
## Background
|
|
15
|
+
|
|
16
|
+
Born from [CodeFluent](https://github.com/frederick-douglas-pearce/codefluent) research identifying a gap in the agent observability market: existing tools monitor agent execution (traces, latency, cost) but none evaluate agent *quality* or diagnose *prompt-level issues* from local session data.
|
|
17
|
+
|
|
18
|
+
See `docs/AGENT_ANALYTICS_RESEARCH.md` for the full market analysis and technical feasibility study.
|
|
19
|
+
|
|
20
|
+
## Status
|
|
21
|
+
|
|
22
|
+
Early stage -- defining scope, architecture, and initial backlog.
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "agentfluent"
|
|
3
|
+
version = "0.1.0" # x-release-please-version
|
|
4
|
+
description = "Local-first agent analytics with prompt diagnostics"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
license = "MIT"
|
|
7
|
+
license-files = ["LICENSE"]
|
|
8
|
+
authors = [
|
|
9
|
+
{ name = "Fred Pearce", email = "fpearce@gmail.com" }
|
|
10
|
+
]
|
|
11
|
+
requires-python = ">=3.12"
|
|
12
|
+
keywords = ["claude", "agent", "analytics", "cli", "llm", "diagnostics"]
|
|
13
|
+
classifiers = [
|
|
14
|
+
"Development Status :: 3 - Alpha",
|
|
15
|
+
"Environment :: Console",
|
|
16
|
+
"Intended Audience :: Developers",
|
|
17
|
+
"Operating System :: OS Independent",
|
|
18
|
+
"Programming Language :: Python :: 3",
|
|
19
|
+
"Programming Language :: Python :: 3.12",
|
|
20
|
+
"Programming Language :: Python :: 3.13",
|
|
21
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
22
|
+
"Topic :: Scientific/Engineering :: Artificial Intelligence",
|
|
23
|
+
"Typing :: Typed",
|
|
24
|
+
]
|
|
25
|
+
dependencies = [
|
|
26
|
+
"typer>=0.15",
|
|
27
|
+
"rich>=14.0",
|
|
28
|
+
"pydantic>=2.0",
|
|
29
|
+
"pyyaml>=6.0",
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
[project.urls]
|
|
33
|
+
Homepage = "https://github.com/frederick-douglas-pearce/agentfluent"
|
|
34
|
+
Repository = "https://github.com/frederick-douglas-pearce/agentfluent"
|
|
35
|
+
Issues = "https://github.com/frederick-douglas-pearce/agentfluent/issues"
|
|
36
|
+
Changelog = "https://github.com/frederick-douglas-pearce/agentfluent/blob/main/CHANGELOG.md"
|
|
37
|
+
|
|
38
|
+
[project.scripts]
|
|
39
|
+
agentfluent = "agentfluent.cli.main:app"
|
|
40
|
+
|
|
41
|
+
[dependency-groups]
|
|
42
|
+
dev = [
|
|
43
|
+
"pytest>=8.0",
|
|
44
|
+
"pytest-cov>=6.0",
|
|
45
|
+
"ruff>=0.11",
|
|
46
|
+
"mypy>=1.15",
|
|
47
|
+
"types-pyyaml>=6.0",
|
|
48
|
+
]
|
|
49
|
+
|
|
50
|
+
[build-system]
|
|
51
|
+
requires = ["uv_build>=0.9.5,<0.12.0"]
|
|
52
|
+
build-backend = "uv_build"
|
|
53
|
+
|
|
54
|
+
[tool.pytest.ini_options]
|
|
55
|
+
testpaths = ["tests"]
|
|
56
|
+
markers = [
|
|
57
|
+
"integration: tests that require real session data (deselect with '-m \"not integration\"')",
|
|
58
|
+
]
|
|
59
|
+
|
|
60
|
+
[tool.coverage.run]
|
|
61
|
+
source = ["agentfluent"]
|
|
62
|
+
|
|
63
|
+
[tool.coverage.report]
|
|
64
|
+
show_missing = true
|
|
65
|
+
skip_empty = true
|
|
66
|
+
|
|
67
|
+
[tool.ruff]
|
|
68
|
+
target-version = "py312"
|
|
69
|
+
line-length = 100
|
|
70
|
+
|
|
71
|
+
[tool.ruff.lint]
|
|
72
|
+
select = ["E", "F", "I", "N", "W", "UP"]
|
|
73
|
+
|
|
74
|
+
[tool.mypy]
|
|
75
|
+
python_version = "3.12"
|
|
76
|
+
strict = true
|
|
77
|
+
warn_return_any = true
|
|
78
|
+
warn_unused_configs = true
|
|
File without changes
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"""Extract agent invocations from parsed session messages.
|
|
2
|
+
|
|
3
|
+
Identifies Agent tool_use blocks in assistant messages and matches them
|
|
4
|
+
to their corresponding tool_result blocks to build AgentInvocation objects.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from agentfluent.agents.models import AgentInvocation, is_builtin_agent
|
|
10
|
+
from agentfluent.core.session import SessionMessage
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def extract_agent_invocations(messages: list[SessionMessage]) -> list[AgentInvocation]:
|
|
14
|
+
"""Extract agent invocations from a list of parsed session messages.
|
|
15
|
+
|
|
16
|
+
Scans for assistant messages containing Agent tool_use blocks (name == "Agent"),
|
|
17
|
+
then matches each to its corresponding tool_result by tool_use_id.
|
|
18
|
+
|
|
19
|
+
Args:
|
|
20
|
+
messages: Parsed session messages from the JSONL parser.
|
|
21
|
+
|
|
22
|
+
Returns:
|
|
23
|
+
List of AgentInvocation objects in session order.
|
|
24
|
+
"""
|
|
25
|
+
# Build a lookup of tool_result messages by tool_use_id
|
|
26
|
+
tool_results: dict[str, SessionMessage] = {}
|
|
27
|
+
for msg in messages:
|
|
28
|
+
if msg.type == "tool_result" and msg.tool_use_id:
|
|
29
|
+
tool_results[msg.tool_use_id] = msg
|
|
30
|
+
|
|
31
|
+
invocations: list[AgentInvocation] = []
|
|
32
|
+
|
|
33
|
+
for msg in messages:
|
|
34
|
+
if msg.type != "assistant":
|
|
35
|
+
continue
|
|
36
|
+
|
|
37
|
+
for tool_use in msg.tool_use_blocks:
|
|
38
|
+
if tool_use.name != "Agent":
|
|
39
|
+
continue
|
|
40
|
+
|
|
41
|
+
agent_type = tool_use.input.get("subagent_type", "unknown")
|
|
42
|
+
description = tool_use.input.get("description", "")
|
|
43
|
+
prompt = tool_use.input.get("prompt", "")
|
|
44
|
+
|
|
45
|
+
# Match to tool_result
|
|
46
|
+
result = tool_results.get(tool_use.id)
|
|
47
|
+
|
|
48
|
+
total_tokens = None
|
|
49
|
+
tool_uses_count = None
|
|
50
|
+
duration_ms = None
|
|
51
|
+
agent_id = None
|
|
52
|
+
output_text = ""
|
|
53
|
+
|
|
54
|
+
if result is not None:
|
|
55
|
+
output_text = result.text
|
|
56
|
+
|
|
57
|
+
if result.metadata is not None:
|
|
58
|
+
total_tokens = result.metadata.total_tokens
|
|
59
|
+
tool_uses_count = result.metadata.tool_uses
|
|
60
|
+
duration_ms = result.metadata.duration_ms
|
|
61
|
+
agent_id = result.metadata.agent_id
|
|
62
|
+
|
|
63
|
+
invocations.append(
|
|
64
|
+
AgentInvocation(
|
|
65
|
+
agent_type=agent_type,
|
|
66
|
+
is_builtin=is_builtin_agent(agent_type),
|
|
67
|
+
description=description,
|
|
68
|
+
prompt=prompt,
|
|
69
|
+
tool_use_id=tool_use.id,
|
|
70
|
+
total_tokens=total_tokens,
|
|
71
|
+
tool_uses=tool_uses_count,
|
|
72
|
+
duration_ms=duration_ms,
|
|
73
|
+
agent_id=agent_id,
|
|
74
|
+
output_text=output_text,
|
|
75
|
+
)
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
return invocations
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"""Data models for agent invocations extracted from session data."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
|
|
7
|
+
# Built-in agent types (case-insensitive matching).
|
|
8
|
+
# Update this set as Anthropic adds new built-in agents.
|
|
9
|
+
BUILTIN_AGENT_TYPES: frozenset[str] = frozenset(
|
|
10
|
+
{
|
|
11
|
+
"explore",
|
|
12
|
+
"plan",
|
|
13
|
+
"general-purpose",
|
|
14
|
+
"code-reviewer",
|
|
15
|
+
"statusline-setup",
|
|
16
|
+
"claude-code-guide",
|
|
17
|
+
}
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def is_builtin_agent(agent_type: str) -> bool:
|
|
22
|
+
"""Check if an agent type is a built-in Claude Code agent."""
|
|
23
|
+
return agent_type.lower() in BUILTIN_AGENT_TYPES
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@dataclass
|
|
27
|
+
class AgentInvocation:
|
|
28
|
+
"""A single agent invocation extracted from a session.
|
|
29
|
+
|
|
30
|
+
Combines data from the Agent tool_use block (in the assistant message)
|
|
31
|
+
with the corresponding tool_result (including metadata).
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
agent_type: str
|
|
35
|
+
"""Agent type (e.g., 'pm', 'Explore', 'Plan')."""
|
|
36
|
+
|
|
37
|
+
is_builtin: bool
|
|
38
|
+
"""Whether this is a built-in Claude Code agent."""
|
|
39
|
+
|
|
40
|
+
description: str
|
|
41
|
+
"""The description passed to the Agent tool."""
|
|
42
|
+
|
|
43
|
+
prompt: str
|
|
44
|
+
"""The delegation prompt sent to the agent."""
|
|
45
|
+
|
|
46
|
+
tool_use_id: str
|
|
47
|
+
"""The tool_use ID linking this invocation to its tool_result."""
|
|
48
|
+
|
|
49
|
+
# From tool_result metadata (may be None if no metadata or agent was interrupted)
|
|
50
|
+
total_tokens: int | None = None
|
|
51
|
+
tool_uses: int | None = None
|
|
52
|
+
duration_ms: int | None = None
|
|
53
|
+
agent_id: str | None = None
|
|
54
|
+
|
|
55
|
+
# From tool_result content
|
|
56
|
+
output_text: str = ""
|
|
57
|
+
"""The agent's final summary/output text."""
|
|
58
|
+
|
|
59
|
+
@property
|
|
60
|
+
def tokens_per_tool_use(self) -> float | None:
|
|
61
|
+
"""Average tokens per tool call. None if data unavailable."""
|
|
62
|
+
if self.total_tokens is not None and self.tool_uses and self.tool_uses > 0:
|
|
63
|
+
return self.total_tokens / self.tool_uses
|
|
64
|
+
return None
|
|
65
|
+
|
|
66
|
+
@property
|
|
67
|
+
def duration_per_tool_use(self) -> float | None:
|
|
68
|
+
"""Average duration (ms) per tool call. None if data unavailable."""
|
|
69
|
+
if self.duration_ms is not None and self.tool_uses and self.tool_uses > 0:
|
|
70
|
+
return self.duration_ms / self.tool_uses
|
|
71
|
+
return None
|
|
File without changes
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
"""Per-agent execution metrics.
|
|
2
|
+
|
|
3
|
+
Computes invocation counts, token usage, and efficiency metrics
|
|
4
|
+
grouped by agent type. Reports token counts and percentages rather
|
|
5
|
+
than dollar costs, since tool_result metadata only provides aggregate
|
|
6
|
+
total_tokens without the per-category breakdown needed for accurate
|
|
7
|
+
cost estimation.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from dataclasses import dataclass, field
|
|
13
|
+
|
|
14
|
+
from agentfluent.agents.models import AgentInvocation
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@dataclass
|
|
18
|
+
class AgentTypeMetrics:
|
|
19
|
+
"""Execution metrics for a single agent type."""
|
|
20
|
+
|
|
21
|
+
agent_type: str
|
|
22
|
+
is_builtin: bool
|
|
23
|
+
invocation_count: int = 0
|
|
24
|
+
total_tokens: int = 0
|
|
25
|
+
total_tool_uses: int = 0
|
|
26
|
+
total_duration_ms: int = 0
|
|
27
|
+
avg_tokens_per_tool_use: float | None = None
|
|
28
|
+
avg_duration_per_tool_use: float | None = None
|
|
29
|
+
|
|
30
|
+
@property
|
|
31
|
+
def avg_tokens_per_invocation(self) -> float | None:
|
|
32
|
+
if self.invocation_count > 0 and self.total_tokens > 0:
|
|
33
|
+
return self.total_tokens / self.invocation_count
|
|
34
|
+
return None
|
|
35
|
+
|
|
36
|
+
@property
|
|
37
|
+
def avg_duration_per_invocation(self) -> float | None:
|
|
38
|
+
if self.invocation_count > 0 and self.total_duration_ms > 0:
|
|
39
|
+
return self.total_duration_ms / self.invocation_count
|
|
40
|
+
return None
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@dataclass
|
|
44
|
+
class AgentMetrics:
|
|
45
|
+
"""Aggregated agent execution metrics for a session."""
|
|
46
|
+
|
|
47
|
+
by_agent_type: dict[str, AgentTypeMetrics] = field(default_factory=dict)
|
|
48
|
+
"""Per-agent-type metrics, keyed by agent_type."""
|
|
49
|
+
|
|
50
|
+
total_invocations: int = 0
|
|
51
|
+
total_agent_tokens: int = 0
|
|
52
|
+
total_agent_duration_ms: int = 0
|
|
53
|
+
|
|
54
|
+
builtin_invocations: int = 0
|
|
55
|
+
custom_invocations: int = 0
|
|
56
|
+
|
|
57
|
+
agent_token_percentage: float = 0.0
|
|
58
|
+
"""Agent tokens as percentage of session total tokens (0-100).
|
|
59
|
+
Set by the caller who has session-level token data."""
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def compute_agent_metrics(
|
|
63
|
+
invocations: list[AgentInvocation],
|
|
64
|
+
session_total_tokens: int = 0,
|
|
65
|
+
) -> AgentMetrics:
|
|
66
|
+
"""Compute per-agent-type execution metrics from extracted invocations.
|
|
67
|
+
|
|
68
|
+
Groups invocations by agent_type and computes counts, totals, and averages.
|
|
69
|
+
Invocations with missing metadata are counted but excluded from averages.
|
|
70
|
+
|
|
71
|
+
Args:
|
|
72
|
+
invocations: Extracted agent invocations from a session.
|
|
73
|
+
session_total_tokens: Total session tokens for computing agent percentage.
|
|
74
|
+
|
|
75
|
+
Returns:
|
|
76
|
+
AgentMetrics with per-type breakdowns and summary totals.
|
|
77
|
+
"""
|
|
78
|
+
by_type: dict[str, AgentTypeMetrics] = {}
|
|
79
|
+
|
|
80
|
+
for inv in invocations:
|
|
81
|
+
key = inv.agent_type.lower()
|
|
82
|
+
metrics = by_type.get(key)
|
|
83
|
+
if metrics is None:
|
|
84
|
+
metrics = AgentTypeMetrics(agent_type=inv.agent_type, is_builtin=inv.is_builtin)
|
|
85
|
+
by_type[key] = metrics
|
|
86
|
+
|
|
87
|
+
metrics.invocation_count += 1
|
|
88
|
+
|
|
89
|
+
if inv.total_tokens is not None:
|
|
90
|
+
metrics.total_tokens += inv.total_tokens
|
|
91
|
+
if inv.tool_uses is not None:
|
|
92
|
+
metrics.total_tool_uses += inv.tool_uses
|
|
93
|
+
if inv.duration_ms is not None:
|
|
94
|
+
metrics.total_duration_ms += inv.duration_ms
|
|
95
|
+
|
|
96
|
+
# Compute averages
|
|
97
|
+
for metrics in by_type.values():
|
|
98
|
+
if metrics.total_tool_uses > 0:
|
|
99
|
+
if metrics.total_tokens > 0:
|
|
100
|
+
metrics.avg_tokens_per_tool_use = metrics.total_tokens / metrics.total_tool_uses
|
|
101
|
+
if metrics.total_duration_ms > 0:
|
|
102
|
+
metrics.avg_duration_per_tool_use = (
|
|
103
|
+
metrics.total_duration_ms / metrics.total_tool_uses
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
# Aggregate totals
|
|
107
|
+
total_invocations = sum(m.invocation_count for m in by_type.values())
|
|
108
|
+
total_agent_tokens = sum(m.total_tokens for m in by_type.values())
|
|
109
|
+
total_agent_duration = sum(m.total_duration_ms for m in by_type.values())
|
|
110
|
+
builtin_count = sum(m.invocation_count for m in by_type.values() if m.is_builtin)
|
|
111
|
+
custom_count = sum(m.invocation_count for m in by_type.values() if not m.is_builtin)
|
|
112
|
+
|
|
113
|
+
agent_token_pct = (
|
|
114
|
+
round(total_agent_tokens / session_total_tokens * 100, 1)
|
|
115
|
+
if session_total_tokens > 0 and total_agent_tokens > 0
|
|
116
|
+
else 0.0
|
|
117
|
+
)
|
|
118
|
+
|
|
119
|
+
return AgentMetrics(
|
|
120
|
+
by_agent_type=by_type,
|
|
121
|
+
total_invocations=total_invocations,
|
|
122
|
+
total_agent_tokens=total_agent_tokens,
|
|
123
|
+
total_agent_duration_ms=total_agent_duration,
|
|
124
|
+
builtin_invocations=builtin_count,
|
|
125
|
+
custom_invocations=custom_count,
|
|
126
|
+
agent_token_percentage=agent_token_pct,
|
|
127
|
+
)
|