fluxloop-cli 0.1.0__py3-none-any.whl → 0.2.0__py3-none-any.whl

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.

Potentially problematic release.


This version of fluxloop-cli might be problematic. Click here for more details.

@@ -0,0 +1,83 @@
1
+ """Configuration section definitions and merge strategy for FluxLoop CLI."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+ from pathlib import Path
7
+ from typing import Dict, Iterable, Tuple
8
+
9
+ from .constants import (
10
+ CONFIG_DIRECTORY_NAME,
11
+ CONFIG_SECTION_FILENAMES,
12
+ CONFIG_SECTION_ORDER,
13
+ DEFAULT_CONFIG_FILENAME,
14
+ LEGACY_CONFIG_FILENAMES,
15
+ )
16
+
17
+
18
+ @dataclass(frozen=True)
19
+ class ConfigSection:
20
+ """Represents an individual configuration section file."""
21
+
22
+ key: str
23
+ filename: str
24
+ description: str
25
+ required: bool = False
26
+
27
+ def resolve_path(self, root: Path) -> Path:
28
+ """Return the canonical path to this section within a project root."""
29
+
30
+ return root / CONFIG_DIRECTORY_NAME / self.filename
31
+
32
+
33
+ CONFIG_SECTIONS: Tuple[ConfigSection, ...] = (
34
+ ConfigSection(
35
+ key="project",
36
+ filename=CONFIG_SECTION_FILENAMES["project"],
37
+ description="Project metadata, collector configuration, and shared defaults.",
38
+ required=True,
39
+ ),
40
+ ConfigSection(
41
+ key="input",
42
+ filename=CONFIG_SECTION_FILENAMES["input"],
43
+ description="Personas, base inputs, and input generation settings.",
44
+ ),
45
+ ConfigSection(
46
+ key="simulation",
47
+ filename=CONFIG_SECTION_FILENAMES["simulation"],
48
+ description="Experiment execution parameters (runner, iterations, environments).",
49
+ required=True,
50
+ ),
51
+ ConfigSection(
52
+ key="evaluation",
53
+ filename=CONFIG_SECTION_FILENAMES["evaluation"],
54
+ description="Post-run evaluation pipelines and scoring strategies.",
55
+ ),
56
+ )
57
+
58
+ CONFIG_SECTION_BY_KEY: Dict[str, ConfigSection] = {
59
+ section.key: section for section in CONFIG_SECTIONS
60
+ }
61
+
62
+ CONFIG_REQUIRED_KEYS: Tuple[str, ...] = tuple(
63
+ section.key for section in CONFIG_SECTIONS if section.required
64
+ )
65
+
66
+
67
+ def iter_section_paths(root: Path) -> Iterable[Path]:
68
+ """Yield section paths in merge order for the given project root."""
69
+
70
+ for key in CONFIG_SECTION_ORDER:
71
+ section = CONFIG_SECTION_BY_KEY[key]
72
+ yield section.resolve_path(root)
73
+
74
+
75
+ def is_legacy_config(filename: str) -> bool:
76
+ """Return True if filename refers to a legacy single-file configuration."""
77
+
78
+ return filename in (DEFAULT_CONFIG_FILENAME, *LEGACY_CONFIG_FILENAMES)
79
+
80
+
81
+ MERGE_PRIORITY: Tuple[str, ...] = CONFIG_SECTION_ORDER
82
+ """Order in which configuration sections should be merged."""
83
+
fluxloop_cli/constants.py CHANGED
@@ -10,3 +10,18 @@ LEGACY_CONFIG_FILENAMES = ("fluxloop.yaml",)
10
10
 
11
11
  DEFAULT_CONFIG_PATH = Path(DEFAULT_CONFIG_FILENAME)
12
12
 
13
+ CONFIG_DIRECTORY_NAME = "configs"
14
+ PROJECT_CONFIG_FILENAME = "project.yaml"
15
+ INPUT_CONFIG_FILENAME = "input.yaml"
16
+ SIMULATION_CONFIG_FILENAME = "simulation.yaml"
17
+ EVALUATION_CONFIG_FILENAME = "evaluation.yaml"
18
+
19
+ CONFIG_SECTION_FILENAMES = {
20
+ "project": PROJECT_CONFIG_FILENAME,
21
+ "input": INPUT_CONFIG_FILENAME,
22
+ "simulation": SIMULATION_CONFIG_FILENAME,
23
+ "evaluation": EVALUATION_CONFIG_FILENAME,
24
+ }
25
+
26
+ CONFIG_SECTION_ORDER = tuple(CONFIG_SECTION_FILENAMES.keys())
27
+
@@ -3,7 +3,13 @@
3
3
  from pathlib import Path
4
4
  from typing import Optional
5
5
 
6
- from .constants import DEFAULT_CONFIG_FILENAME, DEFAULT_CONFIG_PATH, DEFAULT_ROOT_DIR_NAME
6
+ from .constants import (
7
+ DEFAULT_CONFIG_FILENAME,
8
+ DEFAULT_CONFIG_PATH,
9
+ DEFAULT_ROOT_DIR_NAME,
10
+ CONFIG_DIRECTORY_NAME,
11
+ CONFIG_SECTION_FILENAMES,
12
+ )
7
13
 
8
14
 
9
15
  def _normalize_path(path: Path) -> Path:
@@ -43,16 +49,37 @@ def resolve_project_relative(path: Path, project: Optional[str], root: Optional[
43
49
  def resolve_config_path(config_file: Path, project: Optional[str], root: Optional[Path]) -> Path:
44
50
  """Resolve the path to a configuration file, honoring project/root settings."""
45
51
 
46
- if project:
47
- if config_file == DEFAULT_CONFIG_PATH:
48
- target = Path(DEFAULT_CONFIG_FILENAME)
49
- else:
50
- target = config_file
51
- return resolve_project_relative(target, project, root)
52
-
53
52
  if config_file.is_absolute():
54
53
  return _normalize_path(config_file)
55
54
 
55
+ base_dir = (
56
+ resolve_project_dir(project, root)
57
+ if project
58
+ else _normalize_path(Path.cwd())
59
+ )
60
+
61
+ config_dir = base_dir / CONFIG_DIRECTORY_NAME
62
+
63
+ # Default: prefer simulation config inside configs/
64
+ if config_file == DEFAULT_CONFIG_PATH:
65
+ simulation_candidate = config_dir / CONFIG_SECTION_FILENAMES["simulation"]
66
+ if simulation_candidate.exists() or config_dir.exists():
67
+ return _normalize_path(simulation_candidate)
68
+
69
+ # Explicit section filenames resolve relative to configs/
70
+ if config_file.name in CONFIG_SECTION_FILENAMES.values():
71
+ return _normalize_path(config_dir / config_file.name)
72
+
73
+ # Explicit configs/ directory
74
+ if config_file == Path(CONFIG_DIRECTORY_NAME):
75
+ return _normalize_path(config_dir)
76
+
77
+ if config_file.parent == Path(CONFIG_DIRECTORY_NAME):
78
+ return _normalize_path(config_dir / config_file.name)
79
+
80
+ if project:
81
+ return resolve_project_relative(config_file, project, root)
82
+
56
83
  return _normalize_path(Path.cwd() / config_file)
57
84
 
58
85
 
@@ -78,3 +105,28 @@ def resolve_env_path(env_file: Path, project: Optional[str], root: Optional[Path
78
105
 
79
106
  return _normalize_path(Path.cwd() / env_file)
80
107
 
108
+
109
+ def resolve_config_directory(project: Optional[str], root: Optional[Path]) -> Path:
110
+ """Return the canonical configs/ directory for the given context."""
111
+
112
+ base_dir = (
113
+ resolve_project_dir(project, root)
114
+ if project
115
+ else _normalize_path(Path.cwd())
116
+ )
117
+ return _normalize_path(base_dir / CONFIG_DIRECTORY_NAME)
118
+
119
+
120
+ def resolve_config_section_path(
121
+ section_key: str,
122
+ project: Optional[str],
123
+ root: Optional[Path],
124
+ ) -> Path:
125
+ """Return the path to a specific configuration section file."""
126
+
127
+ if section_key not in CONFIG_SECTION_FILENAMES:
128
+ raise KeyError(f"Unknown configuration section: {section_key}")
129
+
130
+ config_dir = resolve_config_directory(project, root)
131
+ return _normalize_path(config_dir / CONFIG_SECTION_FILENAMES[section_key])
132
+
fluxloop_cli/templates.py CHANGED
@@ -2,127 +2,297 @@
2
2
  Templates for generating configuration and code files.
3
3
  """
