parseforge 0.2.12__tar.gz → 0.3.1__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.
- parseforge-0.3.1/PKG-INFO +199 -0
- parseforge-0.3.1/README.md +95 -0
- {parseforge-0.2.12 → parseforge-0.3.1}/SPEC.md +2 -2
- parseforge-0.3.1/parseforge/__init__.py +96 -0
- parseforge-0.3.1/parseforge/api.py +127 -0
- {parseforge-0.2.12 → parseforge-0.3.1}/parseforge/cli/main.py +2 -1
- {parseforge-0.2.12 → parseforge-0.3.1}/parseforge/generation.py +0 -2
- {parseforge-0.2.12 → parseforge-0.3.1}/parseforge/naming/llm.py +0 -1
- {parseforge-0.2.12 → parseforge-0.3.1}/parseforge/naming/providers/anthropic.py +1 -11
- {parseforge-0.2.12 → parseforge-0.3.1}/parseforge/naming/providers/azure.py +1 -16
- {parseforge-0.2.12 → parseforge-0.3.1}/parseforge/naming/providers/bedrock.py +1 -11
- {parseforge-0.2.12 → parseforge-0.3.1}/parseforge/naming/providers/cerebras.py +1 -11
- {parseforge-0.2.12 → parseforge-0.3.1}/parseforge/naming/providers/cohere.py +1 -11
- {parseforge-0.2.12 → parseforge-0.3.1}/parseforge/naming/providers/deepseek.py +1 -11
- {parseforge-0.2.12 → parseforge-0.3.1}/parseforge/naming/providers/fireworks.py +1 -11
- {parseforge-0.2.12 → parseforge-0.3.1}/parseforge/naming/providers/gemini.py +1 -11
- {parseforge-0.2.12 → parseforge-0.3.1}/parseforge/naming/providers/groq.py +1 -11
- {parseforge-0.2.12 → parseforge-0.3.1}/parseforge/naming/providers/mistral.py +1 -11
- parseforge-0.3.1/parseforge/naming/providers/models.py +17 -0
- parseforge-0.3.1/parseforge/naming/providers/models.yaml +30 -0
- {parseforge-0.2.12 → parseforge-0.3.1}/parseforge/naming/providers/moonshot.py +1 -11
- {parseforge-0.2.12 → parseforge-0.3.1}/parseforge/naming/providers/oci.py +1 -11
- {parseforge-0.2.12 → parseforge-0.3.1}/parseforge/naming/providers/openai.py +1 -11
- {parseforge-0.2.12 → parseforge-0.3.1}/parseforge/naming/providers/openrouter.py +1 -11
- {parseforge-0.2.12 → parseforge-0.3.1}/parseforge/naming/providers/perplexity.py +1 -11
- {parseforge-0.2.12 → parseforge-0.3.1}/parseforge/naming/providers/together.py +1 -11
- {parseforge-0.2.12 → parseforge-0.3.1}/parseforge/naming/providers/vertexai.py +1 -11
- {parseforge-0.2.12 → parseforge-0.3.1}/parseforge/naming/providers/xai.py +1 -11
- {parseforge-0.2.12 → parseforge-0.3.1}/parseforge/pipeline.py +25 -3
- parseforge-0.3.1/parseforge.egg-info/PKG-INFO +199 -0
- {parseforge-0.2.12 → parseforge-0.3.1}/parseforge.egg-info/SOURCES.txt +1 -1
- {parseforge-0.2.12 → parseforge-0.3.1}/parseforge.egg-info/requires.txt +19 -19
- {parseforge-0.2.12 → parseforge-0.3.1}/pyproject.toml +26 -20
- parseforge-0.2.12/PKG-INFO +0 -339
- parseforge-0.2.12/README.md +0 -235
- parseforge-0.2.12/parseforge/__init__.py +0 -1
- parseforge-0.2.12/parseforge/naming/providers/cost.py +0 -29
- parseforge-0.2.12/parseforge/naming/providers/models.py +0 -26
- parseforge-0.2.12/parseforge/naming/providers/models.yaml +0 -182
- parseforge-0.2.12/parseforge.egg-info/PKG-INFO +0 -339
- {parseforge-0.2.12 → parseforge-0.3.1}/LICENSE +0 -0
- {parseforge-0.2.12 → parseforge-0.3.1}/MANIFEST.in +0 -0
- {parseforge-0.2.12 → parseforge-0.3.1}/parseforge/cli/__init__.py +0 -0
- {parseforge-0.2.12 → parseforge-0.3.1}/parseforge/cli/config.py +0 -0
- {parseforge-0.2.12 → parseforge-0.3.1}/parseforge/drift.py +0 -0
- {parseforge-0.2.12 → parseforge-0.3.1}/parseforge/integration.py +0 -0
- {parseforge-0.2.12 → parseforge-0.3.1}/parseforge/naming/__init__.py +0 -0
- {parseforge-0.2.12 → parseforge-0.3.1}/parseforge/naming/assemble.py +0 -0
- {parseforge-0.2.12 → parseforge-0.3.1}/parseforge/naming/cache.py +0 -0
- {parseforge-0.2.12 → parseforge-0.3.1}/parseforge/naming/prompts.py +0 -0
- {parseforge-0.2.12 → parseforge-0.3.1}/parseforge/naming/prompts.yaml +0 -0
- {parseforge-0.2.12 → parseforge-0.3.1}/parseforge/naming/providers/__init__.py +0 -0
- {parseforge-0.2.12 → parseforge-0.3.1}/parseforge/naming/providers/errors.py +0 -0
- {parseforge-0.2.12 → parseforge-0.3.1}/parseforge/naming/providers/text.py +0 -0
- {parseforge-0.2.12 → parseforge-0.3.1}/parseforge/naming/resolver.py +0 -0
- {parseforge-0.2.12 → parseforge-0.3.1}/parseforge/paths.py +0 -0
- {parseforge-0.2.12 → parseforge-0.3.1}/parseforge/promotion.py +0 -0
- {parseforge-0.2.12 → parseforge-0.3.1}/parseforge/sampling/__init__.py +0 -0
- {parseforge-0.2.12 → parseforge-0.3.1}/parseforge/sampling/backends/__init__.py +0 -0
- {parseforge-0.2.12 → parseforge-0.3.1}/parseforge/sampling/backends/netmiko.py +0 -0
- {parseforge-0.2.12 → parseforge-0.3.1}/parseforge/sampling/core.py +0 -0
- {parseforge-0.2.12 → parseforge-0.3.1}/parseforge/validation.py +0 -0
- {parseforge-0.2.12 → parseforge-0.3.1}/parseforge.egg-info/dependency_links.txt +0 -0
- {parseforge-0.2.12 → parseforge-0.3.1}/parseforge.egg-info/entry_points.txt +0 -0
- {parseforge-0.2.12 → parseforge-0.3.1}/parseforge.egg-info/top_level.txt +0 -0
- {parseforge-0.2.12 → parseforge-0.3.1}/setup.cfg +0 -0
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: parseforge
|
|
3
|
+
Version: 0.3.1
|
|
4
|
+
Summary: LLM-driven pipeline that forges, validates, and promotes TextFSM templates from network CLI output
|
|
5
|
+
Author-email: Tuyen Mathew Duong <tuyen@geekstrident.com>
|
|
6
|
+
Maintainer-email: Tuyen Mathew Duong <tuyen@geekstrident.com>
|
|
7
|
+
License: MIT
|
|
8
|
+
Keywords: textfsm,network automation,cli parsing,template generator,ai,llm,netmiko,device output parsing
|
|
9
|
+
Classifier: Development Status :: 2 - Pre-Alpha
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: Intended Audience :: Information Technology
|
|
12
|
+
Classifier: Intended Audience :: System Administrators
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
19
|
+
Classifier: Topic :: Software Development :: Code Generators
|
|
20
|
+
Classifier: Topic :: Text Processing
|
|
21
|
+
Classifier: Topic :: Utilities
|
|
22
|
+
Classifier: Operating System :: OS Independent
|
|
23
|
+
Requires-Python: >=3.9
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
License-File: LICENSE
|
|
26
|
+
Requires-Dist: click>=8.1
|
|
27
|
+
Requires-Dist: PyYAML>=6.0
|
|
28
|
+
Requires-Dist: textfsm>=1.1.0
|
|
29
|
+
Requires-Dist: textfsm-ai>=0.7.1
|
|
30
|
+
Provides-Extra: anthropic
|
|
31
|
+
Requires-Dist: anthropic>=0.40.0; extra == "anthropic"
|
|
32
|
+
Requires-Dist: textfsm-ai[anthropic]>=0.7.1; extra == "anthropic"
|
|
33
|
+
Provides-Extra: deepseek
|
|
34
|
+
Requires-Dist: openai>=1.0.0; extra == "deepseek"
|
|
35
|
+
Requires-Dist: textfsm-ai[deepseek]>=0.7.1; extra == "deepseek"
|
|
36
|
+
Provides-Extra: openai
|
|
37
|
+
Requires-Dist: openai>=1.0.0; extra == "openai"
|
|
38
|
+
Requires-Dist: textfsm-ai[openai]>=0.7.1; extra == "openai"
|
|
39
|
+
Provides-Extra: groq
|
|
40
|
+
Requires-Dist: openai>=1.0.0; extra == "groq"
|
|
41
|
+
Requires-Dist: textfsm-ai[groq]>=0.7.1; extra == "groq"
|
|
42
|
+
Provides-Extra: xai
|
|
43
|
+
Requires-Dist: openai>=1.0.0; extra == "xai"
|
|
44
|
+
Requires-Dist: textfsm-ai[xai]>=0.7.1; extra == "xai"
|
|
45
|
+
Provides-Extra: together
|
|
46
|
+
Requires-Dist: openai>=1.0.0; extra == "together"
|
|
47
|
+
Requires-Dist: textfsm-ai[together]>=0.7.1; extra == "together"
|
|
48
|
+
Provides-Extra: fireworks
|
|
49
|
+
Requires-Dist: openai>=1.0.0; extra == "fireworks"
|
|
50
|
+
Requires-Dist: textfsm-ai[fireworks]>=0.7.1; extra == "fireworks"
|
|
51
|
+
Provides-Extra: perplexity
|
|
52
|
+
Requires-Dist: openai>=1.0.0; extra == "perplexity"
|
|
53
|
+
Requires-Dist: textfsm-ai[perplexity]>=0.7.1; extra == "perplexity"
|
|
54
|
+
Provides-Extra: openrouter
|
|
55
|
+
Requires-Dist: openai>=1.0.0; extra == "openrouter"
|
|
56
|
+
Requires-Dist: textfsm-ai[openrouter]>=0.7.1; extra == "openrouter"
|
|
57
|
+
Provides-Extra: moonshot
|
|
58
|
+
Requires-Dist: openai>=1.0.0; extra == "moonshot"
|
|
59
|
+
Requires-Dist: textfsm-ai[moonshot]>=0.7.1; extra == "moonshot"
|
|
60
|
+
Provides-Extra: cerebras
|
|
61
|
+
Requires-Dist: openai>=1.0.0; extra == "cerebras"
|
|
62
|
+
Requires-Dist: textfsm-ai[cerebras]>=0.7.1; extra == "cerebras"
|
|
63
|
+
Provides-Extra: mistral
|
|
64
|
+
Requires-Dist: mistralai==1.10.0; extra == "mistral"
|
|
65
|
+
Requires-Dist: textfsm-ai[mistral]>=0.7.1; extra == "mistral"
|
|
66
|
+
Provides-Extra: cohere
|
|
67
|
+
Requires-Dist: cohere==5.21.1; extra == "cohere"
|
|
68
|
+
Requires-Dist: textfsm-ai[cohere]>=0.7.1; extra == "cohere"
|
|
69
|
+
Provides-Extra: azure
|
|
70
|
+
Requires-Dist: azure-ai-inference>=1.0.0b9; extra == "azure"
|
|
71
|
+
Requires-Dist: textfsm-ai[azure]>=0.7.1; extra == "azure"
|
|
72
|
+
Provides-Extra: gemini
|
|
73
|
+
Requires-Dist: google-genai>=0.2.0; extra == "gemini"
|
|
74
|
+
Requires-Dist: textfsm-ai[gemini]>=0.7.1; extra == "gemini"
|
|
75
|
+
Provides-Extra: vertexai
|
|
76
|
+
Requires-Dist: google-genai>=0.2.0; extra == "vertexai"
|
|
77
|
+
Requires-Dist: textfsm-ai[vertexai]>=0.7.1; extra == "vertexai"
|
|
78
|
+
Provides-Extra: bedrock
|
|
79
|
+
Requires-Dist: boto3==1.42.97; extra == "bedrock"
|
|
80
|
+
Requires-Dist: textfsm-ai[bedrock]>=0.7.1; extra == "bedrock"
|
|
81
|
+
Provides-Extra: oci
|
|
82
|
+
Requires-Dist: oci==2.182.0; extra == "oci"
|
|
83
|
+
Requires-Dist: textfsm-ai[oci]>=0.7.1; extra == "oci"
|
|
84
|
+
Provides-Extra: sampling
|
|
85
|
+
Requires-Dist: netmiko>=4.0; extra == "sampling"
|
|
86
|
+
Provides-Extra: dev
|
|
87
|
+
Requires-Dist: pytest>=8.0.0; extra == "dev"
|
|
88
|
+
Requires-Dist: pytest-cov>=5.0.0; extra == "dev"
|
|
89
|
+
Requires-Dist: ruff>=0.4.0; extra == "dev"
|
|
90
|
+
Requires-Dist: black>=24.0.0; extra == "dev"
|
|
91
|
+
Requires-Dist: mypy>=1.10.0; extra == "dev"
|
|
92
|
+
Requires-Dist: anthropic>=0.40.0; extra == "dev"
|
|
93
|
+
Requires-Dist: openai>=1.0.0; extra == "dev"
|
|
94
|
+
Requires-Dist: mistralai==1.10.0; extra == "dev"
|
|
95
|
+
Requires-Dist: cohere==5.21.1; extra == "dev"
|
|
96
|
+
Requires-Dist: azure-ai-inference>=1.0.0b9; extra == "dev"
|
|
97
|
+
Requires-Dist: google-genai>=0.2.0; extra == "dev"
|
|
98
|
+
Requires-Dist: boto3==1.42.97; extra == "dev"
|
|
99
|
+
Requires-Dist: oci==2.182.0; extra == "dev"
|
|
100
|
+
Provides-Extra: release
|
|
101
|
+
Requires-Dist: bump2version; extra == "release"
|
|
102
|
+
Requires-Dist: build; extra == "release"
|
|
103
|
+
Dynamic: license-file
|
|
104
|
+
|
|
105
|
+
# ParseForge
|
|
106
|
+
|
|
107
|
+
LLM-driven pipeline that forges, cross-validates, and promotes [TextFSM](https://github.com/google/textfsm)
|
|
108
|
+
templates from network device CLI output.
|
|
109
|
+
|
|
110
|
+
Full design plan: [SPEC.md](SPEC.md).
|
|
111
|
+
|
|
112
|
+
## Features
|
|
113
|
+
|
|
114
|
+
- **Trial → integration → promotion: a Human-in-the-Loop review workflow,
|
|
115
|
+
not a one-shot generator.** Every LLM-generated template starts as
|
|
116
|
+
unreviewed evidence in `trials/`. `integration` clusters every trial for
|
|
117
|
+
a command by the *output schema* its parsed records actually have, not
|
|
118
|
+
exact template text — a command's output can legitimately vary by
|
|
119
|
+
hardware/firmware, so distinct schemas become separate, independently
|
|
120
|
+
tracked groups instead of one hand-picked "winner." `promotion` then
|
|
121
|
+
auto-promotes any group whose match rate against every known sample
|
|
122
|
+
clears a configurable gate straight into `authoritative/`; anything
|
|
123
|
+
short of that gate is queued for human review instead of silently
|
|
124
|
+
shipped. A human-reviewed promotion is recorded as a named snapshot
|
|
125
|
+
alongside whatever's currently live, never silently overwriting it —
|
|
126
|
+
so review effort goes only where the evidence is actually ambiguous.
|
|
127
|
+
- **Eighteen LLM providers, one interface.** Anthropic, OpenAI, DeepSeek,
|
|
128
|
+
Groq, xAI, Together, Fireworks, Perplexity, OpenRouter, Moonshot,
|
|
129
|
+
Cerebras, Mistral, Cohere, Azure OpenAI, Gemini, Vertex AI, Amazon
|
|
130
|
+
Bedrock, and Oracle Cloud (OCI) — including four with non-standard auth
|
|
131
|
+
(deployment names, GCP Application Default Credentials, AWS's
|
|
132
|
+
credential chain, OCI request-signing) handled transparently. Naming
|
|
133
|
+
and generation can use two different providers in the same trial. See
|
|
134
|
+
[Providers](https://geeks-trident-llc.github.io/parseforge/guides/providers/).
|
|
135
|
+
- **Self-caching cli-name resolution.** A raw CLI command
|
|
136
|
+
(`show interface GE1.1 status`) only ever costs LLM tokens once — it's
|
|
137
|
+
resolved to a canonical, indexed name
|
|
138
|
+
(`show-interface-var1-status`) and cached locally; every later trial for
|
|
139
|
+
that command is a free lookup.
|
|
140
|
+
- **Self-validation, not just "the LLM said so."** Every generated
|
|
141
|
+
template is immediately run against its own sample before being
|
|
142
|
+
recorded as passed — a template that doesn't actually parse the output
|
|
143
|
+
it was generated from never gets a chance to look good on paper.
|
|
144
|
+
- **Drift monitoring.** An authoritative template is periodically checked
|
|
145
|
+
against new production samples; a failing sample feeds back into the
|
|
146
|
+
pipeline as a new trial automatically, closing the loop instead of just
|
|
147
|
+
logging an alert.
|
|
148
|
+
- **CLI and Python API, same underlying calls.** Everything the CLI does
|
|
149
|
+
— `run`, `trial`, `integration`, `promotion` — is one function call in
|
|
150
|
+
Python too. See the
|
|
151
|
+
[Python API guide](https://geeks-trident-llc.github.io/parseforge/guides/python-api/).
|
|
152
|
+
|
|
153
|
+
## Status
|
|
154
|
+
|
|
155
|
+
Early beta. The full pipeline is implemented and tested end to end — naming,
|
|
156
|
+
sampling, generation, self-validation, integration (output-schema group/variant
|
|
157
|
+
clustering), promotion (auto and human-reviewed), and drift monitoring — and
|
|
158
|
+
wired into the CLI. A few things are intentionally not there yet:
|
|
159
|
+
|
|
160
|
+
- **`USER_REVIEWED` promotion has a library entry point but no CLI command**
|
|
161
|
+
(`promotion.promote_user_reviewed()` works today; there's no
|
|
162
|
+
`parseforge promotion --mode user-reviewed` yet). Deferred until real
|
|
163
|
+
human-reviewed cases exist to show what a CLI/config shape for a list of
|
|
164
|
+
case/suffix/gate requests should actually look like, rather than guessing
|
|
165
|
+
ahead of need.
|
|
166
|
+
- **Batch sampling mode** (collect several samples per command before
|
|
167
|
+
generating, SPEC.md §4) is designed but not built — the simpler
|
|
168
|
+
per-command loop mode is the only one implemented.
|
|
169
|
+
- **One sampling connector** (Netmiko/SSH). The CLI's `--connector` registry
|
|
170
|
+
is built to hold more without a redesign, but nothing else is wired in yet.
|
|
171
|
+
|
|
172
|
+
## Installation
|
|
173
|
+
|
|
174
|
+
```bash
|
|
175
|
+
# minimal install, no AI-provider SDK
|
|
176
|
+
pip install parseforge
|
|
177
|
+
|
|
178
|
+
# with a provider extra, e.g. anthropic
|
|
179
|
+
pip install parseforge[anthropic]
|
|
180
|
+
|
|
181
|
+
# local development
|
|
182
|
+
pip install -e ".[dev,sampling]"
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
For the full extras list (all eighteen providers, `sampling`, combining
|
|
186
|
+
extras, per-provider `requirements/` files, and the dev/test/release
|
|
187
|
+
setup), see
|
|
188
|
+
[Installation](https://geeks-trident-llc.github.io/parseforge/getting-started/installation/).
|
|
189
|
+
|
|
190
|
+
## Reference
|
|
191
|
+
|
|
192
|
+
- [Documentation site](https://geeks-trident-llc.github.io/parseforge/) ([source](./docs/index.md))
|
|
193
|
+
- [Quickstart](https://geeks-trident-llc.github.io/parseforge/getting-started/quickstart/) — a full walkthrough, from a single lookup to the end-to-end workflow
|
|
194
|
+
- [Providers](https://geeks-trident-llc.github.io/parseforge/guides/providers/) — every provider's extra, auth requirements, and default model
|
|
195
|
+
- [CLI Guide](https://geeks-trident-llc.github.io/parseforge/guides/cli/) — every command, in full
|
|
196
|
+
- [Python API guide](https://geeks-trident-llc.github.io/parseforge/guides/python-api/) — calling parseforge from Python instead of the CLI
|
|
197
|
+
- [API Reference](https://geeks-trident-llc.github.io/parseforge/reference/api/) — every public function/class, by pipeline stage
|
|
198
|
+
- [Changelog](https://geeks-trident-llc.github.io/parseforge/changelog/) — what shipped in each release
|
|
199
|
+
- [SPEC.md](SPEC.md) — full design plan and open questions
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# ParseForge
|
|
2
|
+
|
|
3
|
+
LLM-driven pipeline that forges, cross-validates, and promotes [TextFSM](https://github.com/google/textfsm)
|
|
4
|
+
templates from network device CLI output.
|
|
5
|
+
|
|
6
|
+
Full design plan: [SPEC.md](SPEC.md).
|
|
7
|
+
|
|
8
|
+
## Features
|
|
9
|
+
|
|
10
|
+
- **Trial → integration → promotion: a Human-in-the-Loop review workflow,
|
|
11
|
+
not a one-shot generator.** Every LLM-generated template starts as
|
|
12
|
+
unreviewed evidence in `trials/`. `integration` clusters every trial for
|
|
13
|
+
a command by the *output schema* its parsed records actually have, not
|
|
14
|
+
exact template text — a command's output can legitimately vary by
|
|
15
|
+
hardware/firmware, so distinct schemas become separate, independently
|
|
16
|
+
tracked groups instead of one hand-picked "winner." `promotion` then
|
|
17
|
+
auto-promotes any group whose match rate against every known sample
|
|
18
|
+
clears a configurable gate straight into `authoritative/`; anything
|
|
19
|
+
short of that gate is queued for human review instead of silently
|
|
20
|
+
shipped. A human-reviewed promotion is recorded as a named snapshot
|
|
21
|
+
alongside whatever's currently live, never silently overwriting it —
|
|
22
|
+
so review effort goes only where the evidence is actually ambiguous.
|
|
23
|
+
- **Eighteen LLM providers, one interface.** Anthropic, OpenAI, DeepSeek,
|
|
24
|
+
Groq, xAI, Together, Fireworks, Perplexity, OpenRouter, Moonshot,
|
|
25
|
+
Cerebras, Mistral, Cohere, Azure OpenAI, Gemini, Vertex AI, Amazon
|
|
26
|
+
Bedrock, and Oracle Cloud (OCI) — including four with non-standard auth
|
|
27
|
+
(deployment names, GCP Application Default Credentials, AWS's
|
|
28
|
+
credential chain, OCI request-signing) handled transparently. Naming
|
|
29
|
+
and generation can use two different providers in the same trial. See
|
|
30
|
+
[Providers](https://geeks-trident-llc.github.io/parseforge/guides/providers/).
|
|
31
|
+
- **Self-caching cli-name resolution.** A raw CLI command
|
|
32
|
+
(`show interface GE1.1 status`) only ever costs LLM tokens once — it's
|
|
33
|
+
resolved to a canonical, indexed name
|
|
34
|
+
(`show-interface-var1-status`) and cached locally; every later trial for
|
|
35
|
+
that command is a free lookup.
|
|
36
|
+
- **Self-validation, not just "the LLM said so."** Every generated
|
|
37
|
+
template is immediately run against its own sample before being
|
|
38
|
+
recorded as passed — a template that doesn't actually parse the output
|
|
39
|
+
it was generated from never gets a chance to look good on paper.
|
|
40
|
+
- **Drift monitoring.** An authoritative template is periodically checked
|
|
41
|
+
against new production samples; a failing sample feeds back into the
|
|
42
|
+
pipeline as a new trial automatically, closing the loop instead of just
|
|
43
|
+
logging an alert.
|
|
44
|
+
- **CLI and Python API, same underlying calls.** Everything the CLI does
|
|
45
|
+
— `run`, `trial`, `integration`, `promotion` — is one function call in
|
|
46
|
+
Python too. See the
|
|
47
|
+
[Python API guide](https://geeks-trident-llc.github.io/parseforge/guides/python-api/).
|
|
48
|
+
|
|
49
|
+
## Status
|
|
50
|
+
|
|
51
|
+
Early beta. The full pipeline is implemented and tested end to end — naming,
|
|
52
|
+
sampling, generation, self-validation, integration (output-schema group/variant
|
|
53
|
+
clustering), promotion (auto and human-reviewed), and drift monitoring — and
|
|
54
|
+
wired into the CLI. A few things are intentionally not there yet:
|
|
55
|
+
|
|
56
|
+
- **`USER_REVIEWED` promotion has a library entry point but no CLI command**
|
|
57
|
+
(`promotion.promote_user_reviewed()` works today; there's no
|
|
58
|
+
`parseforge promotion --mode user-reviewed` yet). Deferred until real
|
|
59
|
+
human-reviewed cases exist to show what a CLI/config shape for a list of
|
|
60
|
+
case/suffix/gate requests should actually look like, rather than guessing
|
|
61
|
+
ahead of need.
|
|
62
|
+
- **Batch sampling mode** (collect several samples per command before
|
|
63
|
+
generating, SPEC.md §4) is designed but not built — the simpler
|
|
64
|
+
per-command loop mode is the only one implemented.
|
|
65
|
+
- **One sampling connector** (Netmiko/SSH). The CLI's `--connector` registry
|
|
66
|
+
is built to hold more without a redesign, but nothing else is wired in yet.
|
|
67
|
+
|
|
68
|
+
## Installation
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
# minimal install, no AI-provider SDK
|
|
72
|
+
pip install parseforge
|
|
73
|
+
|
|
74
|
+
# with a provider extra, e.g. anthropic
|
|
75
|
+
pip install parseforge[anthropic]
|
|
76
|
+
|
|
77
|
+
# local development
|
|
78
|
+
pip install -e ".[dev,sampling]"
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
For the full extras list (all eighteen providers, `sampling`, combining
|
|
82
|
+
extras, per-provider `requirements/` files, and the dev/test/release
|
|
83
|
+
setup), see
|
|
84
|
+
[Installation](https://geeks-trident-llc.github.io/parseforge/getting-started/installation/).
|
|
85
|
+
|
|
86
|
+
## Reference
|
|
87
|
+
|
|
88
|
+
- [Documentation site](https://geeks-trident-llc.github.io/parseforge/) ([source](./docs/index.md))
|
|
89
|
+
- [Quickstart](https://geeks-trident-llc.github.io/parseforge/getting-started/quickstart/) — a full walkthrough, from a single lookup to the end-to-end workflow
|
|
90
|
+
- [Providers](https://geeks-trident-llc.github.io/parseforge/guides/providers/) — every provider's extra, auth requirements, and default model
|
|
91
|
+
- [CLI Guide](https://geeks-trident-llc.github.io/parseforge/guides/cli/) — every command, in full
|
|
92
|
+
- [Python API guide](https://geeks-trident-llc.github.io/parseforge/guides/python-api/) — calling parseforge from Python instead of the CLI
|
|
93
|
+
- [API Reference](https://geeks-trident-llc.github.io/parseforge/reference/api/) — every public function/class, by pipeline stage
|
|
94
|
+
- [Changelog](https://geeks-trident-llc.github.io/parseforge/changelog/) — what shipped in each release
|
|
95
|
+
- [SPEC.md](SPEC.md) — full design plan and open questions
|
|
@@ -79,7 +79,7 @@ Keep the timestamp+shortid directories (not `result1..N`) — chronological orde
|
|
|
79
79
|
| `derive/template.textfsm` | Cleaned, DSL-compiled TextFSM template candidate (`.textfsm` extension — recognized by TextFSM tooling/linters, unlike `textfsm.template`) |
|
|
80
80
|
| `derive/readable-dsl.txt` | Human-readable description of what the template captures |
|
|
81
81
|
| `derive/recognizers.txt` | Heuristics/signatures for detecting this output type at runtime |
|
|
82
|
-
| `summary.json` | Everything else about the run: `created_at`/`ended_at`/`duration_ms`, `passed`, `error` (when `passed` is false), `metadata` (project/username/email/description), `command_info` (vendor/family/os/version/device_type/command), `usage` (naming + generation token counts
|
|
82
|
+
| `summary.json` | Everything else about the run: `created_at`/`ended_at`/`duration_ms`, `passed`, `error` (when `passed` is false), `metadata` (project/username/email/description), `command_info` (vendor/family/os/version/device_type/command), `usage` (naming + generation token counts), and `provider_info` (the generation provider/model) |
|
|
83
83
|
|
|
84
84
|
### 3.2 `integration/` (no human review yet)
|
|
85
85
|
|
|
@@ -180,4 +180,4 @@ Where a fourth tier is tempting but better handled as **metadata instead of a ne
|
|
|
180
180
|
- Do you want a **registry/index file** (e.g. `catalog.json`) at the repo root listing every `<vendor>/<family>/<os>/<cli-name>` combination that exists, plus its authoritative status, for fast lookup without walking the filesystem?
|
|
181
181
|
- What **confidence threshold** (match-rate %, sample count minimum) should gate auto-promotion vs. human review in step 9 — worth making this configurable per-project rather than hardcoded?
|
|
182
182
|
- Should `recognizers.txt` support **one-of-many matching** from day one (per the multi-variant note in §6), or is that a v2 concern?
|
|
183
|
-
- What's the **LLM provider/model** for generation — worth pinning per-project so `summary.json`'s usage
|
|
183
|
+
- What's the **LLM provider/model** for generation — worth pinning per-project so `summary.json`'s token usage is comparable across runs?
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
__version__ = "0.3.1"
|
|
2
|
+
version = __version__
|
|
3
|
+
|
|
4
|
+
from .api import ( # noqa: E402
|
|
5
|
+
DEFAULT_STORE_ROOT,
|
|
6
|
+
CliContext,
|
|
7
|
+
DeviceConnection,
|
|
8
|
+
DeviceKey,
|
|
9
|
+
DriftCheckResult,
|
|
10
|
+
DriftGate,
|
|
11
|
+
GenerationResult,
|
|
12
|
+
GenerationTokenUsage,
|
|
13
|
+
GroupEvaluation,
|
|
14
|
+
LLMCLIResponse,
|
|
15
|
+
LLMProviderConfig,
|
|
16
|
+
Mode,
|
|
17
|
+
NamingResolution,
|
|
18
|
+
NamingTokenUsage,
|
|
19
|
+
ParseResult,
|
|
20
|
+
PromotionDecision,
|
|
21
|
+
PromotionGate,
|
|
22
|
+
PromotionMetadata,
|
|
23
|
+
PromotionMode,
|
|
24
|
+
PromotionRunResult,
|
|
25
|
+
Reference,
|
|
26
|
+
ReferenceGroup,
|
|
27
|
+
ReferenceVariant,
|
|
28
|
+
RegexBuilder,
|
|
29
|
+
Sampler,
|
|
30
|
+
TrialMetadata,
|
|
31
|
+
TrialResult,
|
|
32
|
+
UserReviewedRequest,
|
|
33
|
+
build_integration,
|
|
34
|
+
build_reference_summary,
|
|
35
|
+
check_drift,
|
|
36
|
+
cli_name,
|
|
37
|
+
decide_promotion,
|
|
38
|
+
discover_device_keys,
|
|
39
|
+
evaluate_cases,
|
|
40
|
+
generate,
|
|
41
|
+
parse,
|
|
42
|
+
promote_auto,
|
|
43
|
+
promote_user_reviewed,
|
|
44
|
+
resolve_cli_name,
|
|
45
|
+
run_command_pipeline,
|
|
46
|
+
sample,
|
|
47
|
+
write_reference_summary,
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
__all__ = [
|
|
51
|
+
"version",
|
|
52
|
+
"__version__",
|
|
53
|
+
"cli_name",
|
|
54
|
+
"resolve_cli_name",
|
|
55
|
+
"NamingResolution",
|
|
56
|
+
"CliContext",
|
|
57
|
+
"RegexBuilder",
|
|
58
|
+
"LLMCLIResponse",
|
|
59
|
+
"NamingTokenUsage",
|
|
60
|
+
"sample",
|
|
61
|
+
"DeviceConnection",
|
|
62
|
+
"Sampler",
|
|
63
|
+
"generate",
|
|
64
|
+
"GenerationResult",
|
|
65
|
+
"GenerationTokenUsage",
|
|
66
|
+
"parse",
|
|
67
|
+
"ParseResult",
|
|
68
|
+
"run_command_pipeline",
|
|
69
|
+
"LLMProviderConfig",
|
|
70
|
+
"TrialMetadata",
|
|
71
|
+
"TrialResult",
|
|
72
|
+
"Mode",
|
|
73
|
+
"build_integration",
|
|
74
|
+
"build_reference_summary",
|
|
75
|
+
"write_reference_summary",
|
|
76
|
+
"Reference",
|
|
77
|
+
"ReferenceGroup",
|
|
78
|
+
"ReferenceVariant",
|
|
79
|
+
"promote_auto",
|
|
80
|
+
"promote_user_reviewed",
|
|
81
|
+
"decide_promotion",
|
|
82
|
+
"evaluate_cases",
|
|
83
|
+
"PromotionGate",
|
|
84
|
+
"PromotionMetadata",
|
|
85
|
+
"UserReviewedRequest",
|
|
86
|
+
"GroupEvaluation",
|
|
87
|
+
"PromotionRunResult",
|
|
88
|
+
"PromotionDecision",
|
|
89
|
+
"PromotionMode",
|
|
90
|
+
"check_drift",
|
|
91
|
+
"DriftGate",
|
|
92
|
+
"DriftCheckResult",
|
|
93
|
+
"DEFAULT_STORE_ROOT",
|
|
94
|
+
"DeviceKey",
|
|
95
|
+
"discover_device_keys",
|
|
96
|
+
]
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
"""Public Python API — the single place to import parseforge's stable,
|
|
2
|
+
supported surface from. Mirrors textfsm-ai's own api.py convention
|
|
3
|
+
(https://github.com/Geeks-Trident-LLC/textfsm-ai): one entry point per
|
|
4
|
+
pipeline stage (SPEC.md §5), plus the types each one returns or accepts.
|
|
5
|
+
|
|
6
|
+
Provider-specific naming builders (``AnthropicRegexBuilder``,
|
|
7
|
+
``OCIRegexBuilder``, ...) aren't re-exported here — they're already a
|
|
8
|
+
clean, documented import path via :mod:`parseforge.naming` directly, and
|
|
9
|
+
listing all eighteen here would bloat this module without adding
|
|
10
|
+
anything. Anything not listed in ``__all__`` (module-internal helpers,
|
|
11
|
+
provider implementation details, CLI plumbing) isn't part of the public
|
|
12
|
+
API and may change without notice.
|
|
13
|
+
|
|
14
|
+
``naming``'s and ``generation``'s ``TokenUsage`` classes are separate
|
|
15
|
+
types that happen to share a name (naming makes one call per cache
|
|
16
|
+
miss; generation's is already accumulated across every LLM call in its
|
|
17
|
+
own pipeline, see ``GenerationResult.usage``) — aliased here as
|
|
18
|
+
``NamingTokenUsage``/``GenerationTokenUsage`` to avoid the collision,
|
|
19
|
+
the same convention this package's own test suite already uses.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
from __future__ import annotations
|
|
23
|
+
|
|
24
|
+
from .drift import DriftCheckResult, DriftGate, check_drift
|
|
25
|
+
from .generation import GenerationResult, generate
|
|
26
|
+
from .generation import TokenUsage as GenerationTokenUsage
|
|
27
|
+
from .integration import (
|
|
28
|
+
Reference,
|
|
29
|
+
ReferenceGroup,
|
|
30
|
+
ReferenceVariant,
|
|
31
|
+
build_integration,
|
|
32
|
+
build_reference_summary,
|
|
33
|
+
write_reference_summary,
|
|
34
|
+
)
|
|
35
|
+
from .naming import (
|
|
36
|
+
CliContext,
|
|
37
|
+
LLMCLIResponse,
|
|
38
|
+
NamingResolution,
|
|
39
|
+
RegexBuilder,
|
|
40
|
+
cli_name,
|
|
41
|
+
resolve_cli_name,
|
|
42
|
+
)
|
|
43
|
+
from .naming import TokenUsage as NamingTokenUsage
|
|
44
|
+
from .paths import DEFAULT_STORE_ROOT, DeviceKey, discover_device_keys
|
|
45
|
+
from .pipeline import (
|
|
46
|
+
LLMProviderConfig,
|
|
47
|
+
Mode,
|
|
48
|
+
TrialMetadata,
|
|
49
|
+
TrialResult,
|
|
50
|
+
run_command_pipeline,
|
|
51
|
+
)
|
|
52
|
+
from .promotion import (
|
|
53
|
+
GroupEvaluation,
|
|
54
|
+
PromotionDecision,
|
|
55
|
+
PromotionGate,
|
|
56
|
+
PromotionMetadata,
|
|
57
|
+
PromotionMode,
|
|
58
|
+
PromotionRunResult,
|
|
59
|
+
UserReviewedRequest,
|
|
60
|
+
decide_promotion,
|
|
61
|
+
evaluate_cases,
|
|
62
|
+
promote_auto,
|
|
63
|
+
promote_user_reviewed,
|
|
64
|
+
)
|
|
65
|
+
from .sampling import DeviceConnection, Sampler, sample
|
|
66
|
+
from .validation import ParseResult, parse
|
|
67
|
+
|
|
68
|
+
__all__ = [
|
|
69
|
+
# Naming (SPEC.md §2) — resolve a raw CLI command to its canonical
|
|
70
|
+
# cli-name, cached after the first LLM call.
|
|
71
|
+
"cli_name",
|
|
72
|
+
"resolve_cli_name",
|
|
73
|
+
"NamingResolution",
|
|
74
|
+
"CliContext",
|
|
75
|
+
"RegexBuilder",
|
|
76
|
+
"LLMCLIResponse",
|
|
77
|
+
"NamingTokenUsage",
|
|
78
|
+
# Sampling (SPEC.md §3) — capture raw command output from a device.
|
|
79
|
+
"sample",
|
|
80
|
+
"DeviceConnection",
|
|
81
|
+
"Sampler",
|
|
82
|
+
# Generation (SPEC.md §5 steps 5-6) — sample -> candidate TextFSM
|
|
83
|
+
# template, via textfsm-ai's delivery pipeline.
|
|
84
|
+
"generate",
|
|
85
|
+
"GenerationResult",
|
|
86
|
+
"GenerationTokenUsage",
|
|
87
|
+
# Validation (SPEC.md §5 step 7) — self-validate a template against
|
|
88
|
+
# its own sample.
|
|
89
|
+
"parse",
|
|
90
|
+
"ParseResult",
|
|
91
|
+
# Pipeline orchestration (SPEC.md §5) — one call runs steps 1-7
|
|
92
|
+
# (naming -> sampling -> generation -> validation) for a single trial.
|
|
93
|
+
"run_command_pipeline",
|
|
94
|
+
"LLMProviderConfig",
|
|
95
|
+
"TrialMetadata",
|
|
96
|
+
"TrialResult",
|
|
97
|
+
"Mode",
|
|
98
|
+
# Integration (SPEC.md §5 step 8) — cluster trials by output schema.
|
|
99
|
+
"build_integration",
|
|
100
|
+
"build_reference_summary",
|
|
101
|
+
"write_reference_summary",
|
|
102
|
+
"Reference",
|
|
103
|
+
"ReferenceGroup",
|
|
104
|
+
"ReferenceVariant",
|
|
105
|
+
# Promotion (SPEC.md §5 step 9) — auto-promote groups that clear
|
|
106
|
+
# their gate; queue everything else for human review.
|
|
107
|
+
"promote_auto",
|
|
108
|
+
"promote_user_reviewed",
|
|
109
|
+
"decide_promotion",
|
|
110
|
+
"evaluate_cases",
|
|
111
|
+
"PromotionGate",
|
|
112
|
+
"PromotionMetadata",
|
|
113
|
+
"UserReviewedRequest",
|
|
114
|
+
"GroupEvaluation",
|
|
115
|
+
"PromotionRunResult",
|
|
116
|
+
"PromotionDecision",
|
|
117
|
+
"PromotionMode",
|
|
118
|
+
# Drift monitoring (SPEC.md §5 step 11) — check an authoritative
|
|
119
|
+
# template against new production samples.
|
|
120
|
+
"check_drift",
|
|
121
|
+
"DriftGate",
|
|
122
|
+
"DriftCheckResult",
|
|
123
|
+
# Store-root layout helpers.
|
|
124
|
+
"DEFAULT_STORE_ROOT",
|
|
125
|
+
"DeviceKey",
|
|
126
|
+
"discover_device_keys",
|
|
127
|
+
]
|
|
@@ -659,6 +659,7 @@ def run_cmd(
|
|
|
659
659
|
click.echo(f"cli_name : {result.cli_name}")
|
|
660
660
|
click.echo(f"passed : {result.passed}")
|
|
661
661
|
click.echo(f"run_dir : {result.run_dir}")
|
|
662
|
+
click.echo(f"usage : {result.total_usage}")
|
|
662
663
|
|
|
663
664
|
|
|
664
665
|
def _check_connector(
|
|
@@ -1200,7 +1201,7 @@ def trial_cmd(config_path: str, store_root_opt: str | None) -> None:
|
|
|
1200
1201
|
for command, result in zip(cfg.commands, results):
|
|
1201
1202
|
click.echo(
|
|
1202
1203
|
f"{command} -> cli_name={result.cli_name} passed={result.passed} "
|
|
1203
|
-
f"run_dir={result.run_dir}"
|
|
1204
|
+
f"run_dir={result.run_dir} usage={result.total_usage}"
|
|
1204
1205
|
)
|
|
1205
1206
|
passed_count += int(result.passed)
|
|
1206
1207
|
click.echo(f"{passed_count}/{len(results)} passed")
|
|
@@ -44,7 +44,6 @@ class TokenUsage:
|
|
|
44
44
|
input_tokens: int
|
|
45
45
|
output_tokens: int
|
|
46
46
|
total_tokens: int
|
|
47
|
-
estimated_cost: float
|
|
48
47
|
|
|
49
48
|
|
|
50
49
|
@dataclass(frozen=True)
|
|
@@ -95,7 +94,6 @@ def generate(
|
|
|
95
94
|
input_tokens=usage_data.get("input_tokens", 0),
|
|
96
95
|
output_tokens=usage_data.get("output_tokens", 0),
|
|
97
96
|
total_tokens=usage_data.get("total_tokens", 0),
|
|
98
|
-
estimated_cost=usage_data.get("estimated_cost", 0.0),
|
|
99
97
|
)
|
|
100
98
|
|
|
101
99
|
gen_stage = (debug.get("generation_pipeline") or {}).get("last_stage") or {}
|
|
@@ -6,7 +6,6 @@ import time
|
|
|
6
6
|
from typing import TYPE_CHECKING, Any
|
|
7
7
|
|
|
8
8
|
from ..llm import CliContext, LLMCLIResponse, TokenUsage, build_prompt
|
|
9
|
-
from .cost import estimate_cost
|
|
10
9
|
from .errors import format_llm_error_reason, is_retryable
|
|
11
10
|
from .models import default_model
|
|
12
11
|
from .text import extract_pattern
|
|
@@ -89,9 +88,7 @@ class AnthropicRegexBuilder:
|
|
|
89
88
|
return LLMCLIResponse(
|
|
90
89
|
content="",
|
|
91
90
|
raw=exc,
|
|
92
|
-
usage=TokenUsage(
|
|
93
|
-
input_tokens=0, output_tokens=0, total_tokens=0, estimated_cost=0.0
|
|
94
|
-
),
|
|
91
|
+
usage=TokenUsage(input_tokens=0, output_tokens=0, total_tokens=0),
|
|
95
92
|
duration_ms=(time.monotonic() - start) * 1000,
|
|
96
93
|
reason=format_llm_error_reason(exc),
|
|
97
94
|
ready=False,
|
|
@@ -111,13 +108,6 @@ class AnthropicRegexBuilder:
|
|
|
111
108
|
input_tokens=response.usage.input_tokens,
|
|
112
109
|
output_tokens=response.usage.output_tokens,
|
|
113
110
|
total_tokens=total_tokens,
|
|
114
|
-
estimated_cost=estimate_cost(
|
|
115
|
-
input_tokens=response.usage.input_tokens,
|
|
116
|
-
output_tokens=response.usage.output_tokens,
|
|
117
|
-
total_tokens=total_tokens,
|
|
118
|
-
provider=self.provider,
|
|
119
|
-
model=self.model,
|
|
120
|
-
),
|
|
121
111
|
),
|
|
122
112
|
duration_ms=duration_ms,
|
|
123
113
|
reason=response.stop_reason or "",
|
|
@@ -17,7 +17,6 @@ import time
|
|
|
17
17
|
from typing import TYPE_CHECKING, Any
|
|
18
18
|
|
|
19
19
|
from ..llm import CliContext, LLMCLIResponse, TokenUsage, build_prompt
|
|
20
|
-
from .cost import estimate_cost
|
|
21
20
|
from .text import extract_pattern
|
|
22
21
|
|
|
23
22
|
if TYPE_CHECKING:
|
|
@@ -161,9 +160,7 @@ class AzureRegexBuilder:
|
|
|
161
160
|
return LLMCLIResponse(
|
|
162
161
|
content="",
|
|
163
162
|
raw=exc,
|
|
164
|
-
usage=TokenUsage(
|
|
165
|
-
input_tokens=0, output_tokens=0, total_tokens=0, estimated_cost=0.0
|
|
166
|
-
),
|
|
163
|
+
usage=TokenUsage(input_tokens=0, output_tokens=0, total_tokens=0),
|
|
167
164
|
duration_ms=(time.monotonic() - start) * 1000,
|
|
168
165
|
reason=_format_error_reason(exc),
|
|
169
166
|
ready=False,
|
|
@@ -178,18 +175,6 @@ class AzureRegexBuilder:
|
|
|
178
175
|
input_tokens=response.usage.prompt_tokens,
|
|
179
176
|
output_tokens=response.usage.completion_tokens,
|
|
180
177
|
total_tokens=response.usage.total_tokens,
|
|
181
|
-
estimated_cost=estimate_cost(
|
|
182
|
-
input_tokens=response.usage.prompt_tokens,
|
|
183
|
-
output_tokens=response.usage.completion_tokens,
|
|
184
|
-
total_tokens=response.usage.total_tokens,
|
|
185
|
-
provider=self.provider,
|
|
186
|
-
# No public pricing table for a customer's own Azure
|
|
187
|
-
# deployment — estimate_cost() falls back to 0.0 with
|
|
188
|
-
# a warning for an unrecognized (provider, model) pair,
|
|
189
|
-
# which is the correct/expected outcome here, not an
|
|
190
|
-
# error condition.
|
|
191
|
-
model=self._resolved_deployment or "",
|
|
192
|
-
),
|
|
193
178
|
),
|
|
194
179
|
duration_ms=duration_ms,
|
|
195
180
|
reason=choice.finish_reason or "",
|