parseforge 0.2.9__tar.gz → 0.2.10__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.9/parseforge.egg-info → parseforge-0.2.10}/PKG-INFO +17 -13
- {parseforge-0.2.9 → parseforge-0.2.10}/README.md +12 -12
- parseforge-0.2.10/parseforge/__init__.py +1 -0
- {parseforge-0.2.9 → parseforge-0.2.10}/parseforge/cli/main.py +4 -2
- {parseforge-0.2.9 → parseforge-0.2.10}/parseforge/naming/__init__.py +2 -0
- {parseforge-0.2.9 → parseforge-0.2.10}/parseforge/naming/providers/__init__.py +2 -0
- parseforge-0.2.10/parseforge/naming/providers/gemini.py +160 -0
- {parseforge-0.2.9 → parseforge-0.2.10}/parseforge/naming/providers/models.yaml +12 -0
- {parseforge-0.2.9 → parseforge-0.2.10/parseforge.egg-info}/PKG-INFO +17 -13
- {parseforge-0.2.9 → parseforge-0.2.10}/parseforge.egg-info/SOURCES.txt +1 -0
- {parseforge-0.2.9 → parseforge-0.2.10}/parseforge.egg-info/requires.txt +5 -0
- {parseforge-0.2.9 → parseforge-0.2.10}/pyproject.toml +7 -5
- parseforge-0.2.9/parseforge/__init__.py +0 -1
- {parseforge-0.2.9 → parseforge-0.2.10}/LICENSE +0 -0
- {parseforge-0.2.9 → parseforge-0.2.10}/MANIFEST.in +0 -0
- {parseforge-0.2.9 → parseforge-0.2.10}/SPEC.md +0 -0
- {parseforge-0.2.9 → parseforge-0.2.10}/parseforge/cli/__init__.py +0 -0
- {parseforge-0.2.9 → parseforge-0.2.10}/parseforge/cli/config.py +0 -0
- {parseforge-0.2.9 → parseforge-0.2.10}/parseforge/drift.py +0 -0
- {parseforge-0.2.9 → parseforge-0.2.10}/parseforge/generation.py +0 -0
- {parseforge-0.2.9 → parseforge-0.2.10}/parseforge/integration.py +0 -0
- {parseforge-0.2.9 → parseforge-0.2.10}/parseforge/naming/assemble.py +0 -0
- {parseforge-0.2.9 → parseforge-0.2.10}/parseforge/naming/cache.py +0 -0
- {parseforge-0.2.9 → parseforge-0.2.10}/parseforge/naming/llm.py +0 -0
- {parseforge-0.2.9 → parseforge-0.2.10}/parseforge/naming/prompts.py +0 -0
- {parseforge-0.2.9 → parseforge-0.2.10}/parseforge/naming/prompts.yaml +0 -0
- {parseforge-0.2.9 → parseforge-0.2.10}/parseforge/naming/providers/anthropic.py +0 -0
- {parseforge-0.2.9 → parseforge-0.2.10}/parseforge/naming/providers/azure.py +0 -0
- {parseforge-0.2.9 → parseforge-0.2.10}/parseforge/naming/providers/cerebras.py +0 -0
- {parseforge-0.2.9 → parseforge-0.2.10}/parseforge/naming/providers/cohere.py +0 -0
- {parseforge-0.2.9 → parseforge-0.2.10}/parseforge/naming/providers/cost.py +0 -0
- {parseforge-0.2.9 → parseforge-0.2.10}/parseforge/naming/providers/deepseek.py +0 -0
- {parseforge-0.2.9 → parseforge-0.2.10}/parseforge/naming/providers/errors.py +0 -0
- {parseforge-0.2.9 → parseforge-0.2.10}/parseforge/naming/providers/fireworks.py +0 -0
- {parseforge-0.2.9 → parseforge-0.2.10}/parseforge/naming/providers/groq.py +0 -0
- {parseforge-0.2.9 → parseforge-0.2.10}/parseforge/naming/providers/mistral.py +0 -0
- {parseforge-0.2.9 → parseforge-0.2.10}/parseforge/naming/providers/models.py +0 -0
- {parseforge-0.2.9 → parseforge-0.2.10}/parseforge/naming/providers/moonshot.py +0 -0
- {parseforge-0.2.9 → parseforge-0.2.10}/parseforge/naming/providers/openai.py +0 -0
- {parseforge-0.2.9 → parseforge-0.2.10}/parseforge/naming/providers/openrouter.py +0 -0
- {parseforge-0.2.9 → parseforge-0.2.10}/parseforge/naming/providers/perplexity.py +0 -0
- {parseforge-0.2.9 → parseforge-0.2.10}/parseforge/naming/providers/text.py +0 -0
- {parseforge-0.2.9 → parseforge-0.2.10}/parseforge/naming/providers/together.py +0 -0
- {parseforge-0.2.9 → parseforge-0.2.10}/parseforge/naming/providers/xai.py +0 -0
- {parseforge-0.2.9 → parseforge-0.2.10}/parseforge/naming/resolver.py +0 -0
- {parseforge-0.2.9 → parseforge-0.2.10}/parseforge/paths.py +0 -0
- {parseforge-0.2.9 → parseforge-0.2.10}/parseforge/pipeline.py +0 -0
- {parseforge-0.2.9 → parseforge-0.2.10}/parseforge/promotion.py +0 -0
- {parseforge-0.2.9 → parseforge-0.2.10}/parseforge/sampling/__init__.py +0 -0
- {parseforge-0.2.9 → parseforge-0.2.10}/parseforge/sampling/backends/__init__.py +0 -0
- {parseforge-0.2.9 → parseforge-0.2.10}/parseforge/sampling/backends/netmiko.py +0 -0
- {parseforge-0.2.9 → parseforge-0.2.10}/parseforge/sampling/core.py +0 -0
- {parseforge-0.2.9 → parseforge-0.2.10}/parseforge/validation.py +0 -0
- {parseforge-0.2.9 → parseforge-0.2.10}/parseforge.egg-info/dependency_links.txt +0 -0
- {parseforge-0.2.9 → parseforge-0.2.10}/parseforge.egg-info/entry_points.txt +0 -0
- {parseforge-0.2.9 → parseforge-0.2.10}/parseforge.egg-info/top_level.txt +0 -0
- {parseforge-0.2.9 → parseforge-0.2.10}/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.10
|
|
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>
|
|
@@ -69,6 +69,9 @@ Requires-Dist: textfsm-ai[cohere]>=0.6.1; extra == "cohere"
|
|
|
69
69
|
Provides-Extra: azure
|
|
70
70
|
Requires-Dist: azure-ai-inference>=1.0.0b9; extra == "azure"
|
|
71
71
|
Requires-Dist: textfsm-ai[azure]>=0.6.1; extra == "azure"
|
|
72
|
+
Provides-Extra: gemini
|
|
73
|
+
Requires-Dist: google-genai>=0.2.0; extra == "gemini"
|
|
74
|
+
Requires-Dist: textfsm-ai[gemini]>=0.6.1; extra == "gemini"
|
|
72
75
|
Provides-Extra: sampling
|
|
73
76
|
Requires-Dist: netmiko>=4.0; extra == "sampling"
|
|
74
77
|
Provides-Extra: dev
|
|
@@ -82,6 +85,7 @@ Requires-Dist: openai>=1.0.0; extra == "dev"
|
|
|
82
85
|
Requires-Dist: mistralai==1.10.0; extra == "dev"
|
|
83
86
|
Requires-Dist: cohere==5.21.1; extra == "dev"
|
|
84
87
|
Requires-Dist: azure-ai-inference>=1.0.0b9; extra == "dev"
|
|
88
|
+
Requires-Dist: google-genai>=0.2.0; extra == "dev"
|
|
85
89
|
Provides-Extra: release
|
|
86
90
|
Requires-Dist: bump2version; extra == "release"
|
|
87
91
|
Requires-Dist: build; extra == "release"
|
|
@@ -124,11 +128,11 @@ command that's pure local processing (`canonical`/`readable`/`recognizers`,
|
|
|
124
128
|
LLM (`name`, `check --provider`, `run`, `generate-template`, `trial`) needs the
|
|
125
129
|
extra for whichever provider it uses: `anthropic`, `openai`, `deepseek`,
|
|
126
130
|
`groq`, `xai`, `together`, `fireworks`, `perplexity`, `openrouter`,
|
|
127
|
-
`moonshot`, `cerebras`, `mistral`, `cohere`, or `
|
|
128
|
-
to `anthropic` wherever it isn't required, so that's
|
|
129
|
-
need. `pip install parseforge[sampling]` adds Netmiko
|
|
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]`.
|
|
131
|
+
`moonshot`, `cerebras`, `mistral`, `cohere`, `azure`, or `gemini`.
|
|
132
|
+
`--provider` defaults to `anthropic` wherever it isn't required, so that's
|
|
133
|
+
the one most setups need. `pip install parseforge[sampling]` adds Netmiko
|
|
134
|
+
for live device sampling; combine extras as needed, e.g.
|
|
135
|
+
`pip install parseforge[anthropic,openai,deepseek,groq,xai,together,fireworks,perplexity,openrouter,moonshot,cerebras,mistral,cohere,azure,gemini,sampling]`.
|
|
132
136
|
|
|
133
137
|
## Development
|
|
134
138
|
|
|
@@ -136,13 +140,13 @@ sampling; combine extras as needed, e.g.
|
|
|
136
140
|
pip install -e ".[dev,sampling]"
|
|
137
141
|
pytest
|
|
138
142
|
```
|
|
139
|
-
`dev` already includes the `anthropic`, `openai`, `mistralai`, `cohere`,
|
|
140
|
-
`azure-ai-inference` SDKs (tests exercise all
|
|
141
|
-
`anthropic`, `openai`, `deepseek`, `groq`, `xai`, `together`,
|
|
142
|
-
`perplexity`, `openrouter`, `moonshot`, `cerebras` share just
|
|
143
|
-
packages, and `mistral`/`cohere`/`azure` each need
|
|
144
|
-
silently skip) — add the specific `,<provider>`
|
|
145
|
-
installing outside of `dev`.
|
|
143
|
+
`dev` already includes the `anthropic`, `openai`, `mistralai`, `cohere`,
|
|
144
|
+
`azure-ai-inference`, and `google-genai` SDKs (tests exercise all fifteen
|
|
145
|
+
providers — `anthropic`, `openai`, `deepseek`, `groq`, `xai`, `together`,
|
|
146
|
+
`fireworks`, `perplexity`, `openrouter`, `moonshot`, `cerebras` share just
|
|
147
|
+
the first two packages, and `mistral`/`cohere`/`azure`/`gemini` each need
|
|
148
|
+
their own SDK — and never silently skip) — add the specific `,<provider>`
|
|
149
|
+
extra explicitly only if installing outside of `dev`.
|
|
146
150
|
|
|
147
151
|
Linting/formatting/type-checking/docs run through tox instead of extras — see
|
|
148
152
|
`tox.ini` (`tox -e lint`/`format`/`typecheck`/`docs`), each installing its own
|
|
@@ -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`, `cohere`, or `
|
|
39
|
-
to `anthropic` wherever it isn't required, so that's
|
|
40
|
-
need. `pip install parseforge[sampling]` adds Netmiko
|
|
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]`.
|
|
38
|
+
`moonshot`, `cerebras`, `mistral`, `cohere`, `azure`, or `gemini`.
|
|
39
|
+
`--provider` defaults to `anthropic` wherever it isn't required, so that's
|
|
40
|
+
the one most setups need. `pip install parseforge[sampling]` adds Netmiko
|
|
41
|
+
for live device 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,gemini,sampling]`.
|
|
43
43
|
|
|
44
44
|
## Development
|
|
45
45
|
|
|
@@ -47,13 +47,13 @@ sampling; 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`, `cohere`,
|
|
51
|
-
`azure-ai-inference` SDKs (tests exercise all
|
|
52
|
-
`anthropic`, `openai`, `deepseek`, `groq`, `xai`, `together`,
|
|
53
|
-
`perplexity`, `openrouter`, `moonshot`, `cerebras` share just
|
|
54
|
-
packages, and `mistral`/`cohere`/`azure` each need
|
|
55
|
-
silently skip) — add the specific `,<provider>`
|
|
56
|
-
installing outside of `dev`.
|
|
50
|
+
`dev` already includes the `anthropic`, `openai`, `mistralai`, `cohere`,
|
|
51
|
+
`azure-ai-inference`, and `google-genai` SDKs (tests exercise all fifteen
|
|
52
|
+
providers — `anthropic`, `openai`, `deepseek`, `groq`, `xai`, `together`,
|
|
53
|
+
`fireworks`, `perplexity`, `openrouter`, `moonshot`, `cerebras` share just
|
|
54
|
+
the first two packages, and `mistral`/`cohere`/`azure`/`gemini` each need
|
|
55
|
+
their own SDK — and never silently skip) — add the specific `,<provider>`
|
|
56
|
+
extra explicitly only if 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
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.2.10"
|
|
@@ -29,6 +29,7 @@ _BUILDERS: dict[str, type[naming.RegexBuilder]] = {
|
|
|
29
29
|
"cohere": naming.CohereRegexBuilder,
|
|
30
30
|
"deepseek": naming.DeepSeekRegexBuilder,
|
|
31
31
|
"fireworks": naming.FireworksRegexBuilder,
|
|
32
|
+
"gemini": naming.GeminiRegexBuilder,
|
|
32
33
|
"groq": naming.GroqRegexBuilder,
|
|
33
34
|
"mistral": naming.MistralRegexBuilder,
|
|
34
35
|
"moonshot": naming.MoonshotRegexBuilder,
|
|
@@ -246,7 +247,8 @@ def main() -> None:
|
|
|
246
247
|
"variable (ANTHROPIC_API_KEY, OPENAI_API_KEY, DEEPSEEK_API_KEY, GROQ_API_KEY, "
|
|
247
248
|
"XAI_API_KEY, TOGETHER_API_KEY, FIREWORKS_API_KEY, PERPLEXITY_API_KEY, "
|
|
248
249
|
"OPENROUTER_API_KEY, MOONSHOT_API_KEY, CEREBRAS_API_KEY, MISTRAL_API_KEY, "
|
|
249
|
-
"COHERE_API_KEY, AZURE_API_KEY); only needed on a cache
|
|
250
|
+
"COHERE_API_KEY, AZURE_API_KEY, GEMINI_API_KEY); only needed on a cache "
|
|
251
|
+
"miss.",
|
|
250
252
|
)
|
|
251
253
|
@click.option(
|
|
252
254
|
"--model",
|
|
@@ -359,7 +361,7 @@ def name_cmd(
|
|
|
359
361
|
help="LLM provider for template generation (textfsm-ai's own registry, "
|
|
360
362
|
'e.g. "anthropic", "openai", "deepseek", "groq", "xai", "together", '
|
|
361
363
|
'"fireworks", "perplexity", "openrouter", "moonshot", "cerebras", '
|
|
362
|
-
'"mistral", "cohere", "azure"). Naming uses its own separate '
|
|
364
|
+
'"mistral", "cohere", "azure", "gemini"). Naming uses its own separate '
|
|
363
365
|
"--naming-provider, not this one.",
|
|
364
366
|
)
|
|
365
367
|
@click.option("--api-key", required=True, help="API key for the generation LLM call.")
|
|
@@ -15,6 +15,7 @@ from .providers import (
|
|
|
15
15
|
CohereRegexBuilder,
|
|
16
16
|
DeepSeekRegexBuilder,
|
|
17
17
|
FireworksRegexBuilder,
|
|
18
|
+
GeminiRegexBuilder,
|
|
18
19
|
GroqRegexBuilder,
|
|
19
20
|
MistralRegexBuilder,
|
|
20
21
|
MoonshotRegexBuilder,
|
|
@@ -39,6 +40,7 @@ __all__ = [
|
|
|
39
40
|
"CohereRegexBuilder",
|
|
40
41
|
"DeepSeekRegexBuilder",
|
|
41
42
|
"FireworksRegexBuilder",
|
|
43
|
+
"GeminiRegexBuilder",
|
|
42
44
|
"GroqRegexBuilder",
|
|
43
45
|
"MistralRegexBuilder",
|
|
44
46
|
"MoonshotRegexBuilder",
|
|
@@ -4,6 +4,7 @@ from .cerebras import CerebrasRegexBuilder
|
|
|
4
4
|
from .cohere import CohereRegexBuilder
|
|
5
5
|
from .deepseek import DeepSeekRegexBuilder
|
|
6
6
|
from .fireworks import FireworksRegexBuilder
|
|
7
|
+
from .gemini import GeminiRegexBuilder
|
|
7
8
|
from .groq import GroqRegexBuilder
|
|
8
9
|
from .mistral import MistralRegexBuilder
|
|
9
10
|
from .moonshot import MoonshotRegexBuilder
|
|
@@ -20,6 +21,7 @@ __all__ = [
|
|
|
20
21
|
"CohereRegexBuilder",
|
|
21
22
|
"DeepSeekRegexBuilder",
|
|
22
23
|
"FireworksRegexBuilder",
|
|
24
|
+
"GeminiRegexBuilder",
|
|
23
25
|
"GroqRegexBuilder",
|
|
24
26
|
"MistralRegexBuilder",
|
|
25
27
|
"MoonshotRegexBuilder",
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
"""Google Gemini-backed RegexBuilder implementation.
|
|
2
|
+
|
|
3
|
+
Like Mistral/Cohere/Azure, Gemini's official Python SDK (``google-genai``)
|
|
4
|
+
is not built on the OpenAI client library — it has its own client shape
|
|
5
|
+
(``client.models.generate_content()``). Talks to the SDK directly.
|
|
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 .models import default_model
|
|
17
|
+
from .text import extract_pattern
|
|
18
|
+
|
|
19
|
+
if TYPE_CHECKING:
|
|
20
|
+
import google.genai as genai_sdk
|
|
21
|
+
|
|
22
|
+
DEFAULT_MODEL = default_model("gemini")
|
|
23
|
+
|
|
24
|
+
_DEFAULT_MAX_TOKENS = 1024
|
|
25
|
+
|
|
26
|
+
# HTTP statuses where retrying the exact same request can't succeed —
|
|
27
|
+
# mirrors providers/errors.py's NON_RETRYABLE class-name set, but the
|
|
28
|
+
# google-genai SDK raises a single APIError for every HTTP failure
|
|
29
|
+
# (status on exc.code) rather than a family of named exception classes,
|
|
30
|
+
# so classification here is done by status code — same approach as
|
|
31
|
+
# Mistral/Cohere/Azure.
|
|
32
|
+
_NON_RETRYABLE_STATUSES = frozenset({400, 401, 403, 404, 409, 413, 422})
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _import_genai() -> Any:
|
|
36
|
+
"""Deferred import — google-genai is an optional extra
|
|
37
|
+
(parseforge[gemini]); nothing else in this module (or a cache-hit
|
|
38
|
+
lookup, which never reaches build_pattern at all — see
|
|
39
|
+
resolver.cli_name) should require it to be installed."""
|
|
40
|
+
try:
|
|
41
|
+
import google.genai as genai
|
|
42
|
+
except ImportError as exc:
|
|
43
|
+
raise ImportError(
|
|
44
|
+
"the google-genai package is required to use GeminiRegexBuilder — "
|
|
45
|
+
"install it via `pip install parseforge[gemini]`"
|
|
46
|
+
) from exc
|
|
47
|
+
return genai
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _is_retryable(exc: Any) -> bool:
|
|
51
|
+
return getattr(exc, "code", None) not in _NON_RETRYABLE_STATUSES
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _format_error_reason(exc: Any) -> str:
|
|
55
|
+
return f"LLM-ERROR-gemini_sdk-{getattr(exc, 'code', None)}-{exc}"
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class GeminiRegexBuilder:
|
|
59
|
+
"""Builds a cli-name regex pattern by prompting a Gemini model.
|
|
60
|
+
|
|
61
|
+
The client is constructed lazily, on the first actual call — not in
|
|
62
|
+
``__init__`` — so this can be used as a default RegexBuilder without
|
|
63
|
+
requiring ``GEMINI_API_KEY`` to be set for cache-hit lookups, which
|
|
64
|
+
never reach the LLM at all (see resolver.cli_name).
|
|
65
|
+
|
|
66
|
+
API key resolves from the ``api_key`` argument if given, otherwise the
|
|
67
|
+
``GEMINI_API_KEY`` environment variable (the SDK's own default).
|
|
68
|
+
"""
|
|
69
|
+
|
|
70
|
+
provider = "gemini"
|
|
71
|
+
|
|
72
|
+
def __init__(self, model: str = DEFAULT_MODEL, api_key: str | None = None) -> None:
|
|
73
|
+
self.model = model
|
|
74
|
+
self._api_key = api_key
|
|
75
|
+
self._client: genai_sdk.Client | None = None
|
|
76
|
+
|
|
77
|
+
def _get_client(self) -> genai_sdk.Client:
|
|
78
|
+
if self._client is None:
|
|
79
|
+
genai = _import_genai()
|
|
80
|
+
api_key = self._api_key or os.environ.get("GEMINI_API_KEY")
|
|
81
|
+
if not api_key:
|
|
82
|
+
raise RuntimeError(
|
|
83
|
+
"no Gemini API key — pass api_key or set GEMINI_API_KEY"
|
|
84
|
+
)
|
|
85
|
+
self._client = genai.Client(api_key=api_key)
|
|
86
|
+
return self._client
|
|
87
|
+
|
|
88
|
+
def build_pattern(
|
|
89
|
+
self, command: str, context: CliContext, **kwargs: Any
|
|
90
|
+
) -> LLMCLIResponse:
|
|
91
|
+
genai = _import_genai()
|
|
92
|
+
prompt = build_prompt(command, context)
|
|
93
|
+
max_tokens = kwargs.pop("max_tokens", None) or _DEFAULT_MAX_TOKENS
|
|
94
|
+
temperature = kwargs.pop("temperature", None)
|
|
95
|
+
# Disabled by default (budget 0) — thinking mode adds latency/
|
|
96
|
+
# cost this naming call doesn't need, matching textfsm-ai's own
|
|
97
|
+
# GeminiProvider default; override via a thinking_budget kwarg.
|
|
98
|
+
thinking_budget = kwargs.pop("thinking_budget", 0)
|
|
99
|
+
|
|
100
|
+
start = time.monotonic()
|
|
101
|
+
try:
|
|
102
|
+
response = self._get_client().models.generate_content(
|
|
103
|
+
model=self.model,
|
|
104
|
+
contents=prompt,
|
|
105
|
+
config=genai.types.GenerateContentConfig(
|
|
106
|
+
max_output_tokens=max_tokens,
|
|
107
|
+
temperature=temperature,
|
|
108
|
+
thinking_config=genai.types.ThinkingConfig(
|
|
109
|
+
thinking_budget=thinking_budget
|
|
110
|
+
),
|
|
111
|
+
**kwargs,
|
|
112
|
+
),
|
|
113
|
+
)
|
|
114
|
+
except genai.errors.APIError as exc:
|
|
115
|
+
if not _is_retryable(exc):
|
|
116
|
+
# Same request would fail the same way again — stop rather
|
|
117
|
+
# than let a caller burn another attempt on it.
|
|
118
|
+
raise
|
|
119
|
+
return LLMCLIResponse(
|
|
120
|
+
content="",
|
|
121
|
+
raw=exc,
|
|
122
|
+
usage=TokenUsage(
|
|
123
|
+
input_tokens=0, output_tokens=0, total_tokens=0, estimated_cost=0.0
|
|
124
|
+
),
|
|
125
|
+
duration_ms=(time.monotonic() - start) * 1000,
|
|
126
|
+
reason=_format_error_reason(exc),
|
|
127
|
+
ready=False,
|
|
128
|
+
)
|
|
129
|
+
duration_ms = (time.monotonic() - start) * 1000
|
|
130
|
+
|
|
131
|
+
candidate = response.candidates[0] if response.candidates else None
|
|
132
|
+
finish_reason = candidate.finish_reason if candidate else None
|
|
133
|
+
usage = response.usage_metadata
|
|
134
|
+
input_tokens = (usage.prompt_token_count or 0) if usage else 0
|
|
135
|
+
output_tokens = (usage.candidates_token_count or 0) if usage else 0
|
|
136
|
+
total_tokens = (usage.total_token_count or 0) if usage else 0
|
|
137
|
+
|
|
138
|
+
return LLMCLIResponse(
|
|
139
|
+
content=extract_pattern(response.text or ""),
|
|
140
|
+
raw=response,
|
|
141
|
+
usage=TokenUsage(
|
|
142
|
+
input_tokens=input_tokens,
|
|
143
|
+
output_tokens=output_tokens,
|
|
144
|
+
total_tokens=total_tokens,
|
|
145
|
+
estimated_cost=estimate_cost(
|
|
146
|
+
input_tokens=input_tokens,
|
|
147
|
+
output_tokens=output_tokens,
|
|
148
|
+
total_tokens=total_tokens,
|
|
149
|
+
provider=self.provider,
|
|
150
|
+
model=self.model,
|
|
151
|
+
),
|
|
152
|
+
),
|
|
153
|
+
duration_ms=duration_ms,
|
|
154
|
+
# finish_reason is a str-subclassed enum whose own __str__
|
|
155
|
+
# prints "FinishReason.STOP" rather than "STOP" — .value
|
|
156
|
+
# keeps this consistent with every other provider's plain
|
|
157
|
+
# lowercase/uppercase reason strings.
|
|
158
|
+
reason=finish_reason.value if finish_reason else "",
|
|
159
|
+
ready=finish_reason == genai.types.FinishReason.STOP,
|
|
160
|
+
)
|
|
@@ -129,3 +129,15 @@ cohere:
|
|
|
129
129
|
- command-r-plus
|
|
130
130
|
- command-a
|
|
131
131
|
deprecated: []
|
|
132
|
+
|
|
133
|
+
gemini:
|
|
134
|
+
default: gemini-2.5-flash
|
|
135
|
+
supported:
|
|
136
|
+
- gemini-2.5-flash
|
|
137
|
+
- gemini-2.5-flash-lite
|
|
138
|
+
- gemini-2.5-pro
|
|
139
|
+
- gemini-3-flash
|
|
140
|
+
- gemini-3.1-flash-lite
|
|
141
|
+
- gemini-3.1-pro
|
|
142
|
+
- gemini-3.5-flash
|
|
143
|
+
deprecated: []
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: parseforge
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.10
|
|
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>
|
|
@@ -69,6 +69,9 @@ Requires-Dist: textfsm-ai[cohere]>=0.6.1; extra == "cohere"
|
|
|
69
69
|
Provides-Extra: azure
|
|
70
70
|
Requires-Dist: azure-ai-inference>=1.0.0b9; extra == "azure"
|
|
71
71
|
Requires-Dist: textfsm-ai[azure]>=0.6.1; extra == "azure"
|
|
72
|
+
Provides-Extra: gemini
|
|
73
|
+
Requires-Dist: google-genai>=0.2.0; extra == "gemini"
|
|
74
|
+
Requires-Dist: textfsm-ai[gemini]>=0.6.1; extra == "gemini"
|
|
72
75
|
Provides-Extra: sampling
|
|
73
76
|
Requires-Dist: netmiko>=4.0; extra == "sampling"
|
|
74
77
|
Provides-Extra: dev
|
|
@@ -82,6 +85,7 @@ Requires-Dist: openai>=1.0.0; extra == "dev"
|
|
|
82
85
|
Requires-Dist: mistralai==1.10.0; extra == "dev"
|
|
83
86
|
Requires-Dist: cohere==5.21.1; extra == "dev"
|
|
84
87
|
Requires-Dist: azure-ai-inference>=1.0.0b9; extra == "dev"
|
|
88
|
+
Requires-Dist: google-genai>=0.2.0; extra == "dev"
|
|
85
89
|
Provides-Extra: release
|
|
86
90
|
Requires-Dist: bump2version; extra == "release"
|
|
87
91
|
Requires-Dist: build; extra == "release"
|
|
@@ -124,11 +128,11 @@ command that's pure local processing (`canonical`/`readable`/`recognizers`,
|
|
|
124
128
|
LLM (`name`, `check --provider`, `run`, `generate-template`, `trial`) needs the
|
|
125
129
|
extra for whichever provider it uses: `anthropic`, `openai`, `deepseek`,
|
|
126
130
|
`groq`, `xai`, `together`, `fireworks`, `perplexity`, `openrouter`,
|
|
127
|
-
`moonshot`, `cerebras`, `mistral`, `cohere`, or `
|
|
128
|
-
to `anthropic` wherever it isn't required, so that's
|
|
129
|
-
need. `pip install parseforge[sampling]` adds Netmiko
|
|
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]`.
|
|
131
|
+
`moonshot`, `cerebras`, `mistral`, `cohere`, `azure`, or `gemini`.
|
|
132
|
+
`--provider` defaults to `anthropic` wherever it isn't required, so that's
|
|
133
|
+
the one most setups need. `pip install parseforge[sampling]` adds Netmiko
|
|
134
|
+
for live device sampling; combine extras as needed, e.g.
|
|
135
|
+
`pip install parseforge[anthropic,openai,deepseek,groq,xai,together,fireworks,perplexity,openrouter,moonshot,cerebras,mistral,cohere,azure,gemini,sampling]`.
|
|
132
136
|
|
|
133
137
|
## Development
|
|
134
138
|
|
|
@@ -136,13 +140,13 @@ sampling; combine extras as needed, e.g.
|
|
|
136
140
|
pip install -e ".[dev,sampling]"
|
|
137
141
|
pytest
|
|
138
142
|
```
|
|
139
|
-
`dev` already includes the `anthropic`, `openai`, `mistralai`, `cohere`,
|
|
140
|
-
`azure-ai-inference` SDKs (tests exercise all
|
|
141
|
-
`anthropic`, `openai`, `deepseek`, `groq`, `xai`, `together`,
|
|
142
|
-
`perplexity`, `openrouter`, `moonshot`, `cerebras` share just
|
|
143
|
-
packages, and `mistral`/`cohere`/`azure` each need
|
|
144
|
-
silently skip) — add the specific `,<provider>`
|
|
145
|
-
installing outside of `dev`.
|
|
143
|
+
`dev` already includes the `anthropic`, `openai`, `mistralai`, `cohere`,
|
|
144
|
+
`azure-ai-inference`, and `google-genai` SDKs (tests exercise all fifteen
|
|
145
|
+
providers — `anthropic`, `openai`, `deepseek`, `groq`, `xai`, `together`,
|
|
146
|
+
`fireworks`, `perplexity`, `openrouter`, `moonshot`, `cerebras` share just
|
|
147
|
+
the first two packages, and `mistral`/`cohere`/`azure`/`gemini` each need
|
|
148
|
+
their own SDK — and never silently skip) — add the specific `,<provider>`
|
|
149
|
+
extra explicitly only if installing outside of `dev`.
|
|
146
150
|
|
|
147
151
|
Linting/formatting/type-checking/docs run through tox instead of extras — see
|
|
148
152
|
`tox.ini` (`tox -e lint`/`format`/`typecheck`/`docs`), each installing its own
|
|
@@ -36,6 +36,7 @@ parseforge/naming/providers/cost.py
|
|
|
36
36
|
parseforge/naming/providers/deepseek.py
|
|
37
37
|
parseforge/naming/providers/errors.py
|
|
38
38
|
parseforge/naming/providers/fireworks.py
|
|
39
|
+
parseforge/naming/providers/gemini.py
|
|
39
40
|
parseforge/naming/providers/groq.py
|
|
40
41
|
parseforge/naming/providers/mistral.py
|
|
41
42
|
parseforge/naming/providers/models.py
|
|
@@ -34,11 +34,16 @@ openai>=1.0.0
|
|
|
34
34
|
mistralai==1.10.0
|
|
35
35
|
cohere==5.21.1
|
|
36
36
|
azure-ai-inference>=1.0.0b9
|
|
37
|
+
google-genai>=0.2.0
|
|
37
38
|
|
|
38
39
|
[fireworks]
|
|
39
40
|
openai>=1.0.0
|
|
40
41
|
textfsm-ai[fireworks]>=0.6.1
|
|
41
42
|
|
|
43
|
+
[gemini]
|
|
44
|
+
google-genai>=0.2.0
|
|
45
|
+
textfsm-ai[gemini]>=0.6.1
|
|
46
|
+
|
|
42
47
|
[groq]
|
|
43
48
|
openai>=1.0.0
|
|
44
49
|
textfsm-ai[groq]>=0.6.1
|
|
@@ -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.10"
|
|
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" }
|
|
@@ -82,6 +82,7 @@ mistral = ["mistralai==1.10.0", "textfsm-ai[mistral]>=0.6.1"]
|
|
|
82
82
|
# textfsm-ai's own pin.
|
|
83
83
|
cohere = ["cohere==5.21.1", "textfsm-ai[cohere]>=0.6.1"]
|
|
84
84
|
azure = ["azure-ai-inference>=1.0.0b9", "textfsm-ai[azure]>=0.6.1"]
|
|
85
|
+
gemini = ["google-genai>=0.2.0", "textfsm-ai[gemini]>=0.6.1"]
|
|
85
86
|
sampling = ["netmiko>=4.0"]
|
|
86
87
|
dev = [
|
|
87
88
|
"pytest>=8.0.0",
|
|
@@ -89,15 +90,16 @@ dev = [
|
|
|
89
90
|
"ruff>=0.4.0",
|
|
90
91
|
"black>=24.0.0",
|
|
91
92
|
"mypy>=1.10.0",
|
|
92
|
-
# anthropic/openai/mistralai/cohere/azure-ai-inference
|
|
93
|
-
# runtime (see the extras above), but every provider
|
|
94
|
-
# the CLI/tests, which should never silently skip --
|
|
95
|
-
# pulls all
|
|
93
|
+
# anthropic/openai/mistralai/cohere/azure-ai-inference/google-genai
|
|
94
|
+
# are optional at runtime (see the extras above), but every provider
|
|
95
|
+
# is exercised by the CLI/tests, which should never silently skip --
|
|
96
|
+
# so dev always pulls all six in.
|
|
96
97
|
"anthropic>=0.40.0",
|
|
97
98
|
"openai>=1.0.0",
|
|
98
99
|
"mistralai==1.10.0",
|
|
99
100
|
"cohere==5.21.1",
|
|
100
101
|
"azure-ai-inference>=1.0.0b9",
|
|
102
|
+
"google-genai>=0.2.0",
|
|
101
103
|
]
|
|
102
104
|
# Tooling for scripts/release.ps1 and CI's own package-build step.
|
|
103
105
|
# bump2version isn't otherwise installed anywhere -- release.ps1 assumes
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.2.9"
|
|
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
|
|
File without changes
|
|
File without changes
|