parseforge 0.2.8__tar.gz → 0.2.9__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.2.8/parseforge.egg-info → parseforge-0.2.9}/PKG-INFO +32 -17
- {parseforge-0.2.8 → parseforge-0.2.9}/README.md +27 -16
- parseforge-0.2.9/parseforge/__init__.py +1 -0
- {parseforge-0.2.8 → parseforge-0.2.9}/parseforge/cli/config.py +18 -0
- {parseforge-0.2.8 → parseforge-0.2.9}/parseforge/cli/main.py +218 -18
- {parseforge-0.2.8 → parseforge-0.2.9}/parseforge/naming/__init__.py +2 -0
- {parseforge-0.2.8 → parseforge-0.2.9}/parseforge/naming/providers/__init__.py +2 -0
- parseforge-0.2.9/parseforge/naming/providers/azure.py +197 -0
- {parseforge-0.2.8 → parseforge-0.2.9}/parseforge/pipeline.py +17 -0
- {parseforge-0.2.8 → parseforge-0.2.9/parseforge.egg-info}/PKG-INFO +32 -17
- {parseforge-0.2.8 → parseforge-0.2.9}/parseforge.egg-info/SOURCES.txt +1 -0
- {parseforge-0.2.8 → parseforge-0.2.9}/parseforge.egg-info/requires.txt +5 -0
- {parseforge-0.2.8 → parseforge-0.2.9}/pyproject.toml +7 -4
- parseforge-0.2.8/parseforge/__init__.py +0 -1
- {parseforge-0.2.8 → parseforge-0.2.9}/LICENSE +0 -0
- {parseforge-0.2.8 → parseforge-0.2.9}/MANIFEST.in +0 -0
- {parseforge-0.2.8 → parseforge-0.2.9}/SPEC.md +0 -0
- {parseforge-0.2.8 → parseforge-0.2.9}/parseforge/cli/__init__.py +0 -0
- {parseforge-0.2.8 → parseforge-0.2.9}/parseforge/drift.py +0 -0
- {parseforge-0.2.8 → parseforge-0.2.9}/parseforge/generation.py +0 -0
- {parseforge-0.2.8 → parseforge-0.2.9}/parseforge/integration.py +0 -0
- {parseforge-0.2.8 → parseforge-0.2.9}/parseforge/naming/assemble.py +0 -0
- {parseforge-0.2.8 → parseforge-0.2.9}/parseforge/naming/cache.py +0 -0
- {parseforge-0.2.8 → parseforge-0.2.9}/parseforge/naming/llm.py +0 -0
- {parseforge-0.2.8 → parseforge-0.2.9}/parseforge/naming/prompts.py +0 -0
- {parseforge-0.2.8 → parseforge-0.2.9}/parseforge/naming/prompts.yaml +0 -0
- {parseforge-0.2.8 → parseforge-0.2.9}/parseforge/naming/providers/anthropic.py +0 -0
- {parseforge-0.2.8 → parseforge-0.2.9}/parseforge/naming/providers/cerebras.py +0 -0
- {parseforge-0.2.8 → parseforge-0.2.9}/parseforge/naming/providers/cohere.py +0 -0
- {parseforge-0.2.8 → parseforge-0.2.9}/parseforge/naming/providers/cost.py +0 -0
- {parseforge-0.2.8 → parseforge-0.2.9}/parseforge/naming/providers/deepseek.py +0 -0
- {parseforge-0.2.8 → parseforge-0.2.9}/parseforge/naming/providers/errors.py +0 -0
- {parseforge-0.2.8 → parseforge-0.2.9}/parseforge/naming/providers/fireworks.py +0 -0
- {parseforge-0.2.8 → parseforge-0.2.9}/parseforge/naming/providers/groq.py +0 -0
- {parseforge-0.2.8 → parseforge-0.2.9}/parseforge/naming/providers/mistral.py +0 -0
- {parseforge-0.2.8 → parseforge-0.2.9}/parseforge/naming/providers/models.py +0 -0
- {parseforge-0.2.8 → parseforge-0.2.9}/parseforge/naming/providers/models.yaml +0 -0
- {parseforge-0.2.8 → parseforge-0.2.9}/parseforge/naming/providers/moonshot.py +0 -0
- {parseforge-0.2.8 → parseforge-0.2.9}/parseforge/naming/providers/openai.py +0 -0
- {parseforge-0.2.8 → parseforge-0.2.9}/parseforge/naming/providers/openrouter.py +0 -0
- {parseforge-0.2.8 → parseforge-0.2.9}/parseforge/naming/providers/perplexity.py +0 -0
- {parseforge-0.2.8 → parseforge-0.2.9}/parseforge/naming/providers/text.py +0 -0
- {parseforge-0.2.8 → parseforge-0.2.9}/parseforge/naming/providers/together.py +0 -0
- {parseforge-0.2.8 → parseforge-0.2.9}/parseforge/naming/providers/xai.py +0 -0
- {parseforge-0.2.8 → parseforge-0.2.9}/parseforge/naming/resolver.py +0 -0
- {parseforge-0.2.8 → parseforge-0.2.9}/parseforge/paths.py +0 -0
- {parseforge-0.2.8 → parseforge-0.2.9}/parseforge/promotion.py +0 -0
- {parseforge-0.2.8 → parseforge-0.2.9}/parseforge/sampling/__init__.py +0 -0
- {parseforge-0.2.8 → parseforge-0.2.9}/parseforge/sampling/backends/__init__.py +0 -0
- {parseforge-0.2.8 → parseforge-0.2.9}/parseforge/sampling/backends/netmiko.py +0 -0
- {parseforge-0.2.8 → parseforge-0.2.9}/parseforge/sampling/core.py +0 -0
- {parseforge-0.2.8 → parseforge-0.2.9}/parseforge/validation.py +0 -0
- {parseforge-0.2.8 → parseforge-0.2.9}/parseforge.egg-info/dependency_links.txt +0 -0
- {parseforge-0.2.8 → parseforge-0.2.9}/parseforge.egg-info/entry_points.txt +0 -0
- {parseforge-0.2.8 → parseforge-0.2.9}/parseforge.egg-info/top_level.txt +0 -0
- {parseforge-0.2.8 → parseforge-0.2.9}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: parseforge
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.9
|
|
4
4
|
Summary: LLM-driven pipeline that forges, validates, and promotes TextFSM templates from network CLI output
|
|
5
5
|
Author-email: Tuyen Mathew Duong <tuyen@geekstrident.com>
|
|
6
6
|
Maintainer-email: Tuyen Mathew Duong <tuyen@geekstrident.com>
|
|
@@ -66,6 +66,9 @@ Requires-Dist: textfsm-ai[mistral]>=0.6.1; extra == "mistral"
|
|
|
66
66
|
Provides-Extra: cohere
|
|
67
67
|
Requires-Dist: cohere==5.21.1; extra == "cohere"
|
|
68
68
|
Requires-Dist: textfsm-ai[cohere]>=0.6.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.6.1; extra == "azure"
|
|
69
72
|
Provides-Extra: sampling
|
|
70
73
|
Requires-Dist: netmiko>=4.0; extra == "sampling"
|
|
71
74
|
Provides-Extra: dev
|
|
@@ -78,6 +81,7 @@ Requires-Dist: anthropic>=0.40.0; extra == "dev"
|
|
|
78
81
|
Requires-Dist: openai>=1.0.0; extra == "dev"
|
|
79
82
|
Requires-Dist: mistralai==1.10.0; extra == "dev"
|
|
80
83
|
Requires-Dist: cohere==5.21.1; extra == "dev"
|
|
84
|
+
Requires-Dist: azure-ai-inference>=1.0.0b9; extra == "dev"
|
|
81
85
|
Provides-Extra: release
|
|
82
86
|
Requires-Dist: bump2version; extra == "release"
|
|
83
87
|
Requires-Dist: build; extra == "release"
|
|
@@ -120,11 +124,11 @@ command that's pure local processing (`canonical`/`readable`/`recognizers`,
|
|
|
120
124
|
LLM (`name`, `check --provider`, `run`, `generate-template`, `trial`) needs the
|
|
121
125
|
extra for whichever provider it uses: `anthropic`, `openai`, `deepseek`,
|
|
122
126
|
`groq`, `xai`, `together`, `fireworks`, `perplexity`, `openrouter`,
|
|
123
|
-
`moonshot`, `cerebras`, `mistral`, or `
|
|
124
|
-
`anthropic` wherever it isn't required, so that's the one most setups
|
|
125
|
-
`pip install parseforge[sampling]` adds Netmiko for live device
|
|
126
|
-
combine extras as needed, e.g.
|
|
127
|
-
`pip install parseforge[anthropic,openai,deepseek,groq,xai,together,fireworks,perplexity,openrouter,moonshot,cerebras,mistral,cohere,sampling]`.
|
|
127
|
+
`moonshot`, `cerebras`, `mistral`, `cohere`, or `azure`. `--provider` defaults
|
|
128
|
+
to `anthropic` wherever it isn't required, so that's the one most setups
|
|
129
|
+
need. `pip install parseforge[sampling]` adds Netmiko for live device
|
|
130
|
+
sampling; combine extras as needed, e.g.
|
|
131
|
+
`pip install parseforge[anthropic,openai,deepseek,groq,xai,together,fireworks,perplexity,openrouter,moonshot,cerebras,mistral,cohere,azure,sampling]`.
|
|
128
132
|
|
|
129
133
|
## Development
|
|
130
134
|
|
|
@@ -132,13 +136,13 @@ combine extras as needed, e.g.
|
|
|
132
136
|
pip install -e ".[dev,sampling]"
|
|
133
137
|
pytest
|
|
134
138
|
```
|
|
135
|
-
`dev` already includes the `anthropic`, `openai`, `mistralai`,
|
|
136
|
-
SDKs (tests exercise all
|
|
137
|
-
`deepseek`, `groq`, `xai`, `together`, `fireworks`,
|
|
138
|
-
`openrouter`, `moonshot`, `cerebras` share just the first two
|
|
139
|
-
`mistral`/`cohere` each need their own
|
|
140
|
-
skip) — add the specific `,<provider>` extra explicitly only if
|
|
141
|
-
outside of `dev`.
|
|
139
|
+
`dev` already includes the `anthropic`, `openai`, `mistralai`, `cohere`, and
|
|
140
|
+
`azure-ai-inference` SDKs (tests exercise all fourteen providers —
|
|
141
|
+
`anthropic`, `openai`, `deepseek`, `groq`, `xai`, `together`, `fireworks`,
|
|
142
|
+
`perplexity`, `openrouter`, `moonshot`, `cerebras` share just the first two
|
|
143
|
+
packages, and `mistral`/`cohere`/`azure` each need their own SDK — and never
|
|
144
|
+
silently skip) — add the specific `,<provider>` extra explicitly only if
|
|
145
|
+
installing outside of `dev`.
|
|
142
146
|
|
|
143
147
|
Linting/formatting/type-checking/docs run through tox instead of extras — see
|
|
144
148
|
`tox.ini` (`tox -e lint`/`format`/`typecheck`/`docs`), each installing its own
|
|
@@ -160,8 +164,14 @@ parseforge name --vendor cisco --family catalyst9200 --os ios-xe --version 17.9.
|
|
|
160
164
|
```
|
|
161
165
|
Still needs an LLM provider on a cache miss — `--provider` defaults to `anthropic`,
|
|
162
166
|
and `--api-key` falls back to that provider's own env var (`ANTHROPIC_API_KEY`/
|
|
163
|
-
`OPENAI_API_KEY`/`DEEPSEEK_API_KEY
|
|
164
|
-
never touches the LLM, so no key is needed at all in that case.
|
|
167
|
+
`OPENAI_API_KEY`/`DEEPSEEK_API_KEY`/etc.). A cache hit (a command already seen
|
|
168
|
+
before) never touches the LLM, so no key is needed at all in that case.
|
|
169
|
+
|
|
170
|
+
`--provider azure` is the one exception to the usual `--api-key`/`--model` shape —
|
|
171
|
+
Azure has no fixed base_url or model catalog, so it also needs `--endpoint`
|
|
172
|
+
(`AZURE_ENDPOINT`), `--api-version` (`AZURE_API_VERSION`), and `--deployment`
|
|
173
|
+
(`AZURE_DEPLOYMENT`, replacing `--model`). Same three options exist on
|
|
174
|
+
`check --provider`, `run`, and `generate-template` below.
|
|
165
175
|
|
|
166
176
|
**`check`** — validate a connector or provider before spending time/tokens on a real
|
|
167
177
|
run. With neither `--env` nor explicit connection flags, prints what a connector needs
|
|
@@ -206,7 +216,10 @@ parseforge run --vendor cisco --family catalyst9200 --os ios-xe --version 17.9.1
|
|
|
206
216
|
`--naming-provider`/`--naming-api-key`/`--naming-model`, defaulting independently
|
|
207
217
|
(`--naming-provider` defaults to `anthropic`; the other two fall back to that
|
|
208
218
|
provider's own env var/default model) — set them explicitly if naming needs a
|
|
209
|
-
different provider than generation.
|
|
219
|
+
different provider than generation. `--provider azure`/`--naming-provider azure`
|
|
220
|
+
each get their own `--endpoint`/`--api-version`/`--deployment` (generation) and
|
|
221
|
+
`--naming-endpoint`/`--naming-api-version`/`--naming-deployment` (naming) — see
|
|
222
|
+
the `name` section above for what they're for.
|
|
210
223
|
|
|
211
224
|
**`init-trial-config`** — write a placeholder `trial.yaml` to fill in, instead of
|
|
212
225
|
writing one by hand:
|
|
@@ -239,7 +252,9 @@ workers: 1
|
|
|
239
252
|
```
|
|
240
253
|
`provider`/`api_key`/`model` are one shared LLM source used for both naming and
|
|
241
254
|
generation. Use the `run` command's separate `--naming-*`/`--generation-*` flags
|
|
242
|
-
instead if a trial actually needs two different providers.
|
|
255
|
+
instead if a trial actually needs two different providers. For `provider: azure`,
|
|
256
|
+
also set `endpoint`/`api_version`/`deployment` (`deployment` replaces `model`) —
|
|
257
|
+
see `parseforge init-trial-config`'s generated placeholder for the exact keys.
|
|
243
258
|
|
|
244
259
|
**`integration`** — rebuild `integration/` for every case under `trials/` (SPEC §5
|
|
245
260
|
step 8):
|
|
@@ -35,11 +35,11 @@ command that's pure local processing (`canonical`/`readable`/`recognizers`,
|
|
|
35
35
|
LLM (`name`, `check --provider`, `run`, `generate-template`, `trial`) needs the
|
|
36
36
|
extra for whichever provider it uses: `anthropic`, `openai`, `deepseek`,
|
|
37
37
|
`groq`, `xai`, `together`, `fireworks`, `perplexity`, `openrouter`,
|
|
38
|
-
`moonshot`, `cerebras`, `mistral`, or `
|
|
39
|
-
`anthropic` wherever it isn't required, so that's the one most setups
|
|
40
|
-
`pip install parseforge[sampling]` adds Netmiko for live device
|
|
41
|
-
combine extras as needed, e.g.
|
|
42
|
-
`pip install parseforge[anthropic,openai,deepseek,groq,xai,together,fireworks,perplexity,openrouter,moonshot,cerebras,mistral,cohere,sampling]`.
|
|
38
|
+
`moonshot`, `cerebras`, `mistral`, `cohere`, or `azure`. `--provider` defaults
|
|
39
|
+
to `anthropic` wherever it isn't required, so that's the one most setups
|
|
40
|
+
need. `pip install parseforge[sampling]` adds Netmiko for live device
|
|
41
|
+
sampling; combine extras as needed, e.g.
|
|
42
|
+
`pip install parseforge[anthropic,openai,deepseek,groq,xai,together,fireworks,perplexity,openrouter,moonshot,cerebras,mistral,cohere,azure,sampling]`.
|
|
43
43
|
|
|
44
44
|
## Development
|
|
45
45
|
|
|
@@ -47,13 +47,13 @@ combine extras as needed, e.g.
|
|
|
47
47
|
pip install -e ".[dev,sampling]"
|
|
48
48
|
pytest
|
|
49
49
|
```
|
|
50
|
-
`dev` already includes the `anthropic`, `openai`, `mistralai`,
|
|
51
|
-
SDKs (tests exercise all
|
|
52
|
-
`deepseek`, `groq`, `xai`, `together`, `fireworks`,
|
|
53
|
-
`openrouter`, `moonshot`, `cerebras` share just the first two
|
|
54
|
-
`mistral`/`cohere` each need their own
|
|
55
|
-
skip) — add the specific `,<provider>` extra explicitly only if
|
|
56
|
-
outside of `dev`.
|
|
50
|
+
`dev` already includes the `anthropic`, `openai`, `mistralai`, `cohere`, and
|
|
51
|
+
`azure-ai-inference` SDKs (tests exercise all fourteen providers —
|
|
52
|
+
`anthropic`, `openai`, `deepseek`, `groq`, `xai`, `together`, `fireworks`,
|
|
53
|
+
`perplexity`, `openrouter`, `moonshot`, `cerebras` share just the first two
|
|
54
|
+
packages, and `mistral`/`cohere`/`azure` each need their own SDK — and never
|
|
55
|
+
silently skip) — add the specific `,<provider>` extra explicitly only if
|
|
56
|
+
installing outside of `dev`.
|
|
57
57
|
|
|
58
58
|
Linting/formatting/type-checking/docs run through tox instead of extras — see
|
|
59
59
|
`tox.ini` (`tox -e lint`/`format`/`typecheck`/`docs`), each installing its own
|
|
@@ -75,8 +75,14 @@ parseforge name --vendor cisco --family catalyst9200 --os ios-xe --version 17.9.
|
|
|
75
75
|
```
|
|
76
76
|
Still needs an LLM provider on a cache miss — `--provider` defaults to `anthropic`,
|
|
77
77
|
and `--api-key` falls back to that provider's own env var (`ANTHROPIC_API_KEY`/
|
|
78
|
-
`OPENAI_API_KEY`/`DEEPSEEK_API_KEY
|
|
79
|
-
never touches the LLM, so no key is needed at all in that case.
|
|
78
|
+
`OPENAI_API_KEY`/`DEEPSEEK_API_KEY`/etc.). A cache hit (a command already seen
|
|
79
|
+
before) never touches the LLM, so no key is needed at all in that case.
|
|
80
|
+
|
|
81
|
+
`--provider azure` is the one exception to the usual `--api-key`/`--model` shape —
|
|
82
|
+
Azure has no fixed base_url or model catalog, so it also needs `--endpoint`
|
|
83
|
+
(`AZURE_ENDPOINT`), `--api-version` (`AZURE_API_VERSION`), and `--deployment`
|
|
84
|
+
(`AZURE_DEPLOYMENT`, replacing `--model`). Same three options exist on
|
|
85
|
+
`check --provider`, `run`, and `generate-template` below.
|
|
80
86
|
|
|
81
87
|
**`check`** — validate a connector or provider before spending time/tokens on a real
|
|
82
88
|
run. With neither `--env` nor explicit connection flags, prints what a connector needs
|
|
@@ -121,7 +127,10 @@ parseforge run --vendor cisco --family catalyst9200 --os ios-xe --version 17.9.1
|
|
|
121
127
|
`--naming-provider`/`--naming-api-key`/`--naming-model`, defaulting independently
|
|
122
128
|
(`--naming-provider` defaults to `anthropic`; the other two fall back to that
|
|
123
129
|
provider's own env var/default model) — set them explicitly if naming needs a
|
|
124
|
-
different provider than generation.
|
|
130
|
+
different provider than generation. `--provider azure`/`--naming-provider azure`
|
|
131
|
+
each get their own `--endpoint`/`--api-version`/`--deployment` (generation) and
|
|
132
|
+
`--naming-endpoint`/`--naming-api-version`/`--naming-deployment` (naming) — see
|
|
133
|
+
the `name` section above for what they're for.
|
|
125
134
|
|
|
126
135
|
**`init-trial-config`** — write a placeholder `trial.yaml` to fill in, instead of
|
|
127
136
|
writing one by hand:
|
|
@@ -154,7 +163,9 @@ workers: 1
|
|
|
154
163
|
```
|
|
155
164
|
`provider`/`api_key`/`model` are one shared LLM source used for both naming and
|
|
156
165
|
generation. Use the `run` command's separate `--naming-*`/`--generation-*` flags
|
|
157
|
-
instead if a trial actually needs two different providers.
|
|
166
|
+
instead if a trial actually needs two different providers. For `provider: azure`,
|
|
167
|
+
also set `endpoint`/`api_version`/`deployment` (`deployment` replaces `model`) —
|
|
168
|
+
see `parseforge init-trial-config`'s generated placeholder for the exact keys.
|
|
158
169
|
|
|
159
170
|
**`integration`** — rebuild `integration/` for every case under `trials/` (SPEC §5
|
|
160
171
|
step 8):
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.2.9"
|
|
@@ -42,6 +42,14 @@ class TrialConfig:
|
|
|
42
42
|
note: str | None = None
|
|
43
43
|
workers: int = 1
|
|
44
44
|
path: str | None = None
|
|
45
|
+
# Only meaningful for provider: azure — no fixed base_url or model
|
|
46
|
+
# catalog there (see naming/providers/azure.py). endpoint/api_version
|
|
47
|
+
# are ignored by every other provider; deployment replaces the usual
|
|
48
|
+
# "model" concept and is what actually gets sent as the generation
|
|
49
|
+
# model argument (see cli/main.py's trial_cmd).
|
|
50
|
+
endpoint: str | None = None
|
|
51
|
+
api_version: str | None = None
|
|
52
|
+
deployment: str | None = None
|
|
45
53
|
|
|
46
54
|
|
|
47
55
|
@dataclass(frozen=True)
|
|
@@ -57,6 +65,10 @@ class GenerationRequestConfig:
|
|
|
57
65
|
env: str | None = None
|
|
58
66
|
cmdline: str | None = None
|
|
59
67
|
sample_file: str | None = None
|
|
68
|
+
# Only meaningful for provider: azure — see TrialConfig above.
|
|
69
|
+
endpoint: str | None = None
|
|
70
|
+
api_version: str | None = None
|
|
71
|
+
deployment: str | None = None
|
|
60
72
|
|
|
61
73
|
|
|
62
74
|
_TRIAL_REQUIRED = (
|
|
@@ -124,6 +136,9 @@ def load_trial_config(path: Path) -> TrialConfig:
|
|
|
124
136
|
note=raw.get("notes"),
|
|
125
137
|
workers=int(raw.get("workers", 1)),
|
|
126
138
|
path=raw.get("path"),
|
|
139
|
+
endpoint=raw.get("endpoint"),
|
|
140
|
+
api_version=raw.get("api_version"),
|
|
141
|
+
deployment=raw.get("deployment"),
|
|
127
142
|
)
|
|
128
143
|
|
|
129
144
|
|
|
@@ -143,4 +158,7 @@ def load_generation_config(path: Path) -> GenerationRequestConfig:
|
|
|
143
158
|
env=raw.get("env"),
|
|
144
159
|
cmdline=raw.get("cmdline"),
|
|
145
160
|
sample_file=raw.get("sample_file"),
|
|
161
|
+
endpoint=raw.get("endpoint"),
|
|
162
|
+
api_version=raw.get("api_version"),
|
|
163
|
+
deployment=raw.get("deployment"),
|
|
146
164
|
)
|
|
@@ -20,9 +20,11 @@ from parseforge import (
|
|
|
20
20
|
validation,
|
|
21
21
|
)
|
|
22
22
|
from parseforge.cli import config as cli_config
|
|
23
|
+
from parseforge.naming.providers.azure import DEFAULT_API_VERSION
|
|
23
24
|
|
|
24
25
|
_BUILDERS: dict[str, type[naming.RegexBuilder]] = {
|
|
25
26
|
"anthropic": naming.AnthropicRegexBuilder,
|
|
27
|
+
"azure": naming.AzureRegexBuilder,
|
|
26
28
|
"cerebras": naming.CerebrasRegexBuilder,
|
|
27
29
|
"cohere": naming.CohereRegexBuilder,
|
|
28
30
|
"deepseek": naming.DeepSeekRegexBuilder,
|
|
@@ -61,6 +63,13 @@ device_type: <netmiko-device-type>
|
|
|
61
63
|
provider: anthropic
|
|
62
64
|
api_key: <api-key>
|
|
63
65
|
model: <model>
|
|
66
|
+
# For provider: azure only -- no fixed base_url or model catalog there.
|
|
67
|
+
# deployment replaces model above (still keep a model: placeholder value,
|
|
68
|
+
# it's simply ignored); endpoint/api_version default to AZURE_ENDPOINT/
|
|
69
|
+
# AZURE_API_VERSION if omitted.
|
|
70
|
+
# endpoint: <azure-resource-endpoint>
|
|
71
|
+
# api_version: <azure-api-version>
|
|
72
|
+
# deployment: <azure-deployment-name>
|
|
64
73
|
|
|
65
74
|
# --- commands to run (required, at least one) ---
|
|
66
75
|
commands:
|
|
@@ -86,6 +95,10 @@ _GENERATE_TEMPLATE_CONFIG_PLACEHOLDER = """\
|
|
|
86
95
|
provider: anthropic
|
|
87
96
|
api_key: <api-key>
|
|
88
97
|
model: <model>
|
|
98
|
+
# For provider: azure only -- see trial.yaml's placeholder for details.
|
|
99
|
+
# endpoint: <azure-resource-endpoint>
|
|
100
|
+
# api_version: <azure-api-version>
|
|
101
|
+
# deployment: <azure-deployment-name>
|
|
89
102
|
|
|
90
103
|
# --- input: choose exactly one ---
|
|
91
104
|
|
|
@@ -114,13 +127,34 @@ def _write_placeholder_config(out_path: str, force: bool, content: str) -> None:
|
|
|
114
127
|
|
|
115
128
|
|
|
116
129
|
def _build_regex_builder(
|
|
117
|
-
provider: str,
|
|
130
|
+
provider: str,
|
|
131
|
+
api_key: str | None,
|
|
132
|
+
model: str | None,
|
|
133
|
+
endpoint: str | None = None,
|
|
134
|
+
api_version: str | None = None,
|
|
135
|
+
deployment: str | None = None,
|
|
118
136
|
) -> naming.RegexBuilder:
|
|
137
|
+
"""endpoint/api_version/deployment are only meaningful for
|
|
138
|
+
provider="azure" (no fixed base_url or model catalog there — see
|
|
139
|
+
naming/providers/azure.py) — forwarded only when given, so every
|
|
140
|
+
other provider's constructor never sees an unexpected kwarg.
|
|
141
|
+
|
|
142
|
+
When deployment is given, model is *not* forwarded — AzureRegexBuilder
|
|
143
|
+
has no model parameter at all (deployment replaces it), so passing
|
|
144
|
+
both would raise a plain TypeError. This matters in practice: every
|
|
145
|
+
TrialConfig always has a (placeholder) model value, since it's a
|
|
146
|
+
required config key shared by every provider."""
|
|
119
147
|
kwargs: dict[str, str] = {}
|
|
120
148
|
if api_key is not None:
|
|
121
149
|
kwargs["api_key"] = api_key
|
|
122
|
-
if
|
|
150
|
+
if deployment is not None:
|
|
151
|
+
kwargs["deployment"] = deployment
|
|
152
|
+
elif model is not None:
|
|
123
153
|
kwargs["model"] = model
|
|
154
|
+
if endpoint is not None:
|
|
155
|
+
kwargs["endpoint"] = endpoint
|
|
156
|
+
if api_version is not None:
|
|
157
|
+
kwargs["api_version"] = api_version
|
|
124
158
|
return _BUILDERS[provider](**kwargs)
|
|
125
159
|
|
|
126
160
|
|
|
@@ -212,12 +246,31 @@ def main() -> None:
|
|
|
212
246
|
"variable (ANTHROPIC_API_KEY, OPENAI_API_KEY, DEEPSEEK_API_KEY, GROQ_API_KEY, "
|
|
213
247
|
"XAI_API_KEY, TOGETHER_API_KEY, FIREWORKS_API_KEY, PERPLEXITY_API_KEY, "
|
|
214
248
|
"OPENROUTER_API_KEY, MOONSHOT_API_KEY, CEREBRAS_API_KEY, MISTRAL_API_KEY, "
|
|
215
|
-
"COHERE_API_KEY); only needed on a cache miss.",
|
|
249
|
+
"COHERE_API_KEY, AZURE_API_KEY); only needed on a cache miss.",
|
|
216
250
|
)
|
|
217
251
|
@click.option(
|
|
218
252
|
"--model",
|
|
219
253
|
default=None,
|
|
220
|
-
help="Defaults to the selected provider's own default model."
|
|
254
|
+
help="Defaults to the selected provider's own default model. Ignored for "
|
|
255
|
+
"--provider azure — use --deployment instead.",
|
|
256
|
+
)
|
|
257
|
+
@click.option(
|
|
258
|
+
"--endpoint",
|
|
259
|
+
default=None,
|
|
260
|
+
help="Azure resource endpoint. Only used by --provider azure; defaults to "
|
|
261
|
+
"AZURE_ENDPOINT.",
|
|
262
|
+
)
|
|
263
|
+
@click.option(
|
|
264
|
+
"--api-version",
|
|
265
|
+
default=None,
|
|
266
|
+
help="Azure API version. Only used by --provider azure; defaults to "
|
|
267
|
+
"AZURE_API_VERSION, then a built-in default.",
|
|
268
|
+
)
|
|
269
|
+
@click.option(
|
|
270
|
+
"--deployment",
|
|
271
|
+
default=None,
|
|
272
|
+
help="Azure deployment name — replaces --model for --provider azure; "
|
|
273
|
+
"defaults to AZURE_DEPLOYMENT.",
|
|
221
274
|
)
|
|
222
275
|
@click.argument("command", nargs=-1, required=True)
|
|
223
276
|
def name_cmd(
|
|
@@ -228,6 +281,9 @@ def name_cmd(
|
|
|
228
281
|
provider: str,
|
|
229
282
|
api_key: str | None,
|
|
230
283
|
model: str | None,
|
|
284
|
+
endpoint: str | None,
|
|
285
|
+
api_version: str | None,
|
|
286
|
+
deployment: str | None,
|
|
231
287
|
command: tuple[str, ...],
|
|
232
288
|
) -> None:
|
|
233
289
|
"""Print the canonical cli-name for a raw CLI COMMAND.
|
|
@@ -236,7 +292,9 @@ def name_cmd(
|
|
|
236
292
|
on a cache miss.
|
|
237
293
|
"""
|
|
238
294
|
context = naming.CliContext(vendor=vendor, family=family, os=os_, version=version)
|
|
239
|
-
builder = _build_regex_builder(
|
|
295
|
+
builder = _build_regex_builder(
|
|
296
|
+
provider, api_key, model, endpoint, api_version, deployment
|
|
297
|
+
)
|
|
240
298
|
click.echo(naming.cli_name(" ".join(command), context, builder=builder))
|
|
241
299
|
|
|
242
300
|
|
|
@@ -274,7 +332,26 @@ def name_cmd(
|
|
|
274
332
|
@click.option(
|
|
275
333
|
"--naming-model",
|
|
276
334
|
default=None,
|
|
277
|
-
help="Defaults to the naming provider's own default model."
|
|
335
|
+
help="Defaults to the naming provider's own default model. Ignored for "
|
|
336
|
+
"--naming-provider azure — use --naming-deployment instead.",
|
|
337
|
+
)
|
|
338
|
+
@click.option(
|
|
339
|
+
"--naming-endpoint",
|
|
340
|
+
default=None,
|
|
341
|
+
help="Azure resource endpoint for naming. Only used by --naming-provider "
|
|
342
|
+
"azure; defaults to AZURE_ENDPOINT.",
|
|
343
|
+
)
|
|
344
|
+
@click.option(
|
|
345
|
+
"--naming-api-version",
|
|
346
|
+
default=None,
|
|
347
|
+
help="Azure API version for naming. Only used by --naming-provider azure; "
|
|
348
|
+
"defaults to AZURE_API_VERSION, then a built-in default.",
|
|
349
|
+
)
|
|
350
|
+
@click.option(
|
|
351
|
+
"--naming-deployment",
|
|
352
|
+
default=None,
|
|
353
|
+
help="Azure deployment name for naming — replaces --naming-model for "
|
|
354
|
+
"--naming-provider azure; defaults to AZURE_DEPLOYMENT.",
|
|
278
355
|
)
|
|
279
356
|
@click.option(
|
|
280
357
|
"--provider",
|
|
@@ -282,11 +359,39 @@ def name_cmd(
|
|
|
282
359
|
help="LLM provider for template generation (textfsm-ai's own registry, "
|
|
283
360
|
'e.g. "anthropic", "openai", "deepseek", "groq", "xai", "together", '
|
|
284
361
|
'"fireworks", "perplexity", "openrouter", "moonshot", "cerebras", '
|
|
285
|
-
'"mistral", "cohere"). Naming uses its own separate
|
|
286
|
-
"not this one.",
|
|
362
|
+
'"mistral", "cohere", "azure"). Naming uses its own separate '
|
|
363
|
+
"--naming-provider, not this one.",
|
|
287
364
|
)
|
|
288
365
|
@click.option("--api-key", required=True, help="API key for the generation LLM call.")
|
|
289
|
-
@click.option(
|
|
366
|
+
@click.option(
|
|
367
|
+
"--model",
|
|
368
|
+
required=True,
|
|
369
|
+
help="Model for the generation LLM call. For --provider azure, pass the "
|
|
370
|
+
"deployment name here via --deployment instead — this becomes the "
|
|
371
|
+
"deployment name that's actually sent.",
|
|
372
|
+
)
|
|
373
|
+
@click.option(
|
|
374
|
+
"--endpoint",
|
|
375
|
+
default=None,
|
|
376
|
+
envvar="AZURE_ENDPOINT",
|
|
377
|
+
help="Azure resource endpoint for generation. Only used by --provider "
|
|
378
|
+
"azure; defaults to the AZURE_ENDPOINT environment variable.",
|
|
379
|
+
)
|
|
380
|
+
@click.option(
|
|
381
|
+
"--api-version",
|
|
382
|
+
default=None,
|
|
383
|
+
envvar="AZURE_API_VERSION",
|
|
384
|
+
help="Azure API version for generation. Only used by --provider azure; "
|
|
385
|
+
"defaults to the AZURE_API_VERSION environment variable.",
|
|
386
|
+
)
|
|
387
|
+
@click.option(
|
|
388
|
+
"--deployment",
|
|
389
|
+
default=None,
|
|
390
|
+
envvar="AZURE_DEPLOYMENT",
|
|
391
|
+
help="Azure deployment name for generation — overrides --model for "
|
|
392
|
+
"--provider azure; defaults to the AZURE_DEPLOYMENT environment "
|
|
393
|
+
"variable.",
|
|
394
|
+
)
|
|
290
395
|
@click.option(
|
|
291
396
|
"--store-root",
|
|
292
397
|
default=None,
|
|
@@ -308,9 +413,15 @@ def run_cmd(
|
|
|
308
413
|
naming_provider: str,
|
|
309
414
|
naming_api_key: str | None,
|
|
310
415
|
naming_model: str | None,
|
|
416
|
+
naming_endpoint: str | None,
|
|
417
|
+
naming_api_version: str | None,
|
|
418
|
+
naming_deployment: str | None,
|
|
311
419
|
provider: str,
|
|
312
420
|
api_key: str,
|
|
313
421
|
model: str,
|
|
422
|
+
endpoint: str | None,
|
|
423
|
+
api_version: str | None,
|
|
424
|
+
deployment: str | None,
|
|
314
425
|
store_root: str | None,
|
|
315
426
|
project: str | None,
|
|
316
427
|
email: str | None,
|
|
@@ -335,9 +446,20 @@ def run_cmd(
|
|
|
335
446
|
connection = DeviceConnection(
|
|
336
447
|
host=host, username=username, password=password, device_type=device_type
|
|
337
448
|
)
|
|
338
|
-
naming_builder = _build_regex_builder(
|
|
449
|
+
naming_builder = _build_regex_builder(
|
|
450
|
+
naming_provider,
|
|
451
|
+
naming_api_key,
|
|
452
|
+
naming_model,
|
|
453
|
+
naming_endpoint,
|
|
454
|
+
naming_api_version,
|
|
455
|
+
naming_deployment,
|
|
456
|
+
)
|
|
339
457
|
generation_config = LLMProviderConfig(
|
|
340
|
-
provider=provider,
|
|
458
|
+
provider=provider,
|
|
459
|
+
api_key=api_key,
|
|
460
|
+
model=deployment or model,
|
|
461
|
+
endpoint=endpoint,
|
|
462
|
+
api_version=api_version,
|
|
341
463
|
)
|
|
342
464
|
metadata = TrialMetadata(
|
|
343
465
|
project=project,
|
|
@@ -391,8 +513,17 @@ def _check_connector(
|
|
|
391
513
|
click.echo("OK")
|
|
392
514
|
|
|
393
515
|
|
|
394
|
-
def _check_provider(
|
|
395
|
-
|
|
516
|
+
def _check_provider(
|
|
517
|
+
provider: str,
|
|
518
|
+
api_key: str | None,
|
|
519
|
+
model: str | None,
|
|
520
|
+
endpoint: str | None = None,
|
|
521
|
+
api_version: str | None = None,
|
|
522
|
+
deployment: str | None = None,
|
|
523
|
+
) -> None:
|
|
524
|
+
builder = _build_regex_builder(
|
|
525
|
+
provider, api_key, model, endpoint, api_version, deployment
|
|
526
|
+
)
|
|
396
527
|
context = naming.CliContext(vendor="check", family="check", os="check", version="0")
|
|
397
528
|
try:
|
|
398
529
|
response = builder.build_pattern("show clock", context)
|
|
@@ -425,6 +556,21 @@ def _check_provider(provider: str, api_key: str | None, model: str | None) -> No
|
|
|
425
556
|
)
|
|
426
557
|
@click.option("--api-key", default=None, help="Used for a --provider check.")
|
|
427
558
|
@click.option("--model", default=None, help="Used for a --provider check.")
|
|
559
|
+
@click.option(
|
|
560
|
+
"--endpoint",
|
|
561
|
+
default=None,
|
|
562
|
+
help="Used for a --provider azure check; defaults to AZURE_ENDPOINT.",
|
|
563
|
+
)
|
|
564
|
+
@click.option(
|
|
565
|
+
"--api-version",
|
|
566
|
+
default=None,
|
|
567
|
+
help="Used for a --provider azure check; defaults to AZURE_API_VERSION.",
|
|
568
|
+
)
|
|
569
|
+
@click.option(
|
|
570
|
+
"--deployment",
|
|
571
|
+
default=None,
|
|
572
|
+
help="Used for a --provider azure check; defaults to AZURE_DEPLOYMENT.",
|
|
573
|
+
)
|
|
428
574
|
def check_cmd(
|
|
429
575
|
connector: str | None,
|
|
430
576
|
provider: str | None,
|
|
@@ -436,6 +582,9 @@ def check_cmd(
|
|
|
436
582
|
probe_command: str,
|
|
437
583
|
api_key: str | None,
|
|
438
584
|
model: str | None,
|
|
585
|
+
endpoint: str | None,
|
|
586
|
+
api_version: str | None,
|
|
587
|
+
deployment: str | None,
|
|
439
588
|
) -> None:
|
|
440
589
|
"""Validate a --connector or --provider is reachable, or report what it
|
|
441
590
|
needs. Exactly one of --connector/--provider is required."""
|
|
@@ -447,7 +596,7 @@ def check_cmd(
|
|
|
447
596
|
)
|
|
448
597
|
else:
|
|
449
598
|
assert provider is not None
|
|
450
|
-
_check_provider(provider, api_key, model)
|
|
599
|
+
_check_provider(provider, api_key, model, endpoint, api_version, deployment)
|
|
451
600
|
|
|
452
601
|
|
|
453
602
|
@main.command("init-generate-template-config")
|
|
@@ -492,7 +641,34 @@ def init_generate_template_config_cmd(out_path: str, force: bool) -> None:
|
|
|
492
641
|
help='Generation LLM provider (textfsm-ai\'s own registry), e.g. "anthropic".',
|
|
493
642
|
)
|
|
494
643
|
@click.option("--api-key", default=None)
|
|
495
|
-
@click.option(
|
|
644
|
+
@click.option(
|
|
645
|
+
"--model",
|
|
646
|
+
default=None,
|
|
647
|
+
help="Model for the generation LLM call. For --provider azure, pass the "
|
|
648
|
+
"deployment name via --deployment instead — this becomes the "
|
|
649
|
+
"deployment name that's actually sent.",
|
|
650
|
+
)
|
|
651
|
+
@click.option(
|
|
652
|
+
"--endpoint",
|
|
653
|
+
default=None,
|
|
654
|
+
envvar="AZURE_ENDPOINT",
|
|
655
|
+
help="Azure resource endpoint. Only used by --provider azure; defaults to "
|
|
656
|
+
"the AZURE_ENDPOINT environment variable.",
|
|
657
|
+
)
|
|
658
|
+
@click.option(
|
|
659
|
+
"--api-version",
|
|
660
|
+
default=None,
|
|
661
|
+
envvar="AZURE_API_VERSION",
|
|
662
|
+
help="Azure API version. Only used by --provider azure; defaults to the "
|
|
663
|
+
"AZURE_API_VERSION environment variable.",
|
|
664
|
+
)
|
|
665
|
+
@click.option(
|
|
666
|
+
"--deployment",
|
|
667
|
+
default=None,
|
|
668
|
+
envvar="AZURE_DEPLOYMENT",
|
|
669
|
+
help="Azure deployment name — overrides --model for --provider azure; "
|
|
670
|
+
"defaults to the AZURE_DEPLOYMENT environment variable.",
|
|
671
|
+
)
|
|
496
672
|
@click.option(
|
|
497
673
|
"--out",
|
|
498
674
|
"out_dir",
|
|
@@ -513,6 +689,9 @@ def generate_template_cmd(
|
|
|
513
689
|
provider: str | None,
|
|
514
690
|
api_key: str | None,
|
|
515
691
|
model: str | None,
|
|
692
|
+
endpoint: str | None,
|
|
693
|
+
api_version: str | None,
|
|
694
|
+
deployment: str | None,
|
|
516
695
|
out_dir: str | None,
|
|
517
696
|
) -> None:
|
|
518
697
|
"""One-shot template generation — no trial persisted under trials/.
|
|
@@ -533,6 +712,9 @@ def generate_template_cmd(
|
|
|
533
712
|
provider = provider or cfg.provider
|
|
534
713
|
api_key = api_key or cfg.api_key
|
|
535
714
|
model = model or cfg.model
|
|
715
|
+
endpoint = endpoint or cfg.endpoint
|
|
716
|
+
api_version = api_version or cfg.api_version
|
|
717
|
+
deployment = deployment or cfg.deployment
|
|
536
718
|
|
|
537
719
|
if not provider or not model or not api_key:
|
|
538
720
|
raise click.UsageError(
|
|
@@ -552,7 +734,14 @@ def generate_template_cmd(
|
|
|
552
734
|
"or --host/--username/--password/--device-type), or --config"
|
|
553
735
|
)
|
|
554
736
|
|
|
555
|
-
result = generation.generate(
|
|
737
|
+
result = generation.generate(
|
|
738
|
+
sample_text,
|
|
739
|
+
provider,
|
|
740
|
+
api_key,
|
|
741
|
+
deployment or model,
|
|
742
|
+
endpoint=endpoint or "",
|
|
743
|
+
api_version=api_version or DEFAULT_API_VERSION,
|
|
744
|
+
)
|
|
556
745
|
if not result.ready:
|
|
557
746
|
raise click.ClickException(f"generation not ready: {result.reason}")
|
|
558
747
|
|
|
@@ -685,9 +874,20 @@ def trial_cmd(config_path: str, store_root_opt: str | None) -> None:
|
|
|
685
874
|
password=cfg.password,
|
|
686
875
|
device_type=cfg.device_type,
|
|
687
876
|
)
|
|
688
|
-
naming_builder = _build_regex_builder(
|
|
877
|
+
naming_builder = _build_regex_builder(
|
|
878
|
+
cfg.provider,
|
|
879
|
+
cfg.api_key,
|
|
880
|
+
cfg.model,
|
|
881
|
+
cfg.endpoint,
|
|
882
|
+
cfg.api_version,
|
|
883
|
+
cfg.deployment,
|
|
884
|
+
)
|
|
689
885
|
generation_config = pipeline.LLMProviderConfig(
|
|
690
|
-
provider=cfg.provider,
|
|
886
|
+
provider=cfg.provider,
|
|
887
|
+
api_key=cfg.api_key,
|
|
888
|
+
model=cfg.deployment or cfg.model,
|
|
889
|
+
endpoint=cfg.endpoint,
|
|
890
|
+
api_version=cfg.api_version,
|
|
691
891
|
)
|
|
692
892
|
metadata = pipeline.TrialMetadata(
|
|
693
893
|
username=cfg.user, email=cfg.email, description=cfg.description, note=cfg.note
|
|
@@ -10,6 +10,7 @@ from .llm import (
|
|
|
10
10
|
)
|
|
11
11
|
from .providers import (
|
|
12
12
|
AnthropicRegexBuilder,
|
|
13
|
+
AzureRegexBuilder,
|
|
13
14
|
CerebrasRegexBuilder,
|
|
14
15
|
CohereRegexBuilder,
|
|
15
16
|
DeepSeekRegexBuilder,
|
|
@@ -33,6 +34,7 @@ __all__ = [
|
|
|
33
34
|
"RegexBuilder",
|
|
34
35
|
"UnimplementedRegexBuilder",
|
|
35
36
|
"AnthropicRegexBuilder",
|
|
37
|
+
"AzureRegexBuilder",
|
|
36
38
|
"CerebrasRegexBuilder",
|
|
37
39
|
"CohereRegexBuilder",
|
|
38
40
|
"DeepSeekRegexBuilder",
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
from .anthropic import AnthropicRegexBuilder
|
|
2
|
+
from .azure import AzureRegexBuilder
|
|
2
3
|
from .cerebras import CerebrasRegexBuilder
|
|
3
4
|
from .cohere import CohereRegexBuilder
|
|
4
5
|
from .deepseek import DeepSeekRegexBuilder
|
|
@@ -14,6 +15,7 @@ from .xai import XAIRegexBuilder
|
|
|
14
15
|
|
|
15
16
|
__all__ = [
|
|
16
17
|
"AnthropicRegexBuilder",
|
|
18
|
+
"AzureRegexBuilder",
|
|
17
19
|
"CerebrasRegexBuilder",
|
|
18
20
|
"CohereRegexBuilder",
|
|
19
21
|
"DeepSeekRegexBuilder",
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
"""Azure OpenAI-backed RegexBuilder implementation.
|
|
2
|
+
|
|
3
|
+
Unlike every other provider, Azure has no fixed base_url or model catalog:
|
|
4
|
+
the endpoint is the caller's own Azure resource, and the usual "model"
|
|
5
|
+
concept is replaced by an account-specific deployment name. Talks to the
|
|
6
|
+
``azure-ai-inference`` SDK's ``ChatCompletionsClient`` directly — not
|
|
7
|
+
OpenAI-compatible, not Anthropic-style, not Mistral/Cohere-style either,
|
|
8
|
+
though its response shape (``choices[0].message.content``,
|
|
9
|
+
``finish_reason``, ``usage.prompt_tokens/completion_tokens/total_tokens``)
|
|
10
|
+
happens to closely mirror OpenAI's.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import os
|
|
16
|
+
import time
|
|
17
|
+
from typing import TYPE_CHECKING, Any
|
|
18
|
+
|
|
19
|
+
from ..llm import CliContext, LLMCLIResponse, TokenUsage, build_prompt
|
|
20
|
+
from .cost import estimate_cost
|
|
21
|
+
from .text import extract_pattern
|
|
22
|
+
|
|
23
|
+
if TYPE_CHECKING:
|
|
24
|
+
from azure.ai.inference import ChatCompletionsClient as _ChatCompletionsClient
|
|
25
|
+
|
|
26
|
+
_DEFAULT_MAX_TOKENS = 1024
|
|
27
|
+
|
|
28
|
+
# Matches textfsm-ai's own AzureOpenAIProvider.from_env() default.
|
|
29
|
+
DEFAULT_API_VERSION = "2024-02-15-preview"
|
|
30
|
+
|
|
31
|
+
# HTTP statuses where retrying the exact same request can't succeed —
|
|
32
|
+
# mirrors providers/errors.py's NON_RETRYABLE class-name set, but the
|
|
33
|
+
# azure-ai-inference SDK raises a single HttpResponseError for every HTTP
|
|
34
|
+
# failure (status on exc.status_code, populated from the underlying HTTP
|
|
35
|
+
# response) rather than a family of named exception classes, so
|
|
36
|
+
# classification here is done by status code — same approach as Mistral/
|
|
37
|
+
# Cohere.
|
|
38
|
+
_NON_RETRYABLE_STATUSES = frozenset({400, 401, 403, 404, 409, 413, 422})
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _import_azure() -> Any:
|
|
42
|
+
"""Deferred import — azure-ai-inference is an optional extra
|
|
43
|
+
(parseforge[azure]); nothing else in this module (or a cache-hit
|
|
44
|
+
lookup, which never reaches build_pattern at all — see
|
|
45
|
+
resolver.cli_name) should require it to be installed."""
|
|
46
|
+
try:
|
|
47
|
+
from azure.ai.inference import ChatCompletionsClient
|
|
48
|
+
from azure.core.credentials import AzureKeyCredential
|
|
49
|
+
from azure.core.exceptions import HttpResponseError
|
|
50
|
+
except ImportError as exc:
|
|
51
|
+
raise ImportError(
|
|
52
|
+
"the azure-ai-inference package is required to use "
|
|
53
|
+
"AzureRegexBuilder — install it via `pip install parseforge[azure]`"
|
|
54
|
+
) from exc
|
|
55
|
+
return ChatCompletionsClient, AzureKeyCredential, HttpResponseError
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _build_deployment_endpoint(endpoint: str, deployment: str) -> str:
|
|
59
|
+
"""Normalize a base Azure resource endpoint into the full deployment
|
|
60
|
+
endpoint azure-ai-inference's ChatCompletionsClient expects — mirrors
|
|
61
|
+
textfsm-ai's own build_azure_endpoint(). If the caller already passed
|
|
62
|
+
a full deployment endpoint, it's returned unchanged."""
|
|
63
|
+
endpoint = endpoint.rstrip("/")
|
|
64
|
+
if "/openai/deployments/" in endpoint:
|
|
65
|
+
return endpoint
|
|
66
|
+
return f"{endpoint}/openai/deployments/{deployment}"
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _is_retryable(exc: Any) -> bool:
|
|
70
|
+
return getattr(exc, "status_code", None) not in _NON_RETRYABLE_STATUSES
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def _format_error_reason(exc: Any) -> str:
|
|
74
|
+
return f"LLM-ERROR-azure_sdk-{getattr(exc, 'status_code', None)}-{exc}"
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class AzureRegexBuilder:
|
|
78
|
+
"""Builds a cli-name regex pattern by prompting an Azure OpenAI deployment.
|
|
79
|
+
|
|
80
|
+
Unlike every other provider, there's no fixed model catalog — the
|
|
81
|
+
``deployment`` argument (an account-specific deployment name) is
|
|
82
|
+
required, resolving from the argument if given, otherwise the
|
|
83
|
+
``AZURE_DEPLOYMENT`` environment variable. ``endpoint`` (the caller's
|
|
84
|
+
own Azure resource URL) similarly resolves from the argument, then
|
|
85
|
+
``AZURE_ENDPOINT``. ``api_version`` resolves from the argument, then
|
|
86
|
+
``AZURE_API_VERSION``, then a default matching textfsm-ai's own
|
|
87
|
+
AzureOpenAIProvider.
|
|
88
|
+
|
|
89
|
+
The client is constructed lazily, on the first actual call — not in
|
|
90
|
+
``__init__`` — so this can be used as a default RegexBuilder without
|
|
91
|
+
requiring any of these to be set for cache-hit lookups, which never
|
|
92
|
+
reach the LLM at all (see resolver.cli_name).
|
|
93
|
+
"""
|
|
94
|
+
|
|
95
|
+
provider = "azure"
|
|
96
|
+
|
|
97
|
+
def __init__(
|
|
98
|
+
self,
|
|
99
|
+
deployment: str | None = None,
|
|
100
|
+
api_key: str | None = None,
|
|
101
|
+
endpoint: str | None = None,
|
|
102
|
+
api_version: str | None = None,
|
|
103
|
+
) -> None:
|
|
104
|
+
self._deployment = deployment
|
|
105
|
+
self._api_key = api_key
|
|
106
|
+
self._endpoint = endpoint
|
|
107
|
+
self._api_version = api_version
|
|
108
|
+
self._resolved_deployment: str | None = None
|
|
109
|
+
self._client: _ChatCompletionsClient | None = None
|
|
110
|
+
|
|
111
|
+
def _get_client(self) -> _ChatCompletionsClient:
|
|
112
|
+
if self._client is None:
|
|
113
|
+
ChatCompletionsClient, AzureKeyCredential, _ = _import_azure()
|
|
114
|
+
api_key = self._api_key or os.environ.get("AZURE_API_KEY")
|
|
115
|
+
if not api_key:
|
|
116
|
+
raise RuntimeError(
|
|
117
|
+
"no Azure API key — pass api_key or set AZURE_API_KEY"
|
|
118
|
+
)
|
|
119
|
+
endpoint = self._endpoint or os.environ.get("AZURE_ENDPOINT")
|
|
120
|
+
if not endpoint:
|
|
121
|
+
raise RuntimeError(
|
|
122
|
+
"no Azure endpoint — pass endpoint or set AZURE_ENDPOINT"
|
|
123
|
+
)
|
|
124
|
+
deployment = self._deployment or os.environ.get("AZURE_DEPLOYMENT")
|
|
125
|
+
if not deployment:
|
|
126
|
+
raise RuntimeError(
|
|
127
|
+
"no Azure deployment — pass deployment or set AZURE_DEPLOYMENT"
|
|
128
|
+
)
|
|
129
|
+
api_version = (
|
|
130
|
+
self._api_version
|
|
131
|
+
or os.environ.get("AZURE_API_VERSION")
|
|
132
|
+
or DEFAULT_API_VERSION
|
|
133
|
+
)
|
|
134
|
+
self._resolved_deployment = deployment
|
|
135
|
+
self._client = ChatCompletionsClient(
|
|
136
|
+
endpoint=_build_deployment_endpoint(endpoint, deployment),
|
|
137
|
+
credential=AzureKeyCredential(api_key),
|
|
138
|
+
api_version=api_version,
|
|
139
|
+
)
|
|
140
|
+
return self._client
|
|
141
|
+
|
|
142
|
+
def build_pattern(
|
|
143
|
+
self, command: str, context: CliContext, **kwargs: Any
|
|
144
|
+
) -> LLMCLIResponse:
|
|
145
|
+
_, _, HttpResponseError = _import_azure()
|
|
146
|
+
prompt = build_prompt(command, context)
|
|
147
|
+
max_tokens = kwargs.pop("max_tokens", None) or _DEFAULT_MAX_TOKENS
|
|
148
|
+
|
|
149
|
+
start = time.monotonic()
|
|
150
|
+
try:
|
|
151
|
+
response = self._get_client().complete(
|
|
152
|
+
messages=[{"role": "user", "content": prompt}],
|
|
153
|
+
max_tokens=max_tokens,
|
|
154
|
+
**kwargs,
|
|
155
|
+
)
|
|
156
|
+
except HttpResponseError as exc:
|
|
157
|
+
if not _is_retryable(exc):
|
|
158
|
+
# Same request would fail the same way again — stop rather
|
|
159
|
+
# than let a caller burn another attempt on it.
|
|
160
|
+
raise
|
|
161
|
+
return LLMCLIResponse(
|
|
162
|
+
content="",
|
|
163
|
+
raw=exc,
|
|
164
|
+
usage=TokenUsage(
|
|
165
|
+
input_tokens=0, output_tokens=0, total_tokens=0, estimated_cost=0.0
|
|
166
|
+
),
|
|
167
|
+
duration_ms=(time.monotonic() - start) * 1000,
|
|
168
|
+
reason=_format_error_reason(exc),
|
|
169
|
+
ready=False,
|
|
170
|
+
)
|
|
171
|
+
duration_ms = (time.monotonic() - start) * 1000
|
|
172
|
+
|
|
173
|
+
choice = response.choices[0]
|
|
174
|
+
return LLMCLIResponse(
|
|
175
|
+
content=extract_pattern(choice.message.content or ""),
|
|
176
|
+
raw=response,
|
|
177
|
+
usage=TokenUsage(
|
|
178
|
+
input_tokens=response.usage.prompt_tokens,
|
|
179
|
+
output_tokens=response.usage.completion_tokens,
|
|
180
|
+
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
|
+
),
|
|
194
|
+
duration_ms=duration_ms,
|
|
195
|
+
reason=choice.finish_reason or "",
|
|
196
|
+
ready=choice.finish_reason == "stop",
|
|
197
|
+
)
|
|
@@ -46,6 +46,7 @@ from pathlib import Path
|
|
|
46
46
|
from typing import Any
|
|
47
47
|
|
|
48
48
|
from parseforge import generation, naming, paths, sampling, validation
|
|
49
|
+
from parseforge.naming.providers.azure import DEFAULT_API_VERSION
|
|
49
50
|
|
|
50
51
|
|
|
51
52
|
class Mode(str, Enum):
|
|
@@ -55,9 +56,18 @@ class Mode(str, Enum):
|
|
|
55
56
|
|
|
56
57
|
@dataclass(frozen=True)
|
|
57
58
|
class LLMProviderConfig:
|
|
59
|
+
"""``model`` doubles as the deployment name for provider="azure" (no
|
|
60
|
+
fixed model catalog there — see naming/providers/azure.py); the CLI
|
|
61
|
+
resolves --deployment into this field so this dataclass and
|
|
62
|
+
generation.generate()'s positional model arg stay untouched.
|
|
63
|
+
``endpoint``/``api_version`` are ignored by every provider except
|
|
64
|
+
azure (see textfsm-ai's generation_engine.run())."""
|
|
65
|
+
|
|
58
66
|
provider: str
|
|
59
67
|
api_key: str
|
|
60
68
|
model: str
|
|
69
|
+
endpoint: str | None = None
|
|
70
|
+
api_version: str | None = None
|
|
61
71
|
|
|
62
72
|
|
|
63
73
|
@dataclass(frozen=True)
|
|
@@ -153,6 +163,13 @@ def run_command_pipeline(
|
|
|
153
163
|
generation_config.provider,
|
|
154
164
|
generation_config.api_key,
|
|
155
165
|
generation_config.model,
|
|
166
|
+
endpoint=generation_config.endpoint or "",
|
|
167
|
+
# Only meaningful for provider="azure" (ignored by every other
|
|
168
|
+
# provider — see textfsm-ai's generation_engine.run()), which has
|
|
169
|
+
# no from_env()-style fallback of its own on this path, unlike
|
|
170
|
+
# naming/providers/azure.py's own AzureRegexBuilder — so the same
|
|
171
|
+
# default is applied here explicitly.
|
|
172
|
+
api_version=generation_config.api_version or DEFAULT_API_VERSION,
|
|
156
173
|
)
|
|
157
174
|
(derive_dir / "llm-template.textfsm").write_text(
|
|
158
175
|
gen_result.raw_template, encoding="utf-8"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: parseforge
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.9
|
|
4
4
|
Summary: LLM-driven pipeline that forges, validates, and promotes TextFSM templates from network CLI output
|
|
5
5
|
Author-email: Tuyen Mathew Duong <tuyen@geekstrident.com>
|
|
6
6
|
Maintainer-email: Tuyen Mathew Duong <tuyen@geekstrident.com>
|
|
@@ -66,6 +66,9 @@ Requires-Dist: textfsm-ai[mistral]>=0.6.1; extra == "mistral"
|
|
|
66
66
|
Provides-Extra: cohere
|
|
67
67
|
Requires-Dist: cohere==5.21.1; extra == "cohere"
|
|
68
68
|
Requires-Dist: textfsm-ai[cohere]>=0.6.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.6.1; extra == "azure"
|
|
69
72
|
Provides-Extra: sampling
|
|
70
73
|
Requires-Dist: netmiko>=4.0; extra == "sampling"
|
|
71
74
|
Provides-Extra: dev
|
|
@@ -78,6 +81,7 @@ Requires-Dist: anthropic>=0.40.0; extra == "dev"
|
|
|
78
81
|
Requires-Dist: openai>=1.0.0; extra == "dev"
|
|
79
82
|
Requires-Dist: mistralai==1.10.0; extra == "dev"
|
|
80
83
|
Requires-Dist: cohere==5.21.1; extra == "dev"
|
|
84
|
+
Requires-Dist: azure-ai-inference>=1.0.0b9; extra == "dev"
|
|
81
85
|
Provides-Extra: release
|
|
82
86
|
Requires-Dist: bump2version; extra == "release"
|
|
83
87
|
Requires-Dist: build; extra == "release"
|
|
@@ -120,11 +124,11 @@ command that's pure local processing (`canonical`/`readable`/`recognizers`,
|
|
|
120
124
|
LLM (`name`, `check --provider`, `run`, `generate-template`, `trial`) needs the
|
|
121
125
|
extra for whichever provider it uses: `anthropic`, `openai`, `deepseek`,
|
|
122
126
|
`groq`, `xai`, `together`, `fireworks`, `perplexity`, `openrouter`,
|
|
123
|
-
`moonshot`, `cerebras`, `mistral`, or `
|
|
124
|
-
`anthropic` wherever it isn't required, so that's the one most setups
|
|
125
|
-
`pip install parseforge[sampling]` adds Netmiko for live device
|
|
126
|
-
combine extras as needed, e.g.
|
|
127
|
-
`pip install parseforge[anthropic,openai,deepseek,groq,xai,together,fireworks,perplexity,openrouter,moonshot,cerebras,mistral,cohere,sampling]`.
|
|
127
|
+
`moonshot`, `cerebras`, `mistral`, `cohere`, or `azure`. `--provider` defaults
|
|
128
|
+
to `anthropic` wherever it isn't required, so that's the one most setups
|
|
129
|
+
need. `pip install parseforge[sampling]` adds Netmiko for live device
|
|
130
|
+
sampling; combine extras as needed, e.g.
|
|
131
|
+
`pip install parseforge[anthropic,openai,deepseek,groq,xai,together,fireworks,perplexity,openrouter,moonshot,cerebras,mistral,cohere,azure,sampling]`.
|
|
128
132
|
|
|
129
133
|
## Development
|
|
130
134
|
|
|
@@ -132,13 +136,13 @@ combine extras as needed, e.g.
|
|
|
132
136
|
pip install -e ".[dev,sampling]"
|
|
133
137
|
pytest
|
|
134
138
|
```
|
|
135
|
-
`dev` already includes the `anthropic`, `openai`, `mistralai`,
|
|
136
|
-
SDKs (tests exercise all
|
|
137
|
-
`deepseek`, `groq`, `xai`, `together`, `fireworks`,
|
|
138
|
-
`openrouter`, `moonshot`, `cerebras` share just the first two
|
|
139
|
-
`mistral`/`cohere` each need their own
|
|
140
|
-
skip) — add the specific `,<provider>` extra explicitly only if
|
|
141
|
-
outside of `dev`.
|
|
139
|
+
`dev` already includes the `anthropic`, `openai`, `mistralai`, `cohere`, and
|
|
140
|
+
`azure-ai-inference` SDKs (tests exercise all fourteen providers —
|
|
141
|
+
`anthropic`, `openai`, `deepseek`, `groq`, `xai`, `together`, `fireworks`,
|
|
142
|
+
`perplexity`, `openrouter`, `moonshot`, `cerebras` share just the first two
|
|
143
|
+
packages, and `mistral`/`cohere`/`azure` each need their own SDK — and never
|
|
144
|
+
silently skip) — add the specific `,<provider>` extra explicitly only if
|
|
145
|
+
installing outside of `dev`.
|
|
142
146
|
|
|
143
147
|
Linting/formatting/type-checking/docs run through tox instead of extras — see
|
|
144
148
|
`tox.ini` (`tox -e lint`/`format`/`typecheck`/`docs`), each installing its own
|
|
@@ -160,8 +164,14 @@ parseforge name --vendor cisco --family catalyst9200 --os ios-xe --version 17.9.
|
|
|
160
164
|
```
|
|
161
165
|
Still needs an LLM provider on a cache miss — `--provider` defaults to `anthropic`,
|
|
162
166
|
and `--api-key` falls back to that provider's own env var (`ANTHROPIC_API_KEY`/
|
|
163
|
-
`OPENAI_API_KEY`/`DEEPSEEK_API_KEY
|
|
164
|
-
never touches the LLM, so no key is needed at all in that case.
|
|
167
|
+
`OPENAI_API_KEY`/`DEEPSEEK_API_KEY`/etc.). A cache hit (a command already seen
|
|
168
|
+
before) never touches the LLM, so no key is needed at all in that case.
|
|
169
|
+
|
|
170
|
+
`--provider azure` is the one exception to the usual `--api-key`/`--model` shape —
|
|
171
|
+
Azure has no fixed base_url or model catalog, so it also needs `--endpoint`
|
|
172
|
+
(`AZURE_ENDPOINT`), `--api-version` (`AZURE_API_VERSION`), and `--deployment`
|
|
173
|
+
(`AZURE_DEPLOYMENT`, replacing `--model`). Same three options exist on
|
|
174
|
+
`check --provider`, `run`, and `generate-template` below.
|
|
165
175
|
|
|
166
176
|
**`check`** — validate a connector or provider before spending time/tokens on a real
|
|
167
177
|
run. With neither `--env` nor explicit connection flags, prints what a connector needs
|
|
@@ -206,7 +216,10 @@ parseforge run --vendor cisco --family catalyst9200 --os ios-xe --version 17.9.1
|
|
|
206
216
|
`--naming-provider`/`--naming-api-key`/`--naming-model`, defaulting independently
|
|
207
217
|
(`--naming-provider` defaults to `anthropic`; the other two fall back to that
|
|
208
218
|
provider's own env var/default model) — set them explicitly if naming needs a
|
|
209
|
-
different provider than generation.
|
|
219
|
+
different provider than generation. `--provider azure`/`--naming-provider azure`
|
|
220
|
+
each get their own `--endpoint`/`--api-version`/`--deployment` (generation) and
|
|
221
|
+
`--naming-endpoint`/`--naming-api-version`/`--naming-deployment` (naming) — see
|
|
222
|
+
the `name` section above for what they're for.
|
|
210
223
|
|
|
211
224
|
**`init-trial-config`** — write a placeholder `trial.yaml` to fill in, instead of
|
|
212
225
|
writing one by hand:
|
|
@@ -239,7 +252,9 @@ workers: 1
|
|
|
239
252
|
```
|
|
240
253
|
`provider`/`api_key`/`model` are one shared LLM source used for both naming and
|
|
241
254
|
generation. Use the `run` command's separate `--naming-*`/`--generation-*` flags
|
|
242
|
-
instead if a trial actually needs two different providers.
|
|
255
|
+
instead if a trial actually needs two different providers. For `provider: azure`,
|
|
256
|
+
also set `endpoint`/`api_version`/`deployment` (`deployment` replaces `model`) —
|
|
257
|
+
see `parseforge init-trial-config`'s generated placeholder for the exact keys.
|
|
243
258
|
|
|
244
259
|
**`integration`** — rebuild `integration/` for every case under `trials/` (SPEC §5
|
|
245
260
|
step 8):
|
|
@@ -29,6 +29,7 @@ parseforge/naming/prompts.yaml
|
|
|
29
29
|
parseforge/naming/resolver.py
|
|
30
30
|
parseforge/naming/providers/__init__.py
|
|
31
31
|
parseforge/naming/providers/anthropic.py
|
|
32
|
+
parseforge/naming/providers/azure.py
|
|
32
33
|
parseforge/naming/providers/cerebras.py
|
|
33
34
|
parseforge/naming/providers/cohere.py
|
|
34
35
|
parseforge/naming/providers/cost.py
|
|
@@ -7,6 +7,10 @@ textfsm-ai>=0.6.1
|
|
|
7
7
|
anthropic>=0.40.0
|
|
8
8
|
textfsm-ai[anthropic]>=0.6.1
|
|
9
9
|
|
|
10
|
+
[azure]
|
|
11
|
+
azure-ai-inference>=1.0.0b9
|
|
12
|
+
textfsm-ai[azure]>=0.6.1
|
|
13
|
+
|
|
10
14
|
[cerebras]
|
|
11
15
|
openai>=1.0.0
|
|
12
16
|
textfsm-ai[cerebras]>=0.6.1
|
|
@@ -29,6 +33,7 @@ anthropic>=0.40.0
|
|
|
29
33
|
openai>=1.0.0
|
|
30
34
|
mistralai==1.10.0
|
|
31
35
|
cohere==5.21.1
|
|
36
|
+
azure-ai-inference>=1.0.0b9
|
|
32
37
|
|
|
33
38
|
[fireworks]
|
|
34
39
|
openai>=1.0.0
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "parseforge"
|
|
7
|
-
version = "0.2.
|
|
7
|
+
version = "0.2.9"
|
|
8
8
|
description = "LLM-driven pipeline that forges, validates, and promotes TextFSM templates from network CLI output"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = { text = "MIT" }
|
|
@@ -81,6 +81,7 @@ mistral = ["mistralai==1.10.0", "textfsm-ai[mistral]>=0.6.1"]
|
|
|
81
81
|
# supporting Python 3.9 (6.x/7.x require Python >=3.10), matching
|
|
82
82
|
# textfsm-ai's own pin.
|
|
83
83
|
cohere = ["cohere==5.21.1", "textfsm-ai[cohere]>=0.6.1"]
|
|
84
|
+
azure = ["azure-ai-inference>=1.0.0b9", "textfsm-ai[azure]>=0.6.1"]
|
|
84
85
|
sampling = ["netmiko>=4.0"]
|
|
85
86
|
dev = [
|
|
86
87
|
"pytest>=8.0.0",
|
|
@@ -88,13 +89,15 @@ dev = [
|
|
|
88
89
|
"ruff>=0.4.0",
|
|
89
90
|
"black>=24.0.0",
|
|
90
91
|
"mypy>=1.10.0",
|
|
91
|
-
# anthropic/openai/mistralai/cohere are optional at
|
|
92
|
-
# extras above), but every provider is exercised by
|
|
93
|
-
# which should never silently skip -- so dev always
|
|
92
|
+
# anthropic/openai/mistralai/cohere/azure-ai-inference are optional at
|
|
93
|
+
# runtime (see the extras above), but every provider is exercised by
|
|
94
|
+
# the CLI/tests, which should never silently skip -- so dev always
|
|
95
|
+
# pulls all five in.
|
|
94
96
|
"anthropic>=0.40.0",
|
|
95
97
|
"openai>=1.0.0",
|
|
96
98
|
"mistralai==1.10.0",
|
|
97
99
|
"cohere==5.21.1",
|
|
100
|
+
"azure-ai-inference>=1.0.0b9",
|
|
98
101
|
]
|
|
99
102
|
# Tooling for scripts/release.ps1 and CI's own package-build step.
|
|
100
103
|
# bump2version isn't otherwise installed anywhere -- release.ps1 assumes
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.2.8"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|