astromesh-cli 0.3.1__tar.gz → 0.4.0__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.
- {astromesh_cli-0.3.1 → astromesh_cli-0.4.0}/.gitignore +3 -0
- {astromesh_cli-0.3.1 → astromesh_cli-0.4.0}/CHANGELOG.md +18 -0
- {astromesh_cli-0.3.1 → astromesh_cli-0.4.0}/PKG-INFO +2 -2
- {astromesh_cli-0.3.1 → astromesh_cli-0.4.0}/astromesh_cli/__init__.py +1 -1
- astromesh_cli-0.4.0/astromesh_cli/commands/eval.py +17 -0
- {astromesh_cli-0.3.1 → astromesh_cli-0.4.0}/astromesh_cli/commands/new.py +7 -2
- {astromesh_cli-0.3.1 → astromesh_cli-0.4.0}/astromesh_cli/commands/run.py +42 -9
- {astromesh_cli-0.3.1 → astromesh_cli-0.4.0}/astromesh_cli/main.py +5 -2
- astromesh_cli-0.4.0/astromesh_cli/templates/agent.yaml.j2 +47 -0
- {astromesh_cli-0.3.1 → astromesh_cli-0.4.0}/pyproject.toml +2 -2
- astromesh_cli-0.4.0/tests/test_core070.py +73 -0
- {astromesh_cli-0.3.1 → astromesh_cli-0.4.0}/uv.lock +16 -2
- astromesh_cli-0.3.1/astromesh_cli/templates/agent.yaml.j2 +0 -29
- {astromesh_cli-0.3.1 → astromesh_cli-0.4.0}/README.md +0 -0
- {astromesh_cli-0.3.1 → astromesh_cli-0.4.0}/astromesh_cli/client.py +0 -0
- {astromesh_cli-0.3.1 → astromesh_cli-0.4.0}/astromesh_cli/commands/__init__.py +0 -0
- {astromesh_cli-0.3.1 → astromesh_cli-0.4.0}/astromesh_cli/commands/agents.py +0 -0
- {astromesh_cli-0.3.1 → astromesh_cli-0.4.0}/astromesh_cli/commands/ask.py +0 -0
- {astromesh_cli-0.3.1 → astromesh_cli-0.4.0}/astromesh_cli/commands/dev.py +0 -0
- {astromesh_cli-0.3.1 → astromesh_cli-0.4.0}/astromesh_cli/commands/doctor.py +0 -0
- {astromesh_cli-0.3.1 → astromesh_cli-0.4.0}/astromesh_cli/commands/mesh.py +0 -0
- {astromesh_cli-0.3.1 → astromesh_cli-0.4.0}/astromesh_cli/commands/metrics.py +0 -0
- {astromesh_cli-0.3.1 → astromesh_cli-0.4.0}/astromesh_cli/commands/peers.py +0 -0
- {astromesh_cli-0.3.1 → astromesh_cli-0.4.0}/astromesh_cli/commands/providers.py +0 -0
- {astromesh_cli-0.3.1 → astromesh_cli-0.4.0}/astromesh_cli/commands/services.py +0 -0
- {astromesh_cli-0.3.1 → astromesh_cli-0.4.0}/astromesh_cli/commands/status.py +0 -0
- {astromesh_cli-0.3.1 → astromesh_cli-0.4.0}/astromesh_cli/commands/tools.py +0 -0
- {astromesh_cli-0.3.1 → astromesh_cli-0.4.0}/astromesh_cli/commands/traces.py +0 -0
- {astromesh_cli-0.3.1 → astromesh_cli-0.4.0}/astromesh_cli/output.py +0 -0
- {astromesh_cli-0.3.1 → astromesh_cli-0.4.0}/astromesh_cli/templates/tool.py.j2 +0 -0
- {astromesh_cli-0.3.1 → astromesh_cli-0.4.0}/astromesh_cli/templates/workflow.yaml.j2 +0 -0
- {astromesh_cli-0.3.1 → astromesh_cli-0.4.0}/tests/conftest.py +0 -0
- {astromesh_cli-0.3.1 → astromesh_cli-0.4.0}/tests/test_ask.py +0 -0
- {astromesh_cli-0.3.1 → astromesh_cli-0.4.0}/tests/test_client.py +0 -0
- {astromesh_cli-0.3.1 → astromesh_cli-0.4.0}/tests/test_main.py +0 -0
- {astromesh_cli-0.3.1 → astromesh_cli-0.4.0}/tests/test_output.py +0 -0
- {astromesh_cli-0.3.1 → astromesh_cli-0.4.0}/tests/test_run_usage.py +0 -0
- {astromesh_cli-0.3.1 → astromesh_cli-0.4.0}/tests/test_version.py +0 -0
|
@@ -7,6 +7,24 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.4.0] - 2026-10-16
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- `astromeshctl eval <config_dir> [archivos...] [--out reporte.json]`: corre los `kind: Eval` con
|
|
14
|
+
el runner del core (`astromesh.evals`, el mismo de `astromesh-eval`). Exit 0 si todos cumplen,
|
|
15
|
+
1 si alguno queda bajo umbral, 2 por error de carga (un eval inválido no corre ningún caso).
|
|
16
|
+
- `run` muestra un panel **BLOCKED by guardrail** con dirección, regla y motivo cuando el core
|
|
17
|
+
0.70 bloquea la corrida (que vuelve HTTP 200 con el rechazo como `answer`), y una tabla con las
|
|
18
|
+
`propuestas` de las tools `mode: propose`. La tabla por modelo suma la columna `Cached`
|
|
19
|
+
(`tokens_cached`).
|
|
20
|
+
- `new agent --context-window N`; la plantilla trae `guardrails` válidos para el core 0.70
|
|
21
|
+
(`pii_detection` en entrada y salida, `max_length`, `cost_limit` 4000), `context_window`
|
|
22
|
+
(comentado si no se pasa) y un `knowledge.bundle` comentado. Un test la carga con
|
|
23
|
+
`AgentRuntime` sin warnings de guardrails.
|
|
24
|
+
|
|
25
|
+
### Changed
|
|
26
|
+
- Piso del core `astromesh>=0.70.0`.
|
|
27
|
+
|
|
10
28
|
## [0.3.1] - 2026-09-27
|
|
11
29
|
|
|
12
30
|
### Changed
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: astromesh-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Summary: CLI tool for managing Astromesh nodes and clusters
|
|
5
5
|
License: Apache-2.0
|
|
6
6
|
Requires-Python: >=3.12
|
|
7
|
-
Requires-Dist: astromesh>=0.
|
|
7
|
+
Requires-Dist: astromesh>=0.70.0
|
|
8
8
|
Requires-Dist: jinja2>=3.1.0
|
|
9
9
|
Requires-Dist: rich>=13.0.0
|
|
10
10
|
Requires-Dist: typer>=0.12.0
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""astromeshctl eval — run `kind: Eval` files with core's eval runner (`astromesh-eval`)."""
|
|
2
|
+
|
|
3
|
+
import typer
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def eval_command(
|
|
7
|
+
config_dir: str = typer.Argument(..., help="Config tree with the agents under test"),
|
|
8
|
+
files: list[str] | None = typer.Argument( # noqa: B008
|
|
9
|
+
None, help="*.eval.yaml files (default: <config_dir>/evals/*.eval.yaml)"
|
|
10
|
+
),
|
|
11
|
+
out: str | None = typer.Option(None, "--out", help="Write the JSON report here"),
|
|
12
|
+
) -> None:
|
|
13
|
+
"""Run agent evals locally. Exit 0 all pass, 1 below threshold, 2 load error."""
|
|
14
|
+
from astromesh.evals.__main__ import main
|
|
15
|
+
|
|
16
|
+
argv = [config_dir, *(files or [])] + (["--out", out] if out else [])
|
|
17
|
+
raise typer.Exit(code=main(argv))
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
"""astromeshctl new — Scaffold new agents, workflows, and tools."""
|
|
2
2
|
|
|
3
3
|
from pathlib import Path
|
|
4
|
-
from typing import Optional
|
|
5
4
|
|
|
6
5
|
import typer
|
|
7
6
|
from jinja2 import Environment, FileSystemLoader
|
|
@@ -27,6 +26,7 @@ def render_agent_template(
|
|
|
27
26
|
temperature: float = 0.3,
|
|
28
27
|
max_tokens: int = 2048,
|
|
29
28
|
description: str = "",
|
|
29
|
+
context_window: int | None = None,
|
|
30
30
|
) -> str:
|
|
31
31
|
"""Render an agent YAML template."""
|
|
32
32
|
env = _get_jinja_env()
|
|
@@ -42,6 +42,7 @@ def render_agent_template(
|
|
|
42
42
|
tools=tools or [],
|
|
43
43
|
temperature=temperature,
|
|
44
44
|
max_tokens=max_tokens,
|
|
45
|
+
context_window=context_window,
|
|
45
46
|
)
|
|
46
47
|
|
|
47
48
|
|
|
@@ -80,7 +81,10 @@ def new_agent(
|
|
|
80
81
|
provider: str = typer.Option("ollama", "--provider", help="LLM provider"),
|
|
81
82
|
model: str = typer.Option("llama3.1:8b", "--model", help="Model name"),
|
|
82
83
|
orchestration: str = typer.Option("react", "--orchestration", help="Orchestration pattern"),
|
|
83
|
-
tools:
|
|
84
|
+
tools: list[str] | None = typer.Option(None, "--tools", help="Tools to include"), # noqa: B008
|
|
85
|
+
context_window: int | None = typer.Option(
|
|
86
|
+
None, "--context-window", help="Model context window in tokens (sizes the history)"
|
|
87
|
+
),
|
|
84
88
|
output_dir: str = typer.Option("./config/agents", "--output-dir", help="Output directory"),
|
|
85
89
|
force: bool = typer.Option(False, "--force", help="Overwrite existing file"),
|
|
86
90
|
) -> None:
|
|
@@ -99,6 +103,7 @@ def new_agent(
|
|
|
99
103
|
model=model,
|
|
100
104
|
orchestration_pattern=orchestration,
|
|
101
105
|
tools=tools,
|
|
106
|
+
context_window=context_window,
|
|
102
107
|
)
|
|
103
108
|
dest.write_text(content)
|
|
104
109
|
console.print(
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
import json
|
|
4
4
|
import uuid
|
|
5
|
-
from typing import Optional
|
|
6
5
|
|
|
7
6
|
import typer
|
|
8
7
|
from rich.panel import Panel
|
|
@@ -36,16 +35,44 @@ def _format_run_output(data: dict) -> tuple[str, str, list[dict]]:
|
|
|
36
35
|
return text, subtitle, rows
|
|
37
36
|
|
|
38
37
|
|
|
38
|
+
def _print_guardrail_and_propuestas(data: dict) -> None:
|
|
39
|
+
"""Core 0.70: a blocked run is HTTP 200 with the refusal as `answer`; say so."""
|
|
40
|
+
if data.get("blocked"):
|
|
41
|
+
g = data.get("guardrail") or {}
|
|
42
|
+
console.print(
|
|
43
|
+
Panel(
|
|
44
|
+
f"{g.get('direction', '?')} rule [bold]{g.get('rule', '?')}[/bold]: "
|
|
45
|
+
f"{g.get('reason', '')}",
|
|
46
|
+
title="[red]BLOCKED by guardrail[/red]",
|
|
47
|
+
border_style="red",
|
|
48
|
+
)
|
|
49
|
+
)
|
|
50
|
+
propuestas = [p for p in data.get("propuestas") or [] if isinstance(p, dict)]
|
|
51
|
+
if propuestas:
|
|
52
|
+
table = Table(title="Propuestas (sin ejecutar)", show_header=True)
|
|
53
|
+
for col in ("Tool", "Tipo", "Destino", "Operación", "Argumentos"):
|
|
54
|
+
table.add_column(col, style="cyan" if col == "Tool" else None)
|
|
55
|
+
for p in propuestas:
|
|
56
|
+
table.add_row(
|
|
57
|
+
str(p.get("tool", "")),
|
|
58
|
+
str(p.get("tipo", "")),
|
|
59
|
+
str(p.get("destino", "")),
|
|
60
|
+
str(p.get("operacion", "")),
|
|
61
|
+
json.dumps(p.get("argumentos", {}), ensure_ascii=False),
|
|
62
|
+
)
|
|
63
|
+
console.print(table)
|
|
64
|
+
|
|
65
|
+
|
|
39
66
|
def run_command(
|
|
40
67
|
name: str = typer.Argument(..., help="Agent or workflow name to run"),
|
|
41
68
|
query: str = typer.Argument("", help="Query to send to the agent"),
|
|
42
|
-
session:
|
|
69
|
+
session: str | None = typer.Option(
|
|
43
70
|
None, "--session", help="Session ID (auto-generated if not set)"
|
|
44
71
|
),
|
|
45
72
|
json_output: bool = typer.Option(False, "--json", help="Output raw JSON response"),
|
|
46
73
|
timeout: float = typer.Option(60.0, "--timeout", help="Request timeout in seconds"),
|
|
47
74
|
workflow: bool = typer.Option(False, "--workflow", help="Run as workflow instead of agent"),
|
|
48
|
-
input_data:
|
|
75
|
+
input_data: str | None = typer.Option(None, "--input", help="Workflow input data (JSON)"),
|
|
49
76
|
) -> None:
|
|
50
77
|
"""Execute an agent with a query or run a workflow."""
|
|
51
78
|
if workflow:
|
|
@@ -64,7 +91,7 @@ def run_command(
|
|
|
64
91
|
json={"query": query, "session_id": session_id},
|
|
65
92
|
timeout=timeout,
|
|
66
93
|
)
|
|
67
|
-
except Exception as e:
|
|
94
|
+
except Exception as e: # noqa: BLE001
|
|
68
95
|
print_error(f"Failed to run agent '{name}': {e}")
|
|
69
96
|
raise typer.Exit(code=1)
|
|
70
97
|
|
|
@@ -78,18 +105,24 @@ def run_command(
|
|
|
78
105
|
text,
|
|
79
106
|
title=f"[cyan]{name}[/cyan]",
|
|
80
107
|
subtitle=subtitle,
|
|
81
|
-
border_style="blue",
|
|
108
|
+
border_style="red" if data.get("blocked") else "blue",
|
|
82
109
|
)
|
|
83
110
|
)
|
|
111
|
+
_print_guardrail_and_propuestas(data)
|
|
84
112
|
if by_model_rows:
|
|
85
113
|
table = Table(title="Por modelo", show_header=True)
|
|
86
|
-
for col in ("Provider", "Model", "Role", "Calls", "In", "Out", "Cost"):
|
|
114
|
+
for col in ("Provider", "Model", "Role", "Calls", "In", "Cached", "Out", "Cost"):
|
|
87
115
|
table.add_column(col, style="cyan" if col == "Provider" else None)
|
|
88
116
|
for r in by_model_rows:
|
|
89
117
|
cost = r.get("cost", 0.0) or 0.0
|
|
90
118
|
table.add_row(
|
|
91
|
-
r.get("provider", ""),
|
|
92
|
-
|
|
119
|
+
r.get("provider", ""),
|
|
120
|
+
r.get("model", ""),
|
|
121
|
+
r.get("role", ""),
|
|
122
|
+
str(r.get("calls", 0)),
|
|
123
|
+
str(r.get("tokens_in", 0)),
|
|
124
|
+
str(r.get("tokens_cached", 0)),
|
|
125
|
+
str(r.get("tokens_out", 0)),
|
|
93
126
|
f"${cost:.4f}" if cost else "—",
|
|
94
127
|
)
|
|
95
128
|
console.print(table)
|
|
@@ -115,7 +148,7 @@ def _run_workflow(
|
|
|
115
148
|
json=payload,
|
|
116
149
|
timeout=timeout,
|
|
117
150
|
)
|
|
118
|
-
except Exception as e:
|
|
151
|
+
except Exception as e: # noqa: BLE001
|
|
119
152
|
print_error(f"Failed to run workflow '{name}': {e}")
|
|
120
153
|
raise typer.Exit(code=1)
|
|
121
154
|
|
|
@@ -21,6 +21,7 @@ from astromesh_cli.commands import (
|
|
|
21
21
|
tools,
|
|
22
22
|
traces,
|
|
23
23
|
)
|
|
24
|
+
from astromesh_cli.commands import eval as eval_cmd
|
|
24
25
|
|
|
25
26
|
app = typer.Typer(
|
|
26
27
|
name="astromeshctl",
|
|
@@ -44,12 +45,14 @@ app.command("trace")(traces.trace_command)
|
|
|
44
45
|
app.command("metrics")(metrics.metrics_command)
|
|
45
46
|
app.command("cost")(metrics.cost_command)
|
|
46
47
|
app.command("ask")(ask.ask_command)
|
|
48
|
+
app.command("eval")(eval_cmd.eval_command)
|
|
47
49
|
|
|
48
50
|
|
|
49
51
|
@app.command()
|
|
50
52
|
def version():
|
|
51
53
|
"""Show astromesh version."""
|
|
52
|
-
from importlib.metadata import PackageNotFoundError
|
|
54
|
+
from importlib.metadata import PackageNotFoundError
|
|
55
|
+
from importlib.metadata import version as _pkg_version
|
|
53
56
|
|
|
54
57
|
typer.echo(f"astromesh-cli {__version__}")
|
|
55
58
|
try:
|
|
@@ -70,7 +73,7 @@ def _load_plugins(app: typer.Typer) -> None:
|
|
|
70
73
|
try:
|
|
71
74
|
register_fn = ep.load()
|
|
72
75
|
register_fn(app)
|
|
73
|
-
except Exception as exc:
|
|
76
|
+
except Exception as exc: # noqa: BLE001
|
|
74
77
|
typer.echo(f"Warning: failed to load plugin '{ep.name}': {exc}", err=True)
|
|
75
78
|
|
|
76
79
|
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
apiVersion: astromesh/v1
|
|
2
|
+
kind: Agent
|
|
3
|
+
metadata:
|
|
4
|
+
name: {{ name }}
|
|
5
|
+
spec:
|
|
6
|
+
identity:
|
|
7
|
+
display_name: "{{ display_name }}"
|
|
8
|
+
description: "{{ description }}"
|
|
9
|
+
model:
|
|
10
|
+
primary:
|
|
11
|
+
provider: {{ provider }}
|
|
12
|
+
model: {{ model }}
|
|
13
|
+
temperature: {{ temperature }}
|
|
14
|
+
max_tokens: {{ max_tokens }}
|
|
15
|
+
{% if context_window %}
|
|
16
|
+
context_window: {{ context_window }}
|
|
17
|
+
{% else %}
|
|
18
|
+
# Declare the model's window so the runtime sizes the history budget:
|
|
19
|
+
# context_window: 128000
|
|
20
|
+
{% endif %}
|
|
21
|
+
routing:
|
|
22
|
+
strategy: quality_first
|
|
23
|
+
prompts:
|
|
24
|
+
system: |
|
|
25
|
+
You are {{ display_name }}, a helpful AI assistant.
|
|
26
|
+
orchestration:
|
|
27
|
+
pattern: {{ orchestration_pattern }}
|
|
28
|
+
max_iterations: 10
|
|
29
|
+
timeout: 30
|
|
30
|
+
{% if tools %}
|
|
31
|
+
tools:
|
|
32
|
+
{% for tool in tools %}
|
|
33
|
+
- name: {{ tool }}
|
|
34
|
+
{% endfor %}
|
|
35
|
+
{% endif %}
|
|
36
|
+
guardrails:
|
|
37
|
+
input:
|
|
38
|
+
- type: pii_detection # redact (default) | block | warn
|
|
39
|
+
- type: max_length # truncate (default) | block
|
|
40
|
+
max_chars: 10000
|
|
41
|
+
output:
|
|
42
|
+
- type: pii_detection
|
|
43
|
+
- type: cost_limit # blocks the run past this many output tokens
|
|
44
|
+
max_tokens_per_turn: 4000
|
|
45
|
+
# An OKF knowledge bundle (a directory) adds the kb_index/kb_read/kb_find tools:
|
|
46
|
+
# knowledge:
|
|
47
|
+
# bundle: ./knowledge/{{ name }}
|
|
@@ -4,12 +4,12 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "astromesh-cli"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.4.0"
|
|
8
8
|
description = "CLI tool for managing Astromesh nodes and clusters"
|
|
9
9
|
requires-python = ">=3.12"
|
|
10
10
|
license = {text = "Apache-2.0"}
|
|
11
11
|
dependencies = [
|
|
12
|
-
"astromesh>=0.
|
|
12
|
+
"astromesh>=0.70.0",
|
|
13
13
|
"typer>=0.12.0",
|
|
14
14
|
"rich>=13.0.0",
|
|
15
15
|
"jinja2>=3.1.0",
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"""CLI against core 0.70: blocked/propuestas in `run`, a template core loads, `eval`."""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
|
|
5
|
+
import yaml
|
|
6
|
+
from typer.testing import CliRunner
|
|
7
|
+
|
|
8
|
+
from astromesh_cli.commands import run as run_mod
|
|
9
|
+
from astromesh_cli.commands.new import render_agent_template
|
|
10
|
+
from astromesh_cli.main import app
|
|
11
|
+
|
|
12
|
+
runner = CliRunner()
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def test_run_shows_blocked_and_propuestas(monkeypatch):
|
|
16
|
+
body = {
|
|
17
|
+
"answer": "I can't help with that request.",
|
|
18
|
+
"blocked": True,
|
|
19
|
+
"guardrail": {"direction": "input", "rule": "pii_detection", "reason": "pii detected"},
|
|
20
|
+
"propuestas": [
|
|
21
|
+
{"tool": "erp_crear", "tipo": "api", "destino": "erp", "operacion": "crear"}
|
|
22
|
+
],
|
|
23
|
+
"usage": {"tokens_in": 0, "tokens_out": 0, "by_model": []},
|
|
24
|
+
}
|
|
25
|
+
monkeypatch.setattr(run_mod, "api_post_with_timeout", lambda *a, **k: body)
|
|
26
|
+
result = runner.invoke(app, ["run", "support", "hi"])
|
|
27
|
+
assert result.exit_code == 0
|
|
28
|
+
assert "BLOCKED by guardrail" in result.stdout and "pii_detection" in result.stdout
|
|
29
|
+
assert "erp_crear" in result.stdout
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
async def test_agent_template_loads_in_core_without_warnings(tmp_path, caplog):
|
|
33
|
+
from astromesh.runtime.engine import AgentRuntime
|
|
34
|
+
|
|
35
|
+
(tmp_path / "agents").mkdir()
|
|
36
|
+
text = render_agent_template("bot", context_window=128000)
|
|
37
|
+
spec = yaml.safe_load(text)["spec"]
|
|
38
|
+
assert spec["model"]["primary"]["context_window"] == 128000
|
|
39
|
+
(tmp_path / "agents" / "bot.agent.yaml").write_text(text)
|
|
40
|
+
runtime = AgentRuntime(config_dir=str(tmp_path))
|
|
41
|
+
with caplog.at_level(logging.WARNING, logger="astromesh.core.guardrails"):
|
|
42
|
+
await runtime.bootstrap()
|
|
43
|
+
assert "bot" in runtime._agents, runtime._agent_errors
|
|
44
|
+
assert not caplog.records
|
|
45
|
+
guard = runtime._agents["bot"]._guardrails
|
|
46
|
+
assert guard.has_input and guard.has_output and guard.cost_limit["max_tokens"] == 4000
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def test_template_without_context_window_leaves_it_commented():
|
|
50
|
+
assert (
|
|
51
|
+
"context_window"
|
|
52
|
+
not in yaml.safe_load(render_agent_template("bot"))["spec"]["model"]["primary"]
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def test_eval_forwards_to_core_runner(monkeypatch):
|
|
57
|
+
import astromesh.evals.__main__ as core_eval
|
|
58
|
+
|
|
59
|
+
seen = {}
|
|
60
|
+
|
|
61
|
+
def fake_main(argv):
|
|
62
|
+
seen["argv"] = argv
|
|
63
|
+
return 1
|
|
64
|
+
|
|
65
|
+
monkeypatch.setattr(core_eval, "main", fake_main)
|
|
66
|
+
result = runner.invoke(app, ["eval", "cfg", "a.eval.yaml", "--out", "r.json"])
|
|
67
|
+
assert result.exit_code == 1
|
|
68
|
+
assert seen["argv"] == ["cfg", "a.eval.yaml", "--out", "r.json"]
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def test_eval_without_evals_is_a_load_error(tmp_path):
|
|
72
|
+
result = runner.invoke(app, ["eval", str(tmp_path)])
|
|
73
|
+
assert result.exit_code == 2
|
|
@@ -35,9 +35,10 @@ wheels = [
|
|
|
35
35
|
|
|
36
36
|
[[package]]
|
|
37
37
|
name = "astromesh"
|
|
38
|
-
version = "0.
|
|
38
|
+
version = "0.70.0"
|
|
39
39
|
source = { editable = "../" }
|
|
40
40
|
dependencies = [
|
|
41
|
+
{ name = "astromesh-okf" },
|
|
41
42
|
{ name = "fastapi" },
|
|
42
43
|
{ name = "httpx" },
|
|
43
44
|
{ name = "jinja2" },
|
|
@@ -52,6 +53,7 @@ requires-dist = [
|
|
|
52
53
|
{ name = "aiosqlite", marker = "extra == 'sqlite'", specifier = ">=0.20.0" },
|
|
53
54
|
{ name = "astromesh", extras = ["redis", "postgres", "sqlite", "chromadb", "qdrant", "faiss", "embeddings", "onnx", "observability", "mcp", "mesh", "litellm", "centinela", "glyph"], marker = "extra == 'all'" },
|
|
54
55
|
{ name = "astromesh-glyph", marker = "extra == 'glyph'", editable = "../astromesh-glyph" },
|
|
56
|
+
{ name = "astromesh-okf", specifier = ">=0.1,<0.2" },
|
|
55
57
|
{ name = "asyncpg", marker = "extra == 'postgres'", specifier = ">=0.30.0" },
|
|
56
58
|
{ name = "chromadb", marker = "extra == 'chromadb'", specifier = ">=0.5.0" },
|
|
57
59
|
{ name = "faiss-cpu", marker = "extra == 'faiss'", specifier = ">=1.8.0" },
|
|
@@ -95,7 +97,7 @@ dev = [
|
|
|
95
97
|
|
|
96
98
|
[[package]]
|
|
97
99
|
name = "astromesh-cli"
|
|
98
|
-
version = "0.
|
|
100
|
+
version = "0.4.0"
|
|
99
101
|
source = { editable = "." }
|
|
100
102
|
dependencies = [
|
|
101
103
|
{ name = "astromesh" },
|
|
@@ -137,6 +139,18 @@ requires-dist = [
|
|
|
137
139
|
]
|
|
138
140
|
provides-extras = ["dev", "test", "all"]
|
|
139
141
|
|
|
142
|
+
[[package]]
|
|
143
|
+
name = "astromesh-okf"
|
|
144
|
+
version = "0.1.0"
|
|
145
|
+
source = { registry = "https://pypi.org/simple" }
|
|
146
|
+
dependencies = [
|
|
147
|
+
{ name = "pyyaml" },
|
|
148
|
+
]
|
|
149
|
+
sdist = { url = "https://files.pythonhosted.org/packages/a5/33/ccf8f70e6cd22021dff6e11cba7c34b3d25d504675a1a417f18bc7c37730/astromesh_okf-0.1.0.tar.gz", hash = "sha256:acb932dc0a6969a77b52588e3572c147b0b69f5136860818c1b83474b503c7cf", size = 51340, upload-time = "2026-10-14T02:03:58.256Z" }
|
|
150
|
+
wheels = [
|
|
151
|
+
{ url = "https://files.pythonhosted.org/packages/e9/9d/cf840f718086009198c763516beec46abaf5d2346ea2afde34c9c6be94b3/astromesh_okf-0.1.0-py3-none-any.whl", hash = "sha256:8be5fa9d80cbd331decc02f2f28b3b614d79f5fc7d7967ed15cb1f0aa20b2bab", size = 28989, upload-time = "2026-10-14T02:03:57.036Z" },
|
|
152
|
+
]
|
|
153
|
+
|
|
140
154
|
[[package]]
|
|
141
155
|
name = "certifi"
|
|
142
156
|
version = "2026.7.22"
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
apiVersion: astromesh/v1
|
|
2
|
-
kind: Agent
|
|
3
|
-
metadata:
|
|
4
|
-
name: {{ name }}
|
|
5
|
-
spec:
|
|
6
|
-
identity:
|
|
7
|
-
display_name: "{{ display_name }}"
|
|
8
|
-
description: "{{ description }}"
|
|
9
|
-
model:
|
|
10
|
-
primary:
|
|
11
|
-
provider: {{ provider }}
|
|
12
|
-
model: {{ model }}
|
|
13
|
-
temperature: {{ temperature }}
|
|
14
|
-
max_tokens: {{ max_tokens }}
|
|
15
|
-
routing:
|
|
16
|
-
strategy: quality_first
|
|
17
|
-
prompts:
|
|
18
|
-
system: |
|
|
19
|
-
You are {{ display_name }}, a helpful AI assistant.
|
|
20
|
-
orchestration:
|
|
21
|
-
pattern: {{ orchestration_pattern }}
|
|
22
|
-
max_iterations: 10
|
|
23
|
-
timeout: 30
|
|
24
|
-
{% if tools %}
|
|
25
|
-
tools:
|
|
26
|
-
{% for tool in tools %}
|
|
27
|
-
- name: {{ tool }}
|
|
28
|
-
{% endfor %}
|
|
29
|
-
{% endif %}
|
|
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
|