crewforge 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- crewforge/__init__.py +3 -0
- crewforge/__main__.py +4 -0
- crewforge/cli.py +128 -0
- crewforge/cli_support.py +109 -0
- crewforge/core/__init__.py +54 -0
- crewforge/core/agent.py +118 -0
- crewforge/core/client.py +253 -0
- crewforge/core/config.py +181 -0
- crewforge/core/crew.py +144 -0
- crewforge/core/errors.py +106 -0
- crewforge/core/parsing.py +110 -0
- crewforge/core/providers.py +131 -0
- crewforge/core/usage.py +163 -0
- crewforge/py.typed +0 -0
- crewforge/recipes/__init__.py +1 -0
- crewforge/recipes/api/__init__.py +38 -0
- crewforge/recipes/api/agents.py +523 -0
- crewforge/recipes/api/analytics.py +99 -0
- crewforge/recipes/api/appcheck.py +73 -0
- crewforge/recipes/api/cli.py +127 -0
- crewforge/recipes/api/formatters.py +32 -0
- crewforge/recipes/api/generator.py +37 -0
- crewforge/recipes/api/pipeline.py +86 -0
- crewforge/recipes/api/scaffold.py +68 -0
- crewforge/recipes/api/templates/docker-compose.j2 +9 -0
- crewforge/recipes/api/templates/dockerfile.j2 +7 -0
- crewforge/recipes/api/templates/env.example.j2 +2 -0
- crewforge/recipes/api/templates/fastapi_main.j2 +10 -0
- crewforge/recipes/api/templates/model.j2 +4 -0
- crewforge/recipes/api/templates/requirements.j2 +6 -0
- crewforge/recipes/api/templates/route.j2 +4 -0
- crewforge/recipes/api/templates/test.j2 +3 -0
- crewforge/recipes/api/utils.py +134 -0
- crewforge/recipes/content/__init__.py +20 -0
- crewforge/recipes/content/agents.py +735 -0
- crewforge/recipes/content/cli.py +128 -0
- crewforge/recipes/content/export.py +84 -0
- crewforge/recipes/content/pipeline.py +222 -0
- crewforge/recipes/content/settings.py +63 -0
- crewforge/recipes/content/text.py +57 -0
- crewforge/recipes/docs/__init__.py +48 -0
- crewforge/recipes/docs/agents.py +426 -0
- crewforge/recipes/docs/cli.py +156 -0
- crewforge/recipes/docs/export.py +118 -0
- crewforge/recipes/docs/index.py +117 -0
- crewforge/recipes/docs/ingest.py +212 -0
- crewforge/recipes/docs/pipeline.py +223 -0
- crewforge/recipes/docs/retrieve.py +70 -0
- crewforge/recipes/docs/settings.py +54 -0
- crewforge/recipes/review/__init__.py +1 -0
- crewforge/recipes/review/agents.py +331 -0
- crewforge/recipes/review/cli.py +144 -0
- crewforge/recipes/review/pipeline.py +125 -0
- crewforge/recipes/review/report.py +103 -0
- crewforge/recipes/review/sources.py +89 -0
- crewforge-0.1.0.dist-info/METADATA +372 -0
- crewforge-0.1.0.dist-info/RECORD +61 -0
- crewforge-0.1.0.dist-info/WHEEL +5 -0
- crewforge-0.1.0.dist-info/entry_points.txt +2 -0
- crewforge-0.1.0.dist-info/licenses/LICENSE +21 -0
- crewforge-0.1.0.dist-info/top_level.txt +1 -0
crewforge/core/usage.py
ADDED
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
"""Per-agent usage accounting built only from provider-reported numbers."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import time
|
|
7
|
+
from dataclasses import asdict, dataclass
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import Any
|
|
10
|
+
|
|
11
|
+
from .client import Usage
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dataclass
|
|
15
|
+
class AgentUsage:
|
|
16
|
+
agent: str
|
|
17
|
+
calls: int = 0
|
|
18
|
+
prompt_tokens: int = 0
|
|
19
|
+
completion_tokens: int = 0
|
|
20
|
+
total_tokens: int = 0
|
|
21
|
+
cached_tokens: int = 0
|
|
22
|
+
unreported_calls: int = 0 # calls whose response carried no usage object
|
|
23
|
+
errors: int = 0
|
|
24
|
+
retries: int = 0 # extra HTTP attempts (429/5xx/network) behind these calls
|
|
25
|
+
latency_ms: float = 0.0
|
|
26
|
+
|
|
27
|
+
@property
|
|
28
|
+
def avg_latency_ms(self) -> float:
|
|
29
|
+
return self.latency_ms / self.calls if self.calls else 0.0
|
|
30
|
+
|
|
31
|
+
@property
|
|
32
|
+
def tokens_per_call(self) -> float:
|
|
33
|
+
return self.total_tokens / self.calls if self.calls else 0.0
|
|
34
|
+
|
|
35
|
+
@property
|
|
36
|
+
def cache_hit_rate(self) -> float:
|
|
37
|
+
"""Share of prompt tokens the provider reported as served from its cache."""
|
|
38
|
+
return self.cached_tokens / self.prompt_tokens if self.prompt_tokens else 0.0
|
|
39
|
+
|
|
40
|
+
def to_dict(self) -> dict[str, Any]:
|
|
41
|
+
data = asdict(self)
|
|
42
|
+
data["avg_latency_ms"] = round(self.avg_latency_ms, 1)
|
|
43
|
+
data["tokens_per_call"] = round(self.tokens_per_call, 1)
|
|
44
|
+
data["cache_hit_rate"] = round(self.cache_hit_rate, 3)
|
|
45
|
+
data["latency_ms"] = round(self.latency_ms, 1)
|
|
46
|
+
return data
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class UsageTracker:
|
|
50
|
+
"""Collects what each agent cost, as reported by the provider."""
|
|
51
|
+
|
|
52
|
+
def __init__(self) -> None:
|
|
53
|
+
self._agents: dict[str, AgentUsage] = {}
|
|
54
|
+
self._started = time.monotonic()
|
|
55
|
+
self._ended: float | None = None
|
|
56
|
+
self.run_id = time.strftime("%Y%m%d_%H%M%S")
|
|
57
|
+
|
|
58
|
+
def record(
|
|
59
|
+
self,
|
|
60
|
+
agent: str,
|
|
61
|
+
usage: Usage | None,
|
|
62
|
+
latency_ms: float = 0.0,
|
|
63
|
+
*,
|
|
64
|
+
error: bool = False,
|
|
65
|
+
retries: int = 0,
|
|
66
|
+
) -> None:
|
|
67
|
+
entry = self._agents.setdefault(agent, AgentUsage(agent))
|
|
68
|
+
entry.calls += 1
|
|
69
|
+
entry.latency_ms += latency_ms
|
|
70
|
+
entry.retries += retries
|
|
71
|
+
if error:
|
|
72
|
+
entry.errors += 1
|
|
73
|
+
return
|
|
74
|
+
if usage is None or not usage.reported:
|
|
75
|
+
entry.unreported_calls += 1
|
|
76
|
+
return
|
|
77
|
+
entry.prompt_tokens += usage.prompt_tokens
|
|
78
|
+
entry.completion_tokens += usage.completion_tokens
|
|
79
|
+
entry.total_tokens += usage.total_tokens
|
|
80
|
+
entry.cached_tokens += usage.cached_tokens
|
|
81
|
+
|
|
82
|
+
def reset(self) -> None:
|
|
83
|
+
"""Forget everything recorded so far and restart the clock."""
|
|
84
|
+
self._agents.clear()
|
|
85
|
+
self._started = time.monotonic()
|
|
86
|
+
self._ended = None
|
|
87
|
+
|
|
88
|
+
def finish(self) -> None:
|
|
89
|
+
self._ended = time.monotonic()
|
|
90
|
+
|
|
91
|
+
def by_agent(self) -> dict[str, AgentUsage]:
|
|
92
|
+
return dict(self._agents)
|
|
93
|
+
|
|
94
|
+
@property
|
|
95
|
+
def total_tokens(self) -> int:
|
|
96
|
+
return sum(a.total_tokens for a in self._agents.values())
|
|
97
|
+
|
|
98
|
+
@property
|
|
99
|
+
def prompt_tokens(self) -> int:
|
|
100
|
+
return sum(a.prompt_tokens for a in self._agents.values())
|
|
101
|
+
|
|
102
|
+
@property
|
|
103
|
+
def completion_tokens(self) -> int:
|
|
104
|
+
return sum(a.completion_tokens for a in self._agents.values())
|
|
105
|
+
|
|
106
|
+
@property
|
|
107
|
+
def total_calls(self) -> int:
|
|
108
|
+
return sum(a.calls for a in self._agents.values())
|
|
109
|
+
|
|
110
|
+
@property
|
|
111
|
+
def unreported_calls(self) -> int:
|
|
112
|
+
return sum(a.unreported_calls for a in self._agents.values())
|
|
113
|
+
|
|
114
|
+
@property
|
|
115
|
+
def errors(self) -> int:
|
|
116
|
+
return sum(a.errors for a in self._agents.values())
|
|
117
|
+
|
|
118
|
+
@property
|
|
119
|
+
def duration_s(self) -> float:
|
|
120
|
+
return (self._ended or time.monotonic()) - self._started
|
|
121
|
+
|
|
122
|
+
def summary(self) -> dict[str, Any]:
|
|
123
|
+
return {
|
|
124
|
+
"run_id": self.run_id,
|
|
125
|
+
"duration_s": round(self.duration_s, 2),
|
|
126
|
+
"calls": self.total_calls,
|
|
127
|
+
"prompt_tokens": self.prompt_tokens,
|
|
128
|
+
"completion_tokens": self.completion_tokens,
|
|
129
|
+
"total_tokens": self.total_tokens,
|
|
130
|
+
"unreported_calls": self.unreported_calls,
|
|
131
|
+
"errors": self.errors,
|
|
132
|
+
"agents": {name: a.to_dict() for name, a in self._agents.items()},
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
def one_line(self) -> str:
|
|
136
|
+
"""e.g. ``7 LLM calls · 12,345 tokens (provider-reported) · 21.4s``."""
|
|
137
|
+
text = (
|
|
138
|
+
f"{self.total_calls} LLM call(s) · {self.total_tokens:,} tokens "
|
|
139
|
+
f"(provider-reported) · {self.duration_s:.1f}s"
|
|
140
|
+
)
|
|
141
|
+
if self.unreported_calls:
|
|
142
|
+
text += f" · {self.unreported_calls} call(s) returned no usage (not counted)"
|
|
143
|
+
return text
|
|
144
|
+
|
|
145
|
+
def report(self) -> str:
|
|
146
|
+
"""Plain-text per-agent table (for logs and non-terminal output)."""
|
|
147
|
+
lines = [
|
|
148
|
+
f"Token usage — run {self.run_id} (provider-reported)",
|
|
149
|
+
f"{'agent':<16}{'calls':>6}{'prompt':>10}{'completion':>12}{'total':>10}{'ms/call':>9}",
|
|
150
|
+
]
|
|
151
|
+
for a in self._agents.values():
|
|
152
|
+
lines.append(
|
|
153
|
+
f"{a.agent:<16}{a.calls:>6}{a.prompt_tokens:>10,}{a.completion_tokens:>12,}"
|
|
154
|
+
f"{a.total_tokens:>10,}{a.avg_latency_ms:>9.0f}"
|
|
155
|
+
)
|
|
156
|
+
lines.append(self.one_line())
|
|
157
|
+
return "\n".join(lines)
|
|
158
|
+
|
|
159
|
+
def save(self, path: str | Path) -> Path:
|
|
160
|
+
out = Path(path)
|
|
161
|
+
out.parent.mkdir(parents=True, exist_ok=True)
|
|
162
|
+
out.write_text(json.dumps(self.summary(), indent=2), encoding="utf-8")
|
|
163
|
+
return out
|
crewforge/py.typed
ADDED
|
File without changes
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""The four recipes: review (code), docs (Q&A), api (scaffolding), content (articles)."""
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"""``api`` recipe (from apiforge): natural language -> FastAPI project + OpenAPI spec.
|
|
2
|
+
|
|
3
|
+
The parser, router, model, auth and docs agents call the LLM; the schema,
|
|
4
|
+
test and validator agents are deterministic.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from .agents import (
|
|
8
|
+
APIDescription,
|
|
9
|
+
AuthAgent,
|
|
10
|
+
DocAgent,
|
|
11
|
+
ModelAgent,
|
|
12
|
+
ParsedEndpoint,
|
|
13
|
+
ParserAgent,
|
|
14
|
+
RouterAgent,
|
|
15
|
+
SchemaAgent,
|
|
16
|
+
TestAgent,
|
|
17
|
+
ValidateAgent,
|
|
18
|
+
ValidationResult,
|
|
19
|
+
)
|
|
20
|
+
from .pipeline import ApiPipeline, PipelineResult
|
|
21
|
+
from .scaffold import ProjectScaffold
|
|
22
|
+
|
|
23
|
+
__all__ = [
|
|
24
|
+
"APIDescription",
|
|
25
|
+
"ApiPipeline",
|
|
26
|
+
"AuthAgent",
|
|
27
|
+
"DocAgent",
|
|
28
|
+
"ModelAgent",
|
|
29
|
+
"ParsedEndpoint",
|
|
30
|
+
"ParserAgent",
|
|
31
|
+
"PipelineResult",
|
|
32
|
+
"ProjectScaffold",
|
|
33
|
+
"RouterAgent",
|
|
34
|
+
"SchemaAgent",
|
|
35
|
+
"TestAgent",
|
|
36
|
+
"ValidateAgent",
|
|
37
|
+
"ValidationResult",
|
|
38
|
+
]
|