fluxloop-cli 0.2.11__tar.gz → 0.2.13__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.
- {fluxloop_cli-0.2.11 → fluxloop_cli-0.2.13}/PKG-INFO +4 -4
- {fluxloop_cli-0.2.11 → fluxloop_cli-0.2.13}/fluxloop_cli/__init__.py +1 -1
- {fluxloop_cli-0.2.11 → fluxloop_cli-0.2.13}/fluxloop_cli/runner.py +111 -18
- {fluxloop_cli-0.2.11 → fluxloop_cli-0.2.13}/fluxloop_cli.egg-info/PKG-INFO +4 -4
- {fluxloop_cli-0.2.11 → fluxloop_cli-0.2.13}/pyproject.toml +4 -4
- {fluxloop_cli-0.2.11 → fluxloop_cli-0.2.13}/README.md +0 -0
- {fluxloop_cli-0.2.11 → fluxloop_cli-0.2.13}/fluxloop_cli/arg_binder.py +0 -0
- {fluxloop_cli-0.2.11 → fluxloop_cli-0.2.13}/fluxloop_cli/commands/__init__.py +0 -0
- {fluxloop_cli-0.2.11 → fluxloop_cli-0.2.13}/fluxloop_cli/commands/config.py +0 -0
- {fluxloop_cli-0.2.11 → fluxloop_cli-0.2.13}/fluxloop_cli/commands/generate.py +0 -0
- {fluxloop_cli-0.2.11 → fluxloop_cli-0.2.13}/fluxloop_cli/commands/init.py +0 -0
- {fluxloop_cli-0.2.11 → fluxloop_cli-0.2.13}/fluxloop_cli/commands/parse.py +0 -0
- {fluxloop_cli-0.2.11 → fluxloop_cli-0.2.13}/fluxloop_cli/commands/record.py +0 -0
- {fluxloop_cli-0.2.11 → fluxloop_cli-0.2.13}/fluxloop_cli/commands/run.py +0 -0
- {fluxloop_cli-0.2.11 → fluxloop_cli-0.2.13}/fluxloop_cli/commands/status.py +0 -0
- {fluxloop_cli-0.2.11 → fluxloop_cli-0.2.13}/fluxloop_cli/config_loader.py +0 -0
- {fluxloop_cli-0.2.11 → fluxloop_cli-0.2.13}/fluxloop_cli/config_schema.py +0 -0
- {fluxloop_cli-0.2.11 → fluxloop_cli-0.2.13}/fluxloop_cli/constants.py +0 -0
- {fluxloop_cli-0.2.11 → fluxloop_cli-0.2.13}/fluxloop_cli/input_generator.py +0 -0
- {fluxloop_cli-0.2.11 → fluxloop_cli-0.2.13}/fluxloop_cli/llm_generator.py +0 -0
- {fluxloop_cli-0.2.11 → fluxloop_cli-0.2.13}/fluxloop_cli/main.py +0 -0
- {fluxloop_cli-0.2.11 → fluxloop_cli-0.2.13}/fluxloop_cli/project_paths.py +0 -0
- {fluxloop_cli-0.2.11 → fluxloop_cli-0.2.13}/fluxloop_cli/target_loader.py +0 -0
- {fluxloop_cli-0.2.11 → fluxloop_cli-0.2.13}/fluxloop_cli/templates.py +0 -0
- {fluxloop_cli-0.2.11 → fluxloop_cli-0.2.13}/fluxloop_cli/validators.py +0 -0
- {fluxloop_cli-0.2.11 → fluxloop_cli-0.2.13}/fluxloop_cli.egg-info/SOURCES.txt +0 -0
- {fluxloop_cli-0.2.11 → fluxloop_cli-0.2.13}/fluxloop_cli.egg-info/dependency_links.txt +0 -0
- {fluxloop_cli-0.2.11 → fluxloop_cli-0.2.13}/fluxloop_cli.egg-info/entry_points.txt +0 -0
- {fluxloop_cli-0.2.11 → fluxloop_cli-0.2.13}/fluxloop_cli.egg-info/requires.txt +0 -0
- {fluxloop_cli-0.2.11 → fluxloop_cli-0.2.13}/fluxloop_cli.egg-info/top_level.txt +0 -0
- {fluxloop_cli-0.2.11 → fluxloop_cli-0.2.13}/setup.cfg +0 -0
- {fluxloop_cli-0.2.11 → fluxloop_cli-0.2.13}/tests/test_arg_binder.py +0 -0
- {fluxloop_cli-0.2.11 → fluxloop_cli-0.2.13}/tests/test_config_command.py +0 -0
- {fluxloop_cli-0.2.11 → fluxloop_cli-0.2.13}/tests/test_input_generator.py +0 -0
- {fluxloop_cli-0.2.11 → fluxloop_cli-0.2.13}/tests/test_target_loader.py +0 -0
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: fluxloop-cli
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.13
|
|
4
4
|
Summary: FluxLoop CLI for running agent simulations
|
|
5
5
|
Author-email: FluxLoop Team <team@fluxloop.dev>
|
|
6
6
|
License: Apache-2.0
|
|
7
|
-
Project-URL: Homepage, https://github.com/
|
|
7
|
+
Project-URL: Homepage, https://github.com/chuckgu/fluxloop
|
|
8
8
|
Project-URL: Documentation, https://docs.fluxloop.dev
|
|
9
|
-
Project-URL: Repository, https://github.com/
|
|
10
|
-
Project-URL: Issues, https://github.com/
|
|
9
|
+
Project-URL: Repository, https://github.com/chuckgu/fluxloop
|
|
10
|
+
Project-URL: Issues, https://github.com/chuckgu/fluxloop/issues
|
|
11
11
|
Classifier: Development Status :: 3 - Alpha
|
|
12
12
|
Classifier: Intended Audience :: Developers
|
|
13
13
|
Classifier: License :: OSI Approved :: Apache Software License
|
|
@@ -5,6 +5,7 @@ Runner modules for executing experiments and agents.
|
|
|
5
5
|
from __future__ import annotations
|
|
6
6
|
|
|
7
7
|
import asyncio
|
|
8
|
+
import contextvars
|
|
8
9
|
import json
|
|
9
10
|
import inspect
|
|
10
11
|
import os
|
|
@@ -56,8 +57,17 @@ class ExperimentRunner:
|
|
|
56
57
|
|
|
57
58
|
offline_dir = output_base / "artifacts"
|
|
58
59
|
offline_dir.mkdir(parents=True, exist_ok=True)
|
|
60
|
+
# Ensure downstream load_env() calls don't re-enable collector unintentionally
|
|
61
|
+
should_use_collector = (not no_collector) and bool(config.collector_url)
|
|
62
|
+
if not should_use_collector:
|
|
63
|
+
os.environ["FLUXLOOP_USE_COLLECTOR"] = "false"
|
|
64
|
+
else:
|
|
65
|
+
os.environ["FLUXLOOP_USE_COLLECTOR"] = "true"
|
|
66
|
+
# Pin offline store dir in env so refresh_config respects our path
|
|
67
|
+
os.environ.setdefault("FLUXLOOP_OFFLINE_DIR", str(offline_dir))
|
|
68
|
+
os.environ.setdefault("FLUXLOOP_OFFLINE_ENABLED", "true")
|
|
59
69
|
fluxloop.configure(
|
|
60
|
-
use_collector=
|
|
70
|
+
use_collector=should_use_collector,
|
|
61
71
|
collector_url=config.collector_url or None,
|
|
62
72
|
api_key=config.collector_api_key,
|
|
63
73
|
offline_store_enabled=True,
|
|
@@ -346,6 +356,11 @@ class ExperimentRunner:
|
|
|
346
356
|
final_output = self._extract_final_output(callback_messages, observations)
|
|
347
357
|
if final_output is not None:
|
|
348
358
|
result = final_output
|
|
359
|
+
else:
|
|
360
|
+
# As a fallback, coerce async streams or objects into text
|
|
361
|
+
coerced = await self._coerce_result_to_text(result)
|
|
362
|
+
if coerced is not None:
|
|
363
|
+
result = coerced
|
|
349
364
|
|
|
350
365
|
# Mark successful
|
|
351
366
|
self.results["successful"] += 1
|
|
@@ -432,10 +447,16 @@ class ExperimentRunner:
|
|
|
432
447
|
return await self._consume_async_gen(agent_func, kwargs)
|
|
433
448
|
|
|
434
449
|
if asyncio.iscoroutinefunction(agent_func):
|
|
435
|
-
|
|
450
|
+
# Ensure current contextvars are preserved for the coroutine
|
|
451
|
+
ctx = contextvars.copy_context()
|
|
452
|
+
result = await ctx.run(lambda: agent_func(**kwargs))
|
|
436
453
|
else:
|
|
437
454
|
loop = asyncio.get_event_loop()
|
|
438
|
-
|
|
455
|
+
# Preserve contextvars across thread execution
|
|
456
|
+
ctx = contextvars.copy_context()
|
|
457
|
+
def _call():
|
|
458
|
+
return agent_func(**kwargs)
|
|
459
|
+
result = await loop.run_in_executor(None, lambda: ctx.run(_call))
|
|
439
460
|
|
|
440
461
|
# If an async generator/iterable is returned, consume it into a string
|
|
441
462
|
if inspect.isasyncgen(result) or hasattr(result, "__aiter__"):
|
|
@@ -510,10 +531,22 @@ class ExperimentRunner:
|
|
|
510
531
|
return await self._consume_async_iterable(gen)
|
|
511
532
|
|
|
512
533
|
async def _consume_async_iterable(self, agen: Any) -> Any:
|
|
513
|
-
"""Consume async iterable items, extracting text via runner.stream_output_path.
|
|
534
|
+
"""Consume async iterable items, extracting text via runner.stream_output_path.
|
|
535
|
+
|
|
536
|
+
Uses a copied contextvars context to ensure FluxLoop context is preserved
|
|
537
|
+
across async iteration boundaries created by upstream frameworks.
|
|
538
|
+
"""
|
|
514
539
|
path = (getattr(self.config.runner, "stream_output_path", None) or "message.delta").split(".")
|
|
515
540
|
chunks: List[str] = []
|
|
516
|
-
|
|
541
|
+
|
|
542
|
+
ctx = contextvars.copy_context()
|
|
543
|
+
|
|
544
|
+
while True:
|
|
545
|
+
try:
|
|
546
|
+
item = await ctx.run(agen.__anext__)
|
|
547
|
+
except StopAsyncIteration:
|
|
548
|
+
break
|
|
549
|
+
|
|
517
550
|
val = self._get_by_path(item, path)
|
|
518
551
|
if isinstance(val, str) and val:
|
|
519
552
|
chunks.append(val)
|
|
@@ -591,6 +624,33 @@ class ExperimentRunner:
|
|
|
591
624
|
"args": list(args),
|
|
592
625
|
"kwargs": kwargs,
|
|
593
626
|
}
|
|
627
|
+
|
|
628
|
+
async def _coerce_result_to_text(self, value: Any) -> Optional[str]:
|
|
629
|
+
"""Best-effort conversion of agent result into text for summaries.
|
|
630
|
+
|
|
631
|
+
Handles async generators/iterables, known streaming item shapes, and strings.
|
|
632
|
+
"""
|
|
633
|
+
import inspect as _inspect
|
|
634
|
+
|
|
635
|
+
if value is None:
|
|
636
|
+
return None
|
|
637
|
+
|
|
638
|
+
if isinstance(value, str):
|
|
639
|
+
return value
|
|
640
|
+
|
|
641
|
+
# Async iterable/generator → consume to text
|
|
642
|
+
if _inspect.isasyncgen(value) or hasattr(value, "__aiter__"):
|
|
643
|
+
try:
|
|
644
|
+
return await self._consume_async_iterable(value)
|
|
645
|
+
except Exception:
|
|
646
|
+
return None
|
|
647
|
+
|
|
648
|
+
# Try extracting text from known event-like objects
|
|
649
|
+
fallback = self._extract_stream_text(value)
|
|
650
|
+
if fallback:
|
|
651
|
+
return fallback
|
|
652
|
+
|
|
653
|
+
return None
|
|
594
654
|
|
|
595
655
|
def _save_results(self) -> None:
|
|
596
656
|
"""Save results to output directory."""
|
|
@@ -657,26 +717,59 @@ class ExperimentRunner:
|
|
|
657
717
|
if not trace_ids:
|
|
658
718
|
return
|
|
659
719
|
|
|
660
|
-
|
|
661
|
-
|
|
720
|
+
# Gather candidate offline stores
|
|
721
|
+
candidates = []
|
|
722
|
+
try:
|
|
723
|
+
from fluxloop import get_config as _get_sdk_config # type: ignore
|
|
724
|
+
sdk_dir = Path(_get_sdk_config().offline_store_dir)
|
|
725
|
+
candidates.append(sdk_dir / "observations.jsonl")
|
|
726
|
+
except Exception:
|
|
727
|
+
pass
|
|
728
|
+
|
|
729
|
+
candidates.append(self.offline_dir / "observations.jsonl")
|
|
730
|
+
# Legacy fallback removed now that SDK defaults to experiments/artifacts
|
|
731
|
+
|
|
732
|
+
# Unique existing paths in priority order
|
|
733
|
+
seen = set()
|
|
734
|
+
existing: list[Path] = []
|
|
735
|
+
for p in candidates:
|
|
736
|
+
try:
|
|
737
|
+
rp = p.resolve()
|
|
738
|
+
except Exception:
|
|
739
|
+
continue
|
|
740
|
+
if rp in seen:
|
|
741
|
+
continue
|
|
742
|
+
seen.add(rp)
|
|
743
|
+
if rp.exists():
|
|
744
|
+
existing.append(rp)
|
|
745
|
+
|
|
746
|
+
if not existing:
|
|
662
747
|
return
|
|
663
748
|
|
|
664
749
|
destination = self.output_dir / "observations.jsonl"
|
|
665
750
|
copied = 0
|
|
751
|
+
seen_lines = set()
|
|
666
752
|
|
|
667
|
-
with
|
|
668
|
-
|
|
669
|
-
) as dst:
|
|
670
|
-
for line in src:
|
|
671
|
-
if not line.strip():
|
|
672
|
-
continue
|
|
753
|
+
with destination.open("w", encoding="utf-8") as dst:
|
|
754
|
+
for source_path in existing:
|
|
673
755
|
try:
|
|
674
|
-
|
|
675
|
-
|
|
756
|
+
with source_path.open("r", encoding="utf-8") as src:
|
|
757
|
+
for line in src:
|
|
758
|
+
if not line.strip():
|
|
759
|
+
continue
|
|
760
|
+
try:
|
|
761
|
+
record = json.loads(line)
|
|
762
|
+
except json.JSONDecodeError:
|
|
763
|
+
continue
|
|
764
|
+
if record.get("trace_id") in trace_ids:
|
|
765
|
+
key = (record.get("id"), record.get("start_time"))
|
|
766
|
+
if key in seen_lines:
|
|
767
|
+
continue
|
|
768
|
+
dst.write(json.dumps(record) + "\n")
|
|
769
|
+
seen_lines.add(key)
|
|
770
|
+
copied += 1
|
|
771
|
+
except OSError:
|
|
676
772
|
continue
|
|
677
|
-
if record.get("trace_id") in trace_ids:
|
|
678
|
-
dst.write(json.dumps(record) + "\n")
|
|
679
|
-
copied += 1
|
|
680
773
|
|
|
681
774
|
console.print(
|
|
682
775
|
f"[green]✅ Saved {copied} observations to {destination.name}[/green]"
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: fluxloop-cli
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.13
|
|
4
4
|
Summary: FluxLoop CLI for running agent simulations
|
|
5
5
|
Author-email: FluxLoop Team <team@fluxloop.dev>
|
|
6
6
|
License: Apache-2.0
|
|
7
|
-
Project-URL: Homepage, https://github.com/
|
|
7
|
+
Project-URL: Homepage, https://github.com/chuckgu/fluxloop
|
|
8
8
|
Project-URL: Documentation, https://docs.fluxloop.dev
|
|
9
|
-
Project-URL: Repository, https://github.com/
|
|
10
|
-
Project-URL: Issues, https://github.com/
|
|
9
|
+
Project-URL: Repository, https://github.com/chuckgu/fluxloop
|
|
10
|
+
Project-URL: Issues, https://github.com/chuckgu/fluxloop/issues
|
|
11
11
|
Classifier: Development Status :: 3 - Alpha
|
|
12
12
|
Classifier: Intended Audience :: Developers
|
|
13
13
|
Classifier: License :: OSI Approved :: Apache Software License
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "fluxloop-cli"
|
|
7
|
-
version = "0.2.
|
|
7
|
+
version = "0.2.13"
|
|
8
8
|
description = "FluxLoop CLI for running agent simulations"
|
|
9
9
|
authors = [
|
|
10
10
|
{name = "FluxLoop Team", email = "team@fluxloop.dev"}
|
|
@@ -56,10 +56,10 @@ anthropic = [
|
|
|
56
56
|
fluxloop = "fluxloop_cli.main:app"
|
|
57
57
|
|
|
58
58
|
[project.urls]
|
|
59
|
-
Homepage = "https://github.com/
|
|
59
|
+
Homepage = "https://github.com/chuckgu/fluxloop"
|
|
60
60
|
Documentation = "https://docs.fluxloop.dev"
|
|
61
|
-
Repository = "https://github.com/
|
|
62
|
-
Issues = "https://github.com/
|
|
61
|
+
Repository = "https://github.com/chuckgu/fluxloop"
|
|
62
|
+
Issues = "https://github.com/chuckgu/fluxloop/issues"
|
|
63
63
|
|
|
64
64
|
[tool.setuptools.packages.find]
|
|
65
65
|
where = ["."]
|
|
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
|