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.
Files changed (61) hide show
  1. crewforge/__init__.py +3 -0
  2. crewforge/__main__.py +4 -0
  3. crewforge/cli.py +128 -0
  4. crewforge/cli_support.py +109 -0
  5. crewforge/core/__init__.py +54 -0
  6. crewforge/core/agent.py +118 -0
  7. crewforge/core/client.py +253 -0
  8. crewforge/core/config.py +181 -0
  9. crewforge/core/crew.py +144 -0
  10. crewforge/core/errors.py +106 -0
  11. crewforge/core/parsing.py +110 -0
  12. crewforge/core/providers.py +131 -0
  13. crewforge/core/usage.py +163 -0
  14. crewforge/py.typed +0 -0
  15. crewforge/recipes/__init__.py +1 -0
  16. crewforge/recipes/api/__init__.py +38 -0
  17. crewforge/recipes/api/agents.py +523 -0
  18. crewforge/recipes/api/analytics.py +99 -0
  19. crewforge/recipes/api/appcheck.py +73 -0
  20. crewforge/recipes/api/cli.py +127 -0
  21. crewforge/recipes/api/formatters.py +32 -0
  22. crewforge/recipes/api/generator.py +37 -0
  23. crewforge/recipes/api/pipeline.py +86 -0
  24. crewforge/recipes/api/scaffold.py +68 -0
  25. crewforge/recipes/api/templates/docker-compose.j2 +9 -0
  26. crewforge/recipes/api/templates/dockerfile.j2 +7 -0
  27. crewforge/recipes/api/templates/env.example.j2 +2 -0
  28. crewforge/recipes/api/templates/fastapi_main.j2 +10 -0
  29. crewforge/recipes/api/templates/model.j2 +4 -0
  30. crewforge/recipes/api/templates/requirements.j2 +6 -0
  31. crewforge/recipes/api/templates/route.j2 +4 -0
  32. crewforge/recipes/api/templates/test.j2 +3 -0
  33. crewforge/recipes/api/utils.py +134 -0
  34. crewforge/recipes/content/__init__.py +20 -0
  35. crewforge/recipes/content/agents.py +735 -0
  36. crewforge/recipes/content/cli.py +128 -0
  37. crewforge/recipes/content/export.py +84 -0
  38. crewforge/recipes/content/pipeline.py +222 -0
  39. crewforge/recipes/content/settings.py +63 -0
  40. crewforge/recipes/content/text.py +57 -0
  41. crewforge/recipes/docs/__init__.py +48 -0
  42. crewforge/recipes/docs/agents.py +426 -0
  43. crewforge/recipes/docs/cli.py +156 -0
  44. crewforge/recipes/docs/export.py +118 -0
  45. crewforge/recipes/docs/index.py +117 -0
  46. crewforge/recipes/docs/ingest.py +212 -0
  47. crewforge/recipes/docs/pipeline.py +223 -0
  48. crewforge/recipes/docs/retrieve.py +70 -0
  49. crewforge/recipes/docs/settings.py +54 -0
  50. crewforge/recipes/review/__init__.py +1 -0
  51. crewforge/recipes/review/agents.py +331 -0
  52. crewforge/recipes/review/cli.py +144 -0
  53. crewforge/recipes/review/pipeline.py +125 -0
  54. crewforge/recipes/review/report.py +103 -0
  55. crewforge/recipes/review/sources.py +89 -0
  56. crewforge-0.1.0.dist-info/METADATA +372 -0
  57. crewforge-0.1.0.dist-info/RECORD +61 -0
  58. crewforge-0.1.0.dist-info/WHEEL +5 -0
  59. crewforge-0.1.0.dist-info/entry_points.txt +2 -0
  60. crewforge-0.1.0.dist-info/licenses/LICENSE +21 -0
  61. crewforge-0.1.0.dist-info/top_level.txt +1 -0
@@ -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
+ ]