4
4
 
5
+ from textwrap import dedent
6
+
7
+
8
+ def create_project_config(project_name: str) -> str:
9
+ """Create default project-level configuration content."""
10
+
11
+ return dedent(
12
+ f"""
13
+ # FluxLoop Project Configuration
14
+ name: {project_name}
15
+ description: AI agent simulation project
16
+ version: 1.0.0
17
+
18
+ collector_url: null
19
+ collector_api_key: null
20
+
21
+ tags:
22
+ - simulation
23
+ - testing
24
+
25
+ metadata:
26
+ team: development
27
+ environment: local
28
+ """
29
+ ).strip() + "\n"
30
+
31
+
32
+ def create_input_config() -> str:
33
+ """Create default input configuration content."""
34
+
35
+ return dedent(
36
+ """
37
+ # FluxLoop Input Configuration
38
+ personas:
39
+ - name: novice_user
40
+ description: A user new to the system
41
+ characteristics:
42
+ - Asks basic questions
43
+ - May use incorrect terminology
44
+ - Needs detailed explanations
45
+ language: en
46
+ expertise_level: novice
47
+ goals:
48
+ - Understand system capabilities
49
+ - Complete basic tasks
50
+
51
+ - name: expert_user
52
+ description: An experienced power user
53
+ characteristics:
54
+ - Uses technical terminology
55
+ - Asks complex questions
56
+ - Expects efficient responses
57
+ language: en
58
+ expertise_level: expert
59
+ goals:
60
+ - Optimize workflows
61
+ - Access advanced features
62
+
63
+ base_inputs:
64
+ - input: "How do I get started?"
65
+ expected_intent: help
66
+ - input: "What can you do?"
67
+ expected_intent: capabilities
68
+ - input: "Show me an example"
69
+ expected_intent: demo
70
+
71
+ variation_strategies:
72
+ - type: rephrase
73
+ - type: verbose
74
+ - type: error_prone
75
+
76
+ variation_count: 2
77
+ variation_temperature: 0.7
78
+
79
+ inputs_file: inputs/generated.yaml
80
+
81
+ input_generation:
82
+ mode: llm
83
+ llm:
84
+ enabled: true
85
+ provider: openai
86
+ model: gpt-4o-mini
87
+ api_key: null
88
+ """
89
+ ).strip() + "\n"
90
+
91
+
92
+ def create_simulation_config(project_name: str) -> str:
93
+ """Create default simulation configuration content."""
94
+
95
+ return dedent(
96
+ f"""
97
+ # FluxLoop Simulation Configuration
98
+ name: {project_name}_experiment
99
+ description: AI agent simulation experiment
100
+
101
+ iterations: 10
102
+ parallel_runs: 1
103
+ run_delay_seconds: 0
104
+ seed: 42
105
+
106
+ runner:
107
+ module_path: "examples.simple_agent"
108
+ function_name: "run"
109
+ target: "examples.simple_agent:run"
110
+ working_directory: .
111
+ timeout_seconds: 120
112
+ max_retries: 3
113
+
114
+ replay_args:
115
+ enabled: false
116
+ recording_file: recordings/args_recording.jsonl
117
+ override_param_path: data.content
118
+
119
+ output_directory: experiments
120
+ save_traces: true
121
+ save_aggregated_metrics: true
122
+ """
123
+ ).strip() + "\n"
124
+
125
+
126
+ def create_evaluation_config() -> str:
127
+ """Create default evaluation configuration content."""
128
+
129
+ return dedent(
130
+ """
131
+ # FluxLoop Evaluation Configuration
132
+ evaluators:
133
+ - name: success_checker
134
+ type: rule_based
135
+ enabled: true
136
+ rules:
137
+ - check: output_not_empty
138
+ weight: 1.0
139
+
140
+ - name: response_quality
141
+ type: llm_judge
142
+ enabled: false
143
+ model: gpt-4o-mini
144
+ prompt_template: |
145
+ Rate the quality of this response on a scale of 1-10:
146
+ Input: {input}
147
+ Output: {output}
148
+
149
+ Consider: relevance, completeness, clarity
150
+ Score:
151
+ """
152
+ ).strip() + "\n"
5
153
 
6
- def create_experiment_config(project_name: str) -> str:
7
- """Create a default experiment configuration."""
8
- return f"""# FluxLoop Experiment Configuration
9
- name: {project_name}_experiment
10
- description: AI agent simulation experiment
11
- version: 1.0.0
12
-
13
- # Simulation settings
14
- iterations: 10
15
- parallel_runs: 1
16
- seed: 42
17
- run_delay_seconds: 0
18
-
19
- # User personas for simulation
20
- personas:
21
- - name: novice_user
22
- description: A user new to the system
23
- characteristics:
24
- - Asks basic questions
25
- - May use incorrect terminology
26
- - Needs detailed explanations
27
- language: en
28
- expertise_level: novice
29
- goals:
30
- - Understand how the system works
31
- - Complete simple tasks
32
-
33
- - name: expert_user
34
- description: An experienced power user
35
- characteristics:
36
- - Uses technical terminology
37
- - Asks complex questions
38
- - Expects efficient responses
39
- language: en
40
- expertise_level: expert
41
- goals:
42
- - Optimize workflows
43
- - Access advanced features
44
-
45
- # Base inputs for reference (generate inputs from these)
46
- base_inputs:
47
- - input: "How do I get started?"
48
- expected_intent: help
49
- - input: "What can you do?"
50
- expected_intent: capabilities
51
- - input: "Show me an example"
52
- expected_intent: demo
53
-
54
- # Agent runner configuration
55
- runner:
56
- target: "examples.simple_agent:run" # module:function or module:Class.method
57
- working_directory: . # IMPORTANT: Set this to your project's root directory
58
- timeout_seconds: 30
59
- max_retries: 3
60
-
61
- # Recorded argument replay (optional)
62
- replay_args:
63
- enabled: false
64
- recording_file: recordings/args_recording.jsonl
65
- callable_providers:
66
- send_message_callback: "builtin:collector.send"
67
- send_error_callback: "builtin:collector.error"
68
- override_param_path: data.content
69
-
70
- # Input generation configuration
71
- input_generation:
72
- mode: llm
73
- llm:
74
- enabled: true
75
- provider: openai
76
- model: gpt-5
77
- strategies:
78
- - type: rephrase
79
- - type: verbose
80
- - type: error_prone
81
- variation_count: 2
82
-
83
- # Evaluation methods
84
- evaluators:
85
- - name: success_checker
86
- type: rule_based
87
- enabled: true
88
- rules:
89
- - check: output_not_empty
90
- weight: 1.0
91
-
92
- - name: response_quality
93
- type: llm_judge
94
- enabled: false
95
- model: gpt-5
96
- prompt_template: |
97
- Rate the quality of this response on a scale of 1-10:
98
- Input: {{input}}
99
- Output: {{output}}
100
-
101
- Consider: relevance, completeness, clarity
102
- Score:
103
-
104
- # Output configuration
105
- output_directory: experiments
106
- save_traces: true
107
- save_aggregated_metrics: true
108
-
109
- # Inputs must be generated before running experiments
110
- inputs_file: inputs/generated.yaml
111
-
112
- # Collector settings (optional)
113
- # collector_url: http://localhost:8000
114
- # collector_api_key: your-api-key
115
-
116
- # Tags and metadata
117
- tags:
118
- - simulation
119
- - testing
120
- metadata:
121
- team: development
122
- environment: local
123
- """
124
154
 
155
+ def create_sample_agent() -> str:
156
+ """Create a sample agent implementation."""
157
+
158
+ return dedent(
159
+ '''
160
+ """Sample agent implementation for FluxLoop testing."""
161
+
162
+ import random
163
+ import time
164
+ from typing import Any, Dict
165
+
166
+ import fluxloop
167
+
168
+
169
+ @fluxloop.agent(name="SimpleAgent")
170
+ def run(input_text: str) -> str:
171
+ """Main agent entry point."""
172
+ processed = process_input(input_text)
173
+ response = generate_response(processed)
174
+ time.sleep(random.uniform(0.1, 0.5))
175
+ return response
176
+
177
+
178
+ @fluxloop.prompt(model="simple-model")
179
+ def generate_response(processed_input: Dict[str, Any]) -> str:
180
+ intent = processed_input.get("intent", "unknown")
181
+ responses = {
182
+ "greeting": "Hello! How can I help you today?",
183
+ "help": "I can assist you with various tasks. What would you like to know?",
184
+ "capabilities": "I can answer questions, provide information, and help with tasks.",
185
+ "demo": "Here's an example: You can ask me about any topic and I'll try to help.",
186
+ "unknown": "I'm not sure I understand. Could you please rephrase?",
187
+ }
188
+ return responses.get(intent, responses["unknown"])
189
+
190
+
191
+ @fluxloop.tool(description="Process and analyze input text")
192
+ def process_input(text: str) -> Dict[str, Any]:
193
+ text_lower = text.lower()
194
+
195
+ intent = "unknown"
196
+ if any(word in text_lower for word in ["hello", "hi", "hey"]):
197
+ intent = "greeting"
198
+ elif any(word in text_lower for word in ["help", "start", "begin"]):
199
+ intent = "help"
200
+ elif any(word in text_lower for word in ["can you", "what can", "capabilities"]):
201
+ intent = "capabilities"
202
+ elif "example" in text_lower or "demo" in text_lower:
203
+ intent = "demo"
204
+
205
+ return {
206
+ "original": text,
207
+ "intent": intent,
208
+ "word_count": len(text.split()),
209
+ "has_question": "?" in text,
210
+ }
211
+
212
+
213
+ if __name__ == "__main__":
214
+ with fluxloop.instrument("test_run"):
215
+ result = run("Hello, what can you help me with?")
216
+ print(f"Result: {result}")
217
+ '''
218
+ ).strip() + "\n"
219
+
220
+
221
+ def create_gitignore() -> str:
222
+ """Create a .gitignore file."""
223
+
224
+ return dedent(
225
+ """
226
+ # Python
227
+ __pycache__/
228
+ *.py[cod]
229
+ *$py.class
230
+ *.so
231
+ .Python
232
+ venv/
233
+ env/
234
+ ENV/
235
+ .venv/
236
+
237
+ # FluxLoop
238
+ traces/
239
+ *.trace
240
+ *.log
241
+
242
+ # Environment
243
+ .env
244
+ .env.local
245
+ *.env
246
+
247
+ # IDE
248
+ .vscode/
249
+ .idea/
250
+ *.swp
251
+ *.swo
252
+
253
+ # OS
254
+ .DS_Store
255
+ Thumbs.db
256
+
257
+ # Testing
258
+ .pytest_cache/
259
+ .coverage
260
+ htmlcov/
261
+ *.coverage
262
+ """
263
+ ).strip() + "\n"
264
+
265
+
266
+ def create_env_file() -> str:
267
+ """Create a .env template file."""
125
268
 
269
+ return dedent(
270
+ """
271
+ # FluxLoop Configuration
272
+ FLUXLOOP_COLLECTOR_URL=http://localhost:8000
273
+ FLUXLOOP_API_KEY=your-api-key-here
274
+ FLUXLOOP_ENABLED=true
275
+ FLUXLOOP_DEBUG=false
276
+ FLUXLOOP_SAMPLE_RATE=1.0
277
+ # Argument Recording (global toggle)
278
+ FLUXLOOP_RECORD_ARGS=false
279
+ FLUXLOOP_RECORDING_FILE=recordings/args_recording.jsonl
280
+
281
+ # Service Configuration
282
+ FLUXLOOP_SERVICE_NAME=my-agent
283
+ FLUXLOOP_ENVIRONMENT=development
284
+
285
+ # LLM API Keys (if needed)
286
+ # OPENAI_API_KEY=sk-...
287
+ # ANTHROPIC_API_KEY=sk-ant-...
288
+
289
+ # Other Configuration
290
+ # Add your custom environment variables here
291
+ """
292
+ ).strip() + "\n"
293
+ """
294
+ Templates for generating configuration and code files.
295
+ """
126
296
  def create_sample_agent() -> str:
127
297
  """Create a sample agent implementation."""
128
298
  return '''"""
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fluxloop-cli
3
- Version: 0.1.0
3
+ Version: 0.2.0
4
4
  Summary: FluxLoop CLI for running agent simulations
5
5
  Author-email: FluxLoop Team <team@fluxloop.dev>
6
6
  License: Apache-2.0
@@ -40,47 +40,51 @@ Requires-Dist: anthropic>=0.7.0; extra == "anthropic"
40
40
 
41
41
  # FluxLoop CLI
42
42
 
43
- Command-line interface for running agent simulations and managing FluxLoop workflows.
43
+ Command-line interface for running agent simulations.
44
44
 
45
45
  ## Installation
46
46
 
47
- ```bash
47
+ ```
48
48
  pip install fluxloop-cli
49
49
  ```
50
50
 
51
- ## Quick Start
52
-
53
- ```bash
54
- # Initialize a new FluxLoop project
55
- fluxloop init
51
+ ## Configuration Overview (v0.2.0)
56
52
 
57
- # Run agent simulations
58
- fluxloop run
53
+ FluxLoop CLI now stores experiment settings in four files under `configs/`:
59
54
 
60
- # Generate test inputs
61
- fluxloop generate
55
+ - `configs/project.yaml` project metadata, collector defaults
56
+ - `configs/input.yaml` – personas, base inputs, input generation options
57
+ - `configs/simulation.yaml` – runtime parameters (iterations, runner, replay args)
58
+ - `configs/evaluation.yaml` – evaluator definitions (rule-based, LLM judge, etc.)
62
59
 
63
- # Check status
64
- fluxloop status
65
- ```
60
+ The legacy `setting.yaml` is still supported, but new projects created with
61
+ `fluxloop init project` will generate the structured layout above.
66
62
 
67
- ## Features
63
+ ## Key Commands
68
64
 
69
- - 🚀 **Easy Setup**: Initialize projects with a single command
70
- - 🔄 **Simulation Runner**: Execute agent tests with various input scenarios
71
- - 📝 **Input Generation**: LLM-powered test input generation
72
- - 📊 **Rich Output**: Beautiful terminal UI with detailed progress tracking
65
+ - `fluxloop init project` scaffold a new project (configs, `.env`, examples)
66
+ - `fluxloop generate inputs` produce input variations for the active project
67
+ - `fluxloop run experiment` execute an experiment using `configs/simulation.yaml`
68
+ - `fluxloop parse experiment` convert experiment outputs into readable artifacts
69
+ - `fluxloop config set-llm` – update LLM provider/model in `configs/input.yaml`
70
+ - `fluxloop record enable|disable|status` – toggle recording mode across `.env` and simulation config
73
71
 
74
- ## Requirements
72
+ Run `fluxloop --help` or `fluxloop <command> --help` for more detail.
75
73
 
76
- - Python 3.8 or higher
77
- - FluxLoop SDK
74
+ ## Developing
78
75
 
79
- ## Documentation
76
+ Install dependencies and run tests:
80
77
 
81
- For detailed documentation, visit [https://docs.fluxloop.dev](https://docs.fluxloop.dev)
78
+ ```
79
+ python -m venv .venv
80
+ source .venv/bin/activate
81
+ pip install -e .[dev]
82
+ pytest
83
+ ```
82
84
 
83
- ## License
85
+ To package the CLI:
84
86
 
85
- Apache License 2.0 - see LICENSE file for details
87
+ ```
88
+ ./build.sh
89
+ ```
86
90