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.
- fluxloop_cli/commands/__init__.py +2 -2
- fluxloop_cli/commands/config.py +12 -2
- fluxloop_cli/commands/generate.py +4 -84
- fluxloop_cli/commands/init.py +39 -12
- fluxloop_cli/commands/record.py +150 -0
- fluxloop_cli/commands/run.py +5 -1
- fluxloop_cli/commands/status.py +41 -5
- fluxloop_cli/config_loader.py +103 -14
- fluxloop_cli/config_schema.py +83 -0
- fluxloop_cli/constants.py +15 -0
- fluxloop_cli/project_paths.py +60 -8
- fluxloop_cli/templates.py +288 -118
- {fluxloop_cli-0.1.0.dist-info → fluxloop_cli-0.2.0.dist-info}/METADATA +31 -27
- fluxloop_cli-0.2.0.dist-info/RECORD +26 -0
- fluxloop_cli-0.1.0.dist-info/RECORD +0 -24
- {fluxloop_cli-0.1.0.dist-info → fluxloop_cli-0.2.0.dist-info}/WHEEL +0 -0
- {fluxloop_cli-0.1.0.dist-info → fluxloop_cli-0.2.0.dist-info}/entry_points.txt +0 -0
- {fluxloop_cli-0.1.0.dist-info → fluxloop_cli-0.2.0.dist-info}/top_level.txt +0 -0
|
@@ -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
|
+
|
fluxloop_cli/project_paths.py
CHANGED
|
@@ -3,7 +3,13 @@
|
|
|
3
3
|
from pathlib import Path
|
|
4
4
|
from typing import Optional
|
|
5
5
|
|
|
6
|
-
from .constants import
|
|
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.
|
|
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
|
|
43
|
+
Command-line interface for running agent simulations.
|
|
44
44
|
|
|
45
45
|
## Installation
|
|
46
46
|
|
|
47
|
-
```
|
|
47
|
+
```
|
|
48
48
|
pip install fluxloop-cli
|
|
49
49
|
```
|
|
50
50
|
|
|
51
|
-
##
|
|
52
|
-
|
|
53
|
-
```bash
|
|
54
|
-
# Initialize a new FluxLoop project
|
|
55
|
-
fluxloop init
|
|
51
|
+
## Configuration Overview (v0.2.0)
|
|
56
52
|
|
|
57
|
-
|
|
58
|
-
fluxloop run
|
|
53
|
+
FluxLoop CLI now stores experiment settings in four files under `configs/`:
|
|
59
54
|
|
|
60
|
-
|
|
61
|
-
|
|
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
|
-
|
|
64
|
-
fluxloop
|
|
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
|
-
##
|
|
63
|
+
## Key Commands
|
|
68
64
|
|
|
69
|
-
-
|
|
70
|
-
-
|
|
71
|
-
-
|
|
72
|
-
-
|
|
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
|
-
|
|
72
|
+
Run `fluxloop --help` or `fluxloop <command> --help` for more detail.
|
|
75
73
|
|
|
76
|
-
|
|
77
|
-
- FluxLoop SDK
|
|
74
|
+
## Developing
|
|
78
75
|
|
|
79
|
-
|
|
76
|
+
Install dependencies and run tests:
|
|
80
77
|
|
|
81
|
-
|
|
78
|
+
```
|
|
79
|
+
python -m venv .venv
|
|
80
|
+
source .venv/bin/activate
|
|
81
|
+
pip install -e .[dev]
|
|
82
|
+
pytest
|
|
83
|
+
```
|
|
82
84
|
|
|
83
|
-
|
|
85
|
+
To package the CLI:
|
|
84
86
|
|
|
85
|
-
|
|
87
|
+
```
|
|
88
|
+
./build.sh
|
|
89
|
+
```
|
|
86
90
|
|