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.
Files changed (35) hide show
  1. {fluxloop_cli-0.2.11 → fluxloop_cli-0.2.13}/PKG-INFO +4 -4
  2. {fluxloop_cli-0.2.11 → fluxloop_cli-0.2.13}/fluxloop_cli/__init__.py +1 -1
  3. {fluxloop_cli-0.2.11 → fluxloop_cli-0.2.13}/fluxloop_cli/runner.py +111 -18
  4. {fluxloop_cli-0.2.11 → fluxloop_cli-0.2.13}/fluxloop_cli.egg-info/PKG-INFO +4 -4
  5. {fluxloop_cli-0.2.11 → fluxloop_cli-0.2.13}/pyproject.toml +4 -4
  6. {fluxloop_cli-0.2.11 → fluxloop_cli-0.2.13}/README.md +0 -0
  7. {fluxloop_cli-0.2.11 → fluxloop_cli-0.2.13}/fluxloop_cli/arg_binder.py +0 -0
  8. {fluxloop_cli-0.2.11 → fluxloop_cli-0.2.13}/fluxloop_cli/commands/__init__.py +0 -0
  9. {fluxloop_cli-0.2.11 → fluxloop_cli-0.2.13}/fluxloop_cli/commands/config.py +0 -0
  10. {fluxloop_cli-0.2.11 → fluxloop_cli-0.2.13}/fluxloop_cli/commands/generate.py +0 -0
  11. {fluxloop_cli-0.2.11 → fluxloop_cli-0.2.13}/fluxloop_cli/commands/init.py +0 -0
  12. {fluxloop_cli-0.2.11 → fluxloop_cli-0.2.13}/fluxloop_cli/commands/parse.py +0 -0
  13. {fluxloop_cli-0.2.11 → fluxloop_cli-0.2.13}/fluxloop_cli/commands/record.py +0 -0
  14. {fluxloop_cli-0.2.11 → fluxloop_cli-0.2.13}/fluxloop_cli/commands/run.py +0 -0
  15. {fluxloop_cli-0.2.11 → fluxloop_cli-0.2.13}/fluxloop_cli/commands/status.py +0 -0
  16. {fluxloop_cli-0.2.11 → fluxloop_cli-0.2.13}/fluxloop_cli/config_loader.py +0 -0
  17. {fluxloop_cli-0.2.11 → fluxloop_cli-0.2.13}/fluxloop_cli/config_schema.py +0 -0
  18. {fluxloop_cli-0.2.11 → fluxloop_cli-0.2.13}/fluxloop_cli/constants.py +0 -0
  19. {fluxloop_cli-0.2.11 → fluxloop_cli-0.2.13}/fluxloop_cli/input_generator.py +0 -0
  20. {fluxloop_cli-0.2.11 → fluxloop_cli-0.2.13}/fluxloop_cli/llm_generator.py +0 -0
  21. {fluxloop_cli-0.2.11 → fluxloop_cli-0.2.13}/fluxloop_cli/main.py +0 -0
  22. {fluxloop_cli-0.2.11 → fluxloop_cli-0.2.13}/fluxloop_cli/project_paths.py +0 -0
  23. {fluxloop_cli-0.2.11 → fluxloop_cli-0.2.13}/fluxloop_cli/target_loader.py +0 -0
  24. {fluxloop_cli-0.2.11 → fluxloop_cli-0.2.13}/fluxloop_cli/templates.py +0 -0
  25. {fluxloop_cli-0.2.11 → fluxloop_cli-0.2.13}/fluxloop_cli/validators.py +0 -0
  26. {fluxloop_cli-0.2.11 → fluxloop_cli-0.2.13}/fluxloop_cli.egg-info/SOURCES.txt +0 -0
  27. {fluxloop_cli-0.2.11 → fluxloop_cli-0.2.13}/fluxloop_cli.egg-info/dependency_links.txt +0 -0
  28. {fluxloop_cli-0.2.11 → fluxloop_cli-0.2.13}/fluxloop_cli.egg-info/entry_points.txt +0 -0
  29. {fluxloop_cli-0.2.11 → fluxloop_cli-0.2.13}/fluxloop_cli.egg-info/requires.txt +0 -0
  30. {fluxloop_cli-0.2.11 → fluxloop_cli-0.2.13}/fluxloop_cli.egg-info/top_level.txt +0 -0
  31. {fluxloop_cli-0.2.11 → fluxloop_cli-0.2.13}/setup.cfg +0 -0
  32. {fluxloop_cli-0.2.11 → fluxloop_cli-0.2.13}/tests/test_arg_binder.py +0 -0
  33. {fluxloop_cli-0.2.11 → fluxloop_cli-0.2.13}/tests/test_config_command.py +0 -0
  34. {fluxloop_cli-0.2.11 → fluxloop_cli-0.2.13}/tests/test_input_generator.py +0 -0
  35. {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.11
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/fluxloop/fluxloop
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/fluxloop/fluxloop
10
- Project-URL: Issues, https://github.com/fluxloop/fluxloop/issues
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
@@ -2,7 +2,7 @@
2
2
  FluxLoop CLI - Command-line interface for running agent simulations.
3
3
  """
4
4
 
5
- __version__ = "0.2.11"
5
+ __version__ = "0.2.13"
6
6
 
7
7
  from .main import app
8
8
 
@@ -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=not no_collector and bool(config.collector_url),
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
- result = await agent_func(**kwargs)
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
- result = await loop.run_in_executor(None, lambda: agent_func(**kwargs))
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
- async for item in agen:
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
- source_path = self.offline_dir / "observations.jsonl"
661
- if not source_path.exists():
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 source_path.open("r", encoding="utf-8") as src, destination.open(
668
- "w", encoding="utf-8"
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
- record = json.loads(line)
675
- except json.JSONDecodeError:
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.11
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/fluxloop/fluxloop
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/fluxloop/fluxloop
10
- Project-URL: Issues, https://github.com/fluxloop/fluxloop/issues
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.11"
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/fluxloop/fluxloop"
59
+ Homepage = "https://github.com/chuckgu/fluxloop"
60
60
  Documentation = "https://docs.fluxloop.dev"
61
- Repository = "https://github.com/fluxloop/fluxloop"
62
- Issues = "https://github.com/fluxloop/fluxloop/issues"
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