parseforge 0.2.4__tar.gz → 0.2.6__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.4/parseforge.egg-info → parseforge-0.2.6}/PKG-INFO +42 -11
- {parseforge-0.2.4 → parseforge-0.2.6}/README.md +14 -10
- parseforge-0.2.6/parseforge/__init__.py +1 -0
- {parseforge-0.2.4 → parseforge-0.2.6}/parseforge/cli/main.py +16 -3
- {parseforge-0.2.4 → parseforge-0.2.6}/parseforge/naming/__init__.py +22 -1
- parseforge-0.2.6/parseforge/naming/providers/__init__.py +25 -0
- parseforge-0.2.6/parseforge/naming/providers/cerebras.py +128 -0
- parseforge-0.2.6/parseforge/naming/providers/fireworks.py +128 -0
- parseforge-0.2.6/parseforge/naming/providers/groq.py +126 -0
- parseforge-0.2.6/parseforge/naming/providers/models.yaml +112 -0
- parseforge-0.2.6/parseforge/naming/providers/moonshot.py +130 -0
- parseforge-0.2.6/parseforge/naming/providers/openai.py +117 -0
- parseforge-0.2.6/parseforge/naming/providers/openrouter.py +130 -0
- parseforge-0.2.6/parseforge/naming/providers/perplexity.py +128 -0
- parseforge-0.2.6/parseforge/naming/providers/together.py +128 -0
- parseforge-0.2.6/parseforge/naming/providers/xai.py +126 -0
- {parseforge-0.2.4 → parseforge-0.2.6/parseforge.egg-info}/PKG-INFO +42 -11
- {parseforge-0.2.4 → parseforge-0.2.6}/parseforge.egg-info/SOURCES.txt +9 -0
- parseforge-0.2.6/parseforge.egg-info/requires.txt +64 -0
- {parseforge-0.2.4 → parseforge-0.2.6}/pyproject.toml +10 -1
- parseforge-0.2.4/parseforge/__init__.py +0 -1
- parseforge-0.2.4/parseforge/naming/providers/__init__.py +0 -4
- parseforge-0.2.4/parseforge/naming/providers/models.yaml +0 -24
- parseforge-0.2.4/parseforge.egg-info/requires.txt +0 -28
- {parseforge-0.2.4 → parseforge-0.2.6}/LICENSE +0 -0
- {parseforge-0.2.4 → parseforge-0.2.6}/MANIFEST.in +0 -0
- {parseforge-0.2.4 → parseforge-0.2.6}/SPEC.md +0 -0
- {parseforge-0.2.4 → parseforge-0.2.6}/parseforge/cli/__init__.py +0 -0
- {parseforge-0.2.4 → parseforge-0.2.6}/parseforge/cli/config.py +0 -0
- {parseforge-0.2.4 → parseforge-0.2.6}/parseforge/drift.py +0 -0
- {parseforge-0.2.4 → parseforge-0.2.6}/parseforge/generation.py +0 -0
- {parseforge-0.2.4 → parseforge-0.2.6}/parseforge/integration.py +0 -0
- {parseforge-0.2.4 → parseforge-0.2.6}/parseforge/naming/assemble.py +0 -0
- {parseforge-0.2.4 → parseforge-0.2.6}/parseforge/naming/cache.py +0 -0
- {parseforge-0.2.4 → parseforge-0.2.6}/parseforge/naming/llm.py +0 -0
- {parseforge-0.2.4 → parseforge-0.2.6}/parseforge/naming/prompts.py +0 -0
- {parseforge-0.2.4 → parseforge-0.2.6}/parseforge/naming/prompts.yaml +0 -0
- {parseforge-0.2.4 → parseforge-0.2.6}/parseforge/naming/providers/anthropic.py +0 -0
- {parseforge-0.2.4 → parseforge-0.2.6}/parseforge/naming/providers/cost.py +0 -0
- {parseforge-0.2.4 → parseforge-0.2.6}/parseforge/naming/providers/deepseek.py +0 -0
- {parseforge-0.2.4 → parseforge-0.2.6}/parseforge/naming/providers/errors.py +0 -0
- {parseforge-0.2.4 → parseforge-0.2.6}/parseforge/naming/providers/models.py +0 -0
- {parseforge-0.2.4 → parseforge-0.2.6}/parseforge/naming/providers/text.py +0 -0
- {parseforge-0.2.4 → parseforge-0.2.6}/parseforge/naming/resolver.py +0 -0
- {parseforge-0.2.4 → parseforge-0.2.6}/parseforge/paths.py +0 -0
- {parseforge-0.2.4 → parseforge-0.2.6}/parseforge/pipeline.py +0 -0
- {parseforge-0.2.4 → parseforge-0.2.6}/parseforge/promotion.py +0 -0
- {parseforge-0.2.4 → parseforge-0.2.6}/parseforge/sampling/__init__.py +0 -0
- {parseforge-0.2.4 → parseforge-0.2.6}/parseforge/sampling/backends/__init__.py +0 -0
- {parseforge-0.2.4 → parseforge-0.2.6}/parseforge/sampling/backends/netmiko.py +0 -0
- {parseforge-0.2.4 → parseforge-0.2.6}/parseforge/sampling/core.py +0 -0
- {parseforge-0.2.4 → parseforge-0.2.6}/parseforge/validation.py +0 -0
- {parseforge-0.2.4 → parseforge-0.2.6}/parseforge.egg-info/dependency_links.txt +0 -0
- {parseforge-0.2.4 → parseforge-0.2.6}/parseforge.egg-info/entry_points.txt +0 -0
- {parseforge-0.2.4 → parseforge-0.2.6}/parseforge.egg-info/top_level.txt +0 -0
- {parseforge-0.2.4 → parseforge-0.2.6}/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.6
|
|
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>
|
|
@@ -33,6 +33,33 @@ Requires-Dist: textfsm-ai[anthropic]>=0.6.1; extra == "anthropic"
|
|
|
33
33
|
Provides-Extra: deepseek
|
|
34
34
|
Requires-Dist: openai>=1.0.0; extra == "deepseek"
|
|
35
35
|
Requires-Dist: textfsm-ai[deepseek]>=0.6.1; extra == "deepseek"
|
|
36
|
+
Provides-Extra: openai
|
|
37
|
+
Requires-Dist: openai>=1.0.0; extra == "openai"
|
|
38
|
+
Requires-Dist: textfsm-ai[openai]>=0.6.1; extra == "openai"
|
|
39
|
+
Provides-Extra: groq
|
|
40
|
+
Requires-Dist: openai>=1.0.0; extra == "groq"
|
|
41
|
+
Requires-Dist: textfsm-ai[groq]>=0.6.1; extra == "groq"
|
|
42
|
+
Provides-Extra: xai
|
|
43
|
+
Requires-Dist: openai>=1.0.0; extra == "xai"
|
|
44
|
+
Requires-Dist: textfsm-ai[xai]>=0.6.1; extra == "xai"
|
|
45
|
+
Provides-Extra: together
|
|
46
|
+
Requires-Dist: openai>=1.0.0; extra == "together"
|
|
47
|
+
Requires-Dist: textfsm-ai[together]>=0.6.1; extra == "together"
|
|
48
|
+
Provides-Extra: fireworks
|
|
49
|
+
Requires-Dist: openai>=1.0.0; extra == "fireworks"
|
|
50
|
+
Requires-Dist: textfsm-ai[fireworks]>=0.6.1; extra == "fireworks"
|
|
51
|
+
Provides-Extra: perplexity
|
|
52
|
+
Requires-Dist: openai>=1.0.0; extra == "perplexity"
|
|
53
|
+
Requires-Dist: textfsm-ai[perplexity]>=0.6.1; extra == "perplexity"
|
|
54
|
+
Provides-Extra: openrouter
|
|
55
|
+
Requires-Dist: openai>=1.0.0; extra == "openrouter"
|
|
56
|
+
Requires-Dist: textfsm-ai[openrouter]>=0.6.1; extra == "openrouter"
|
|
57
|
+
Provides-Extra: moonshot
|
|
58
|
+
Requires-Dist: openai>=1.0.0; extra == "moonshot"
|
|
59
|
+
Requires-Dist: textfsm-ai[moonshot]>=0.6.1; extra == "moonshot"
|
|
60
|
+
Provides-Extra: cerebras
|
|
61
|
+
Requires-Dist: openai>=1.0.0; extra == "cerebras"
|
|
62
|
+
Requires-Dist: textfsm-ai[cerebras]>=0.6.1; extra == "cerebras"
|
|
36
63
|
Provides-Extra: sampling
|
|
37
64
|
Requires-Dist: netmiko>=4.0; extra == "sampling"
|
|
38
65
|
Provides-Extra: dev
|
|
@@ -83,11 +110,13 @@ pip install parseforge[anthropic]
|
|
|
83
110
|
command that's pure local processing (`canonical`/`readable`/`recognizers`,
|
|
84
111
|
`integration`, `promotion`) works with nothing further. Anything that calls an
|
|
85
112
|
LLM (`name`, `check --provider`, `run`, `generate-template`, `trial`) needs the
|
|
86
|
-
extra for whichever provider it uses: `anthropic`
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
`
|
|
113
|
+
extra for whichever provider it uses: `anthropic`, `openai`, `deepseek`,
|
|
114
|
+
`groq`, `xai`, `together`, `fireworks`, `perplexity`, `openrouter`,
|
|
115
|
+
`moonshot`, or `cerebras`. `--provider` defaults to `anthropic` wherever it
|
|
116
|
+
isn't required, so that's the one most setups need. `pip install
|
|
117
|
+
parseforge[sampling]` adds Netmiko for live device sampling; combine extras
|
|
118
|
+
as needed, e.g.
|
|
119
|
+
`pip install parseforge[anthropic,openai,deepseek,groq,xai,together,fireworks,perplexity,openrouter,moonshot,cerebras,sampling]`.
|
|
91
120
|
|
|
92
121
|
## Development
|
|
93
122
|
|
|
@@ -95,9 +124,11 @@ sampling; combine extras as needed, e.g.
|
|
|
95
124
|
pip install -e ".[dev,sampling]"
|
|
96
125
|
pytest
|
|
97
126
|
```
|
|
98
|
-
`dev` already includes both `anthropic` and `openai` (tests exercise
|
|
99
|
-
|
|
100
|
-
|
|
127
|
+
`dev` already includes both the `anthropic` and `openai` SDKs (tests exercise
|
|
128
|
+
all eleven providers — `anthropic`, `openai`, `deepseek`, `groq`, `xai`,
|
|
129
|
+
`together`, `fireworks`, `perplexity`, `openrouter`, `moonshot`, `cerebras`
|
|
130
|
+
share just those two packages — and never silently skip) — add the specific
|
|
131
|
+
`,<provider>` extra explicitly only if installing outside of `dev`.
|
|
101
132
|
|
|
102
133
|
Linting/formatting/type-checking/docs run through tox instead of extras — see
|
|
103
134
|
`tox.ini` (`tox -e lint`/`format`/`typecheck`/`docs`), each installing its own
|
|
@@ -119,8 +150,8 @@ parseforge name --vendor cisco --family catalyst9200 --os ios-xe --version 17.9.
|
|
|
119
150
|
```
|
|
120
151
|
Still needs an LLM provider on a cache miss — `--provider` defaults to `anthropic`,
|
|
121
152
|
and `--api-key` falls back to that provider's own env var (`ANTHROPIC_API_KEY`/
|
|
122
|
-
`DEEPSEEK_API_KEY`). A cache hit (a command already seen before)
|
|
123
|
-
LLM, so no key is needed at all in that case.
|
|
153
|
+
`OPENAI_API_KEY`/`DEEPSEEK_API_KEY`). A cache hit (a command already seen before)
|
|
154
|
+
never touches the LLM, so no key is needed at all in that case.
|
|
124
155
|
|
|
125
156
|
**`check`** — validate a connector or provider before spending time/tokens on a real
|
|
126
157
|
run. With neither `--env` nor explicit connection flags, prints what a connector needs
|
|
@@ -33,11 +33,13 @@ pip install parseforge[anthropic]
|
|
|
33
33
|
command that's pure local processing (`canonical`/`readable`/`recognizers`,
|
|
34
34
|
`integration`, `promotion`) works with nothing further. Anything that calls an
|
|
35
35
|
LLM (`name`, `check --provider`, `run`, `generate-template`, `trial`) needs the
|
|
36
|
-
extra for whichever provider it uses: `anthropic`
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
`
|
|
36
|
+
extra for whichever provider it uses: `anthropic`, `openai`, `deepseek`,
|
|
37
|
+
`groq`, `xai`, `together`, `fireworks`, `perplexity`, `openrouter`,
|
|
38
|
+
`moonshot`, or `cerebras`. `--provider` defaults to `anthropic` wherever it
|
|
39
|
+
isn't required, so that's the one most setups need. `pip install
|
|
40
|
+
parseforge[sampling]` adds Netmiko for live device sampling; combine extras
|
|
41
|
+
as needed, e.g.
|
|
42
|
+
`pip install parseforge[anthropic,openai,deepseek,groq,xai,together,fireworks,perplexity,openrouter,moonshot,cerebras,sampling]`.
|
|
41
43
|
|
|
42
44
|
## Development
|
|
43
45
|
|
|
@@ -45,9 +47,11 @@ sampling; combine extras as needed, e.g.
|
|
|
45
47
|
pip install -e ".[dev,sampling]"
|
|
46
48
|
pytest
|
|
47
49
|
```
|
|
48
|
-
`dev` already includes both `anthropic` and `openai` (tests exercise
|
|
49
|
-
|
|
50
|
-
|
|
50
|
+
`dev` already includes both the `anthropic` and `openai` SDKs (tests exercise
|
|
51
|
+
all eleven providers — `anthropic`, `openai`, `deepseek`, `groq`, `xai`,
|
|
52
|
+
`together`, `fireworks`, `perplexity`, `openrouter`, `moonshot`, `cerebras`
|
|
53
|
+
share just those two packages — and never silently skip) — add the specific
|
|
54
|
+
`,<provider>` extra explicitly only if installing outside of `dev`.
|
|
51
55
|
|
|
52
56
|
Linting/formatting/type-checking/docs run through tox instead of extras — see
|
|
53
57
|
`tox.ini` (`tox -e lint`/`format`/`typecheck`/`docs`), each installing its own
|
|
@@ -69,8 +73,8 @@ parseforge name --vendor cisco --family catalyst9200 --os ios-xe --version 17.9.
|
|
|
69
73
|
```
|
|
70
74
|
Still needs an LLM provider on a cache miss — `--provider` defaults to `anthropic`,
|
|
71
75
|
and `--api-key` falls back to that provider's own env var (`ANTHROPIC_API_KEY`/
|
|
72
|
-
`DEEPSEEK_API_KEY`). A cache hit (a command already seen before)
|
|
73
|
-
LLM, so no key is needed at all in that case.
|
|
76
|
+
`OPENAI_API_KEY`/`DEEPSEEK_API_KEY`). A cache hit (a command already seen before)
|
|
77
|
+
never touches the LLM, so no key is needed at all in that case.
|
|
74
78
|
|
|
75
79
|
**`check`** — validate a connector or provider before spending time/tokens on a real
|
|
76
80
|
run. With neither `--env` nor explicit connection flags, prints what a connector needs
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.2.6"
|
|
@@ -23,7 +23,16 @@ from parseforge.cli import config as cli_config
|
|
|
23
23
|
|
|
24
24
|
_BUILDERS: dict[str, type[naming.RegexBuilder]] = {
|
|
25
25
|
"anthropic": naming.AnthropicRegexBuilder,
|
|
26
|
+
"cerebras": naming.CerebrasRegexBuilder,
|
|
26
27
|
"deepseek": naming.DeepSeekRegexBuilder,
|
|
28
|
+
"fireworks": naming.FireworksRegexBuilder,
|
|
29
|
+
"groq": naming.GroqRegexBuilder,
|
|
30
|
+
"moonshot": naming.MoonshotRegexBuilder,
|
|
31
|
+
"openai": naming.OpenAIRegexBuilder,
|
|
32
|
+
"openrouter": naming.OpenRouterRegexBuilder,
|
|
33
|
+
"perplexity": naming.PerplexityRegexBuilder,
|
|
34
|
+
"together": naming.TogetherRegexBuilder,
|
|
35
|
+
"xai": naming.XAIRegexBuilder,
|
|
27
36
|
}
|
|
28
37
|
|
|
29
38
|
_CONNECTORS = ("netmiko",)
|
|
@@ -198,7 +207,10 @@ def main() -> None:
|
|
|
198
207
|
"--api-key",
|
|
199
208
|
default=None,
|
|
200
209
|
help="Provider API key. Defaults to that provider's own API key environment "
|
|
201
|
-
"variable (ANTHROPIC_API_KEY, DEEPSEEK_API_KEY
|
|
210
|
+
"variable (ANTHROPIC_API_KEY, OPENAI_API_KEY, DEEPSEEK_API_KEY, GROQ_API_KEY, "
|
|
211
|
+
"XAI_API_KEY, TOGETHER_API_KEY, FIREWORKS_API_KEY, PERPLEXITY_API_KEY, "
|
|
212
|
+
"OPENROUTER_API_KEY, MOONSHOT_API_KEY, CEREBRAS_API_KEY); only needed on a "
|
|
213
|
+
"cache miss.",
|
|
202
214
|
)
|
|
203
215
|
@click.option(
|
|
204
216
|
"--model",
|
|
@@ -266,8 +278,9 @@ def name_cmd(
|
|
|
266
278
|
"--provider",
|
|
267
279
|
required=True,
|
|
268
280
|
help="LLM provider for template generation (textfsm-ai's own registry, "
|
|
269
|
-
'e.g. "anthropic", "deepseek"
|
|
270
|
-
"
|
|
281
|
+
'e.g. "anthropic", "openai", "deepseek", "groq", "xai", "together", '
|
|
282
|
+
'"fireworks", "perplexity", "openrouter", "moonshot", "cerebras"). Naming '
|
|
283
|
+
"uses its own separate --naming-provider, not this one.",
|
|
271
284
|
)
|
|
272
285
|
@click.option("--api-key", required=True, help="API key for the generation LLM call.")
|
|
273
286
|
@click.option("--model", required=True, help="Model for the generation LLM call.")
|
|
@@ -8,7 +8,19 @@ from .llm import (
|
|
|
8
8
|
UnimplementedRegexBuilder,
|
|
9
9
|
build_prompt,
|
|
10
10
|
)
|
|
11
|
-
from .providers import
|
|
11
|
+
from .providers import (
|
|
12
|
+
AnthropicRegexBuilder,
|
|
13
|
+
CerebrasRegexBuilder,
|
|
14
|
+
DeepSeekRegexBuilder,
|
|
15
|
+
FireworksRegexBuilder,
|
|
16
|
+
GroqRegexBuilder,
|
|
17
|
+
MoonshotRegexBuilder,
|
|
18
|
+
OpenAIRegexBuilder,
|
|
19
|
+
OpenRouterRegexBuilder,
|
|
20
|
+
PerplexityRegexBuilder,
|
|
21
|
+
TogetherRegexBuilder,
|
|
22
|
+
XAIRegexBuilder,
|
|
23
|
+
)
|
|
12
24
|
from .resolver import NamingResolution, cli_name, resolve_cli_name
|
|
13
25
|
|
|
14
26
|
__all__ = [
|
|
@@ -19,7 +31,16 @@ __all__ = [
|
|
|
19
31
|
"RegexBuilder",
|
|
20
32
|
"UnimplementedRegexBuilder",
|
|
21
33
|
"AnthropicRegexBuilder",
|
|
34
|
+
"CerebrasRegexBuilder",
|
|
22
35
|
"DeepSeekRegexBuilder",
|
|
36
|
+
"FireworksRegexBuilder",
|
|
37
|
+
"GroqRegexBuilder",
|
|
38
|
+
"MoonshotRegexBuilder",
|
|
39
|
+
"OpenAIRegexBuilder",
|
|
40
|
+
"OpenRouterRegexBuilder",
|
|
41
|
+
"PerplexityRegexBuilder",
|
|
42
|
+
"TogetherRegexBuilder",
|
|
43
|
+
"XAIRegexBuilder",
|
|
23
44
|
"LLMCLIResponse",
|
|
24
45
|
"TokenUsage",
|
|
25
46
|
"build_prompt",
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
from .anthropic import AnthropicRegexBuilder
|
|
2
|
+
from .cerebras import CerebrasRegexBuilder
|
|
3
|
+
from .deepseek import DeepSeekRegexBuilder
|
|
4
|
+
from .fireworks import FireworksRegexBuilder
|
|
5
|
+
from .groq import GroqRegexBuilder
|
|
6
|
+
from .moonshot import MoonshotRegexBuilder
|
|
7
|
+
from .openai import OpenAIRegexBuilder
|
|
8
|
+
from .openrouter import OpenRouterRegexBuilder
|
|
9
|
+
from .perplexity import PerplexityRegexBuilder
|
|
10
|
+
from .together import TogetherRegexBuilder
|
|
11
|
+
from .xai import XAIRegexBuilder
|
|
12
|
+
|
|
13
|
+
__all__ = [
|
|
14
|
+
"AnthropicRegexBuilder",
|
|
15
|
+
"CerebrasRegexBuilder",
|
|
16
|
+
"DeepSeekRegexBuilder",
|
|
17
|
+
"FireworksRegexBuilder",
|
|
18
|
+
"GroqRegexBuilder",
|
|
19
|
+
"MoonshotRegexBuilder",
|
|
20
|
+
"OpenAIRegexBuilder",
|
|
21
|
+
"OpenRouterRegexBuilder",
|
|
22
|
+
"PerplexityRegexBuilder",
|
|
23
|
+
"TogetherRegexBuilder",
|
|
24
|
+
"XAIRegexBuilder",
|
|
25
|
+
]
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
"""Cerebras-backed RegexBuilder implementation.
|
|
2
|
+
|
|
3
|
+
Cerebras' API mirrors OpenAI's chat.completions surface (same request/
|
|
4
|
+
response format), so this uses the `openai` SDK pointed at Cerebras'
|
|
5
|
+
base URL rather than a dedicated Cerebras client.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import os
|
|
11
|
+
import time
|
|
12
|
+
from typing import TYPE_CHECKING, Any
|
|
13
|
+
|
|
14
|
+
from ..llm import CliContext, LLMCLIResponse, TokenUsage, build_prompt
|
|
15
|
+
from .cost import estimate_cost
|
|
16
|
+
from .errors import format_llm_error_reason, is_retryable
|
|
17
|
+
from .models import default_model
|
|
18
|
+
from .text import extract_pattern
|
|
19
|
+
|
|
20
|
+
if TYPE_CHECKING:
|
|
21
|
+
import openai as openai_sdk
|
|
22
|
+
|
|
23
|
+
DEFAULT_MODEL = default_model("cerebras")
|
|
24
|
+
|
|
25
|
+
_BASE_URL = "https://api.cerebras.ai/v1"
|
|
26
|
+
|
|
27
|
+
_DEFAULT_MAX_TOKENS = 1024
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _import_openai() -> Any:
|
|
31
|
+
"""Deferred import — openai is an optional extra (parseforge[cerebras]);
|
|
32
|
+
nothing else in this module (or a cache-hit lookup, which never reaches
|
|
33
|
+
build_pattern at all — see resolver.cli_name) should require it to be
|
|
34
|
+
installed."""
|
|
35
|
+
try:
|
|
36
|
+
import openai
|
|
37
|
+
except ImportError as exc:
|
|
38
|
+
raise ImportError(
|
|
39
|
+
"the openai package is required to use CerebrasRegexBuilder — "
|
|
40
|
+
"install it via `pip install parseforge[cerebras]`"
|
|
41
|
+
) from exc
|
|
42
|
+
return openai
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class CerebrasRegexBuilder:
|
|
46
|
+
"""Builds a cli-name regex pattern by prompting a Cerebras-hosted model.
|
|
47
|
+
|
|
48
|
+
The client is constructed lazily, on the first actual call — not in
|
|
49
|
+
``__init__`` — so this can be used as a default RegexBuilder without
|
|
50
|
+
requiring ``CEREBRAS_API_KEY`` to be set for cache-hit lookups, which
|
|
51
|
+
never reach the LLM at all (see resolver.cli_name).
|
|
52
|
+
|
|
53
|
+
API key resolves from the ``api_key`` argument if given, otherwise the
|
|
54
|
+
``CEREBRAS_API_KEY`` environment variable (unlike Anthropic/OpenAI's
|
|
55
|
+
own SDKs, the ``openai`` package has no built-in notion of Cerebras'
|
|
56
|
+
key, so this is resolved explicitly rather than left to the SDK).
|
|
57
|
+
"""
|
|
58
|
+
|
|
59
|
+
provider = "cerebras"
|
|
60
|
+
|
|
61
|
+
def __init__(self, model: str = DEFAULT_MODEL, api_key: str | None = None) -> None:
|
|
62
|
+
self.model = model
|
|
63
|
+
self._api_key = api_key
|
|
64
|
+
self._client: openai_sdk.OpenAI | None = None
|
|
65
|
+
|
|
66
|
+
def _get_client(self) -> openai_sdk.OpenAI:
|
|
67
|
+
if self._client is None:
|
|
68
|
+
openai = _import_openai()
|
|
69
|
+
api_key = self._api_key or os.environ.get("CEREBRAS_API_KEY")
|
|
70
|
+
if not api_key:
|
|
71
|
+
raise RuntimeError(
|
|
72
|
+
"no Cerebras API key — pass api_key or set CEREBRAS_API_KEY"
|
|
73
|
+
)
|
|
74
|
+
self._client = openai.OpenAI(api_key=api_key, base_url=_BASE_URL)
|
|
75
|
+
return self._client
|
|
76
|
+
|
|
77
|
+
def build_pattern(
|
|
78
|
+
self, command: str, context: CliContext, **kwargs: Any
|
|
79
|
+
) -> LLMCLIResponse:
|
|
80
|
+
openai = _import_openai()
|
|
81
|
+
prompt = build_prompt(command, context)
|
|
82
|
+
max_tokens = kwargs.pop("max_tokens", None) or _DEFAULT_MAX_TOKENS
|
|
83
|
+
|
|
84
|
+
start = time.monotonic()
|
|
85
|
+
try:
|
|
86
|
+
response = self._get_client().chat.completions.create(
|
|
87
|
+
model=self.model,
|
|
88
|
+
max_tokens=max_tokens,
|
|
89
|
+
messages=[{"role": "user", "content": prompt}],
|
|
90
|
+
**kwargs,
|
|
91
|
+
)
|
|
92
|
+
except openai.OpenAIError as exc:
|
|
93
|
+
if not is_retryable(exc):
|
|
94
|
+
# Same request would fail the same way again — stop rather
|
|
95
|
+
# than let a caller burn another attempt on it.
|
|
96
|
+
raise
|
|
97
|
+
return LLMCLIResponse(
|
|
98
|
+
content="",
|
|
99
|
+
raw=exc,
|
|
100
|
+
usage=TokenUsage(
|
|
101
|
+
input_tokens=0, output_tokens=0, total_tokens=0, estimated_cost=0.0
|
|
102
|
+
),
|
|
103
|
+
duration_ms=(time.monotonic() - start) * 1000,
|
|
104
|
+
reason=format_llm_error_reason(exc),
|
|
105
|
+
ready=False,
|
|
106
|
+
)
|
|
107
|
+
duration_ms = (time.monotonic() - start) * 1000
|
|
108
|
+
|
|
109
|
+
choice = response.choices[0]
|
|
110
|
+
return LLMCLIResponse(
|
|
111
|
+
content=extract_pattern(choice.message.content or ""),
|
|
112
|
+
raw=response,
|
|
113
|
+
usage=TokenUsage(
|
|
114
|
+
input_tokens=response.usage.prompt_tokens,
|
|
115
|
+
output_tokens=response.usage.completion_tokens,
|
|
116
|
+
total_tokens=response.usage.total_tokens,
|
|
117
|
+
estimated_cost=estimate_cost(
|
|
118
|
+
input_tokens=response.usage.prompt_tokens,
|
|
119
|
+
output_tokens=response.usage.completion_tokens,
|
|
120
|
+
total_tokens=response.usage.total_tokens,
|
|
121
|
+
provider=self.provider,
|
|
122
|
+
model=self.model,
|
|
123
|
+
),
|
|
124
|
+
),
|
|
125
|
+
duration_ms=duration_ms,
|
|
126
|
+
reason=choice.finish_reason or "",
|
|
127
|
+
ready=choice.finish_reason == "stop",
|
|
128
|
+
)
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
"""Fireworks AI-backed RegexBuilder implementation.
|
|
2
|
+
|
|
3
|
+
Fireworks' API mirrors OpenAI's chat.completions surface (same request/
|
|
4
|
+
response format), so this uses the `openai` SDK pointed at Fireworks'
|
|
5
|
+
base URL rather than a dedicated Fireworks client.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import os
|
|
11
|
+
import time
|
|
12
|
+
from typing import TYPE_CHECKING, Any
|
|
13
|
+
|
|
14
|
+
from ..llm import CliContext, LLMCLIResponse, TokenUsage, build_prompt
|
|
15
|
+
from .cost import estimate_cost
|
|
16
|
+
from .errors import format_llm_error_reason, is_retryable
|
|
17
|
+
from .models import default_model
|
|
18
|
+
from .text import extract_pattern
|
|
19
|
+
|
|
20
|
+
if TYPE_CHECKING:
|
|
21
|
+
import openai as openai_sdk
|
|
22
|
+
|
|
23
|
+
DEFAULT_MODEL = default_model("fireworks")
|
|
24
|
+
|
|
25
|
+
_BASE_URL = "https://api.fireworks.ai/inference/v1"
|
|
26
|
+
|
|
27
|
+
_DEFAULT_MAX_TOKENS = 1024
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _import_openai() -> Any:
|
|
31
|
+
"""Deferred import — openai is an optional extra (parseforge[fireworks]);
|
|
32
|
+
nothing else in this module (or a cache-hit lookup, which never reaches
|
|
33
|
+
build_pattern at all — see resolver.cli_name) should require it to be
|
|
34
|
+
installed."""
|
|
35
|
+
try:
|
|
36
|
+
import openai
|
|
37
|
+
except ImportError as exc:
|
|
38
|
+
raise ImportError(
|
|
39
|
+
"the openai package is required to use FireworksRegexBuilder — "
|
|
40
|
+
"install it via `pip install parseforge[fireworks]`"
|
|
41
|
+
) from exc
|
|
42
|
+
return openai
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class FireworksRegexBuilder:
|
|
46
|
+
"""Builds a cli-name regex pattern by prompting a Fireworks-hosted model.
|
|
47
|
+
|
|
48
|
+
The client is constructed lazily, on the first actual call — not in
|
|
49
|
+
``__init__`` — so this can be used as a default RegexBuilder without
|
|
50
|
+
requiring ``FIREWORKS_API_KEY`` to be set for cache-hit lookups, which
|
|
51
|
+
never reach the LLM at all (see resolver.cli_name).
|
|
52
|
+
|
|
53
|
+
API key resolves from the ``api_key`` argument if given, otherwise the
|
|
54
|
+
``FIREWORKS_API_KEY`` environment variable (unlike Anthropic/OpenAI's
|
|
55
|
+
own SDKs, the ``openai`` package has no built-in notion of Fireworks'
|
|
56
|
+
key, so this is resolved explicitly rather than left to the SDK).
|
|
57
|
+
"""
|
|
58
|
+
|
|
59
|
+
provider = "fireworks"
|
|
60
|
+
|
|
61
|
+
def __init__(self, model: str = DEFAULT_MODEL, api_key: str | None = None) -> None:
|
|
62
|
+
self.model = model
|
|
63
|
+
self._api_key = api_key
|
|
64
|
+
self._client: openai_sdk.OpenAI | None = None
|
|
65
|
+
|
|
66
|
+
def _get_client(self) -> openai_sdk.OpenAI:
|
|
67
|
+
if self._client is None:
|
|
68
|
+
openai = _import_openai()
|
|
69
|
+
api_key = self._api_key or os.environ.get("FIREWORKS_API_KEY")
|
|
70
|
+
if not api_key:
|
|
71
|
+
raise RuntimeError(
|
|
72
|
+
"no Fireworks API key — pass api_key or set FIREWORKS_API_KEY"
|
|
73
|
+
)
|
|
74
|
+
self._client = openai.OpenAI(api_key=api_key, base_url=_BASE_URL)
|
|
75
|
+
return self._client
|
|
76
|
+
|
|
77
|
+
def build_pattern(
|
|
78
|
+
self, command: str, context: CliContext, **kwargs: Any
|
|
79
|
+
) -> LLMCLIResponse:
|
|
80
|
+
openai = _import_openai()
|
|
81
|
+
prompt = build_prompt(command, context)
|
|
82
|
+
max_tokens = kwargs.pop("max_tokens", None) or _DEFAULT_MAX_TOKENS
|
|
83
|
+
|
|
84
|
+
start = time.monotonic()
|
|
85
|
+
try:
|
|
86
|
+
response = self._get_client().chat.completions.create(
|
|
87
|
+
model=self.model,
|
|
88
|
+
max_tokens=max_tokens,
|
|
89
|
+
messages=[{"role": "user", "content": prompt}],
|
|
90
|
+
**kwargs,
|
|
91
|
+
)
|
|
92
|
+
except openai.OpenAIError as exc:
|
|
93
|
+
if not is_retryable(exc):
|
|
94
|
+
# Same request would fail the same way again — stop rather
|
|
95
|
+
# than let a caller burn another attempt on it.
|
|
96
|
+
raise
|
|
97
|
+
return LLMCLIResponse(
|
|
98
|
+
content="",
|
|
99
|
+
raw=exc,
|
|
100
|
+
usage=TokenUsage(
|
|
101
|
+
input_tokens=0, output_tokens=0, total_tokens=0, estimated_cost=0.0
|
|
102
|
+
),
|
|
103
|
+
duration_ms=(time.monotonic() - start) * 1000,
|
|
104
|
+
reason=format_llm_error_reason(exc),
|
|
105
|
+
ready=False,
|
|
106
|
+
)
|
|
107
|
+
duration_ms = (time.monotonic() - start) * 1000
|
|
108
|
+
|
|
109
|
+
choice = response.choices[0]
|
|
110
|
+
return LLMCLIResponse(
|
|
111
|
+
content=extract_pattern(choice.message.content or ""),
|
|
112
|
+
raw=response,
|
|
113
|
+
usage=TokenUsage(
|
|
114
|
+
input_tokens=response.usage.prompt_tokens,
|
|
115
|
+
output_tokens=response.usage.completion_tokens,
|
|
116
|
+
total_tokens=response.usage.total_tokens,
|
|
117
|
+
estimated_cost=estimate_cost(
|
|
118
|
+
input_tokens=response.usage.prompt_tokens,
|
|
119
|
+
output_tokens=response.usage.completion_tokens,
|
|
120
|
+
total_tokens=response.usage.total_tokens,
|
|
121
|
+
provider=self.provider,
|
|
122
|
+
model=self.model,
|
|
123
|
+
),
|
|
124
|
+
),
|
|
125
|
+
duration_ms=duration_ms,
|
|
126
|
+
reason=choice.finish_reason or "",
|
|
127
|
+
ready=choice.finish_reason == "stop",
|
|
128
|
+
)
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
"""Groq-backed RegexBuilder implementation.
|
|
2
|
+
|
|
3
|
+
Groq's API mirrors OpenAI's chat.completions surface (same request/
|
|
4
|
+
response format), so this uses the `openai` SDK pointed at Groq's base
|
|
5
|
+
URL rather than a dedicated Groq client.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import os
|
|
11
|
+
import time
|
|
12
|
+
from typing import TYPE_CHECKING, Any
|
|
13
|
+
|
|
14
|
+
from ..llm import CliContext, LLMCLIResponse, TokenUsage, build_prompt
|
|
15
|
+
from .cost import estimate_cost
|
|
16
|
+
from .errors import format_llm_error_reason, is_retryable
|
|
17
|
+
from .models import default_model
|
|
18
|
+
from .text import extract_pattern
|
|
19
|
+
|
|
20
|
+
if TYPE_CHECKING:
|
|
21
|
+
import openai as openai_sdk
|
|
22
|
+
|
|
23
|
+
DEFAULT_MODEL = default_model("groq")
|
|
24
|
+
|
|
25
|
+
_BASE_URL = "https://api.groq.com/openai/v1"
|
|
26
|
+
|
|
27
|
+
_DEFAULT_MAX_TOKENS = 1024
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _import_openai() -> Any:
|
|
31
|
+
"""Deferred import — openai is an optional extra (parseforge[groq]);
|
|
32
|
+
nothing else in this module (or a cache-hit lookup, which never reaches
|
|
33
|
+
build_pattern at all — see resolver.cli_name) should require it to be
|
|
34
|
+
installed."""
|
|
35
|
+
try:
|
|
36
|
+
import openai
|
|
37
|
+
except ImportError as exc:
|
|
38
|
+
raise ImportError(
|
|
39
|
+
"the openai package is required to use GroqRegexBuilder — "
|
|
40
|
+
"install it via `pip install parseforge[groq]`"
|
|
41
|
+
) from exc
|
|
42
|
+
return openai
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class GroqRegexBuilder:
|
|
46
|
+
"""Builds a cli-name regex pattern by prompting a Groq-hosted model.
|
|
47
|
+
|
|
48
|
+
The client is constructed lazily, on the first actual call — not in
|
|
49
|
+
``__init__`` — so this can be used as a default RegexBuilder without
|
|
50
|
+
requiring ``GROQ_API_KEY`` to be set for cache-hit lookups, which
|
|
51
|
+
never reach the LLM at all (see resolver.cli_name).
|
|
52
|
+
|
|
53
|
+
API key resolves from the ``api_key`` argument if given, otherwise the
|
|
54
|
+
``GROQ_API_KEY`` environment variable (unlike Anthropic/OpenAI's own
|
|
55
|
+
SDKs, the ``openai`` package has no built-in notion of Groq's key, so
|
|
56
|
+
this is resolved explicitly rather than left to the SDK).
|
|
57
|
+
"""
|
|
58
|
+
|
|
59
|
+
provider = "groq"
|
|
60
|
+
|
|
61
|
+
def __init__(self, model: str = DEFAULT_MODEL, api_key: str | None = None) -> None:
|
|
62
|
+
self.model = model
|
|
63
|
+
self._api_key = api_key
|
|
64
|
+
self._client: openai_sdk.OpenAI | None = None
|
|
65
|
+
|
|
66
|
+
def _get_client(self) -> openai_sdk.OpenAI:
|
|
67
|
+
if self._client is None:
|
|
68
|
+
openai = _import_openai()
|
|
69
|
+
api_key = self._api_key or os.environ.get("GROQ_API_KEY")
|
|
70
|
+
if not api_key:
|
|
71
|
+
raise RuntimeError("no Groq API key — pass api_key or set GROQ_API_KEY")
|
|
72
|
+
self._client = openai.OpenAI(api_key=api_key, base_url=_BASE_URL)
|
|
73
|
+
return self._client
|
|
74
|
+
|
|
75
|
+
def build_pattern(
|
|
76
|
+
self, command: str, context: CliContext, **kwargs: Any
|
|
77
|
+
) -> LLMCLIResponse:
|
|
78
|
+
openai = _import_openai()
|
|
79
|
+
prompt = build_prompt(command, context)
|
|
80
|
+
max_tokens = kwargs.pop("max_tokens", None) or _DEFAULT_MAX_TOKENS
|
|
81
|
+
|
|
82
|
+
start = time.monotonic()
|
|
83
|
+
try:
|
|
84
|
+
response = self._get_client().chat.completions.create(
|
|
85
|
+
model=self.model,
|
|
86
|
+
max_tokens=max_tokens,
|
|
87
|
+
messages=[{"role": "user", "content": prompt}],
|
|
88
|
+
**kwargs,
|
|
89
|
+
)
|
|
90
|
+
except openai.OpenAIError as exc:
|
|
91
|
+
if not is_retryable(exc):
|
|
92
|
+
# Same request would fail the same way again — stop rather
|
|
93
|
+
# than let a caller burn another attempt on it.
|
|
94
|
+
raise
|
|
95
|
+
return LLMCLIResponse(
|
|
96
|
+
content="",
|
|
97
|
+
raw=exc,
|
|
98
|
+
usage=TokenUsage(
|
|
99
|
+
input_tokens=0, output_tokens=0, total_tokens=0, estimated_cost=0.0
|
|
100
|
+
),
|
|
101
|
+
duration_ms=(time.monotonic() - start) * 1000,
|
|
102
|
+
reason=format_llm_error_reason(exc),
|
|
103
|
+
ready=False,
|
|
104
|
+
)
|
|
105
|
+
duration_ms = (time.monotonic() - start) * 1000
|
|
106
|
+
|
|
107
|
+
choice = response.choices[0]
|
|
108
|
+
return LLMCLIResponse(
|
|
109
|
+
content=extract_pattern(choice.message.content or ""),
|
|
110
|
+
raw=response,
|
|
111
|
+
usage=TokenUsage(
|
|
112
|
+
input_tokens=response.usage.prompt_tokens,
|
|
113
|
+
output_tokens=response.usage.completion_tokens,
|
|
114
|
+
total_tokens=response.usage.total_tokens,
|
|
115
|
+
estimated_cost=estimate_cost(
|
|
116
|
+
input_tokens=response.usage.prompt_tokens,
|
|
117
|
+
output_tokens=response.usage.completion_tokens,
|
|
118
|
+
total_tokens=response.usage.total_tokens,
|
|
119
|
+
provider=self.provider,
|
|
120
|
+
model=self.model,
|
|
121
|
+
),
|
|
122
|
+
),
|
|
123
|
+
duration_ms=duration_ms,
|
|
124
|
+
reason=choice.finish_reason or "",
|
|
125
|
+
ready=choice.finish_reason == "stop",
|
|
126
|
+
)
|