fluxloop-cli 0.2.13__tar.gz → 0.2.14__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.13 → fluxloop_cli-0.2.14}/PKG-INFO +1 -1
- {fluxloop_cli-0.2.13 → fluxloop_cli-0.2.14}/fluxloop_cli/__init__.py +1 -1
- {fluxloop_cli-0.2.13 → fluxloop_cli-0.2.14}/fluxloop_cli/runner.py +76 -13
- {fluxloop_cli-0.2.13 → fluxloop_cli-0.2.14}/fluxloop_cli.egg-info/PKG-INFO +1 -1
- {fluxloop_cli-0.2.13 → fluxloop_cli-0.2.14}/pyproject.toml +1 -1
- {fluxloop_cli-0.2.13 → fluxloop_cli-0.2.14}/README.md +0 -0
- {fluxloop_cli-0.2.13 → fluxloop_cli-0.2.14}/fluxloop_cli/arg_binder.py +0 -0
- {fluxloop_cli-0.2.13 → fluxloop_cli-0.2.14}/fluxloop_cli/commands/__init__.py +0 -0
- {fluxloop_cli-0.2.13 → fluxloop_cli-0.2.14}/fluxloop_cli/commands/config.py +0 -0
- {fluxloop_cli-0.2.13 → fluxloop_cli-0.2.14}/fluxloop_cli/commands/generate.py +0 -0
- {fluxloop_cli-0.2.13 → fluxloop_cli-0.2.14}/fluxloop_cli/commands/init.py +0 -0
- {fluxloop_cli-0.2.13 → fluxloop_cli-0.2.14}/fluxloop_cli/commands/parse.py +0 -0
- {fluxloop_cli-0.2.13 → fluxloop_cli-0.2.14}/fluxloop_cli/commands/record.py +0 -0
- {fluxloop_cli-0.2.13 → fluxloop_cli-0.2.14}/fluxloop_cli/commands/run.py +0 -0
- {fluxloop_cli-0.2.13 → fluxloop_cli-0.2.14}/fluxloop_cli/commands/status.py +0 -0
- {fluxloop_cli-0.2.13 → fluxloop_cli-0.2.14}/fluxloop_cli/config_loader.py +0 -0
- {fluxloop_cli-0.2.13 → fluxloop_cli-0.2.14}/fluxloop_cli/config_schema.py +0 -0
- {fluxloop_cli-0.2.13 → fluxloop_cli-0.2.14}/fluxloop_cli/constants.py +0 -0
- {fluxloop_cli-0.2.13 → fluxloop_cli-0.2.14}/fluxloop_cli/input_generator.py +0 -0
- {fluxloop_cli-0.2.13 → fluxloop_cli-0.2.14}/fluxloop_cli/llm_generator.py +0 -0
- {fluxloop_cli-0.2.13 → fluxloop_cli-0.2.14}/fluxloop_cli/main.py +0 -0
- {fluxloop_cli-0.2.13 → fluxloop_cli-0.2.14}/fluxloop_cli/project_paths.py +0 -0
- {fluxloop_cli-0.2.13 → fluxloop_cli-0.2.14}/fluxloop_cli/target_loader.py +0 -0
- {fluxloop_cli-0.2.13 → fluxloop_cli-0.2.14}/fluxloop_cli/templates.py +0 -0
- {fluxloop_cli-0.2.13 → fluxloop_cli-0.2.14}/fluxloop_cli/validators.py +0 -0
- {fluxloop_cli-0.2.13 → fluxloop_cli-0.2.14}/fluxloop_cli.egg-info/SOURCES.txt +0 -0
- {fluxloop_cli-0.2.13 → fluxloop_cli-0.2.14}/fluxloop_cli.egg-info/dependency_links.txt +0 -0
- {fluxloop_cli-0.2.13 → fluxloop_cli-0.2.14}/fluxloop_cli.egg-info/entry_points.txt +0 -0
- {fluxloop_cli-0.2.13 → fluxloop_cli-0.2.14}/fluxloop_cli.egg-info/requires.txt +0 -0
- {fluxloop_cli-0.2.13 → fluxloop_cli-0.2.14}/fluxloop_cli.egg-info/top_level.txt +0 -0
- {fluxloop_cli-0.2.13 → fluxloop_cli-0.2.14}/setup.cfg +0 -0
- {fluxloop_cli-0.2.13 → fluxloop_cli-0.2.14}/tests/test_arg_binder.py +0 -0
- {fluxloop_cli-0.2.13 → fluxloop_cli-0.2.14}/tests/test_config_command.py +0 -0
- {fluxloop_cli-0.2.13 → fluxloop_cli-0.2.14}/tests/test_input_generator.py +0 -0
- {fluxloop_cli-0.2.13 → fluxloop_cli-0.2.14}/tests/test_target_loader.py +0 -0
|
@@ -512,11 +512,15 @@ class ExperimentRunner:
|
|
|
512
512
|
"""Derive the final output from callbacks or observations."""
|
|
513
513
|
for observation in reversed(observations):
|
|
514
514
|
if observation.get("name") == "agent_final_response" and observation.get("output") is not None:
|
|
515
|
-
|
|
515
|
+
val = observation.get("output")
|
|
516
|
+
if not ExperimentRunner._looks_like_generator_repr(val):
|
|
517
|
+
return val
|
|
516
518
|
|
|
517
519
|
for observation in reversed(observations):
|
|
518
520
|
if observation.get("type") == "agent" and observation.get("output") is not None:
|
|
519
|
-
|
|
521
|
+
val = observation.get("output")
|
|
522
|
+
if not ExperimentRunner._looks_like_generator_repr(val):
|
|
523
|
+
return val
|
|
520
524
|
|
|
521
525
|
send_messages = callback_messages.get("send") if callback_messages else None
|
|
522
526
|
if send_messages:
|
|
@@ -525,6 +529,13 @@ class ExperimentRunner:
|
|
|
525
529
|
|
|
526
530
|
return None
|
|
527
531
|
|
|
532
|
+
@staticmethod
|
|
533
|
+
def _looks_like_generator_repr(value: Any) -> bool:
|
|
534
|
+
if not isinstance(value, str):
|
|
535
|
+
return False
|
|
536
|
+
s = value.strip().lower()
|
|
537
|
+
return s.startswith("<async_generator ") or s.startswith("<generator ") or " object at 0x" in s
|
|
538
|
+
|
|
528
539
|
async def _consume_async_gen(self, func: Callable, kwargs: Dict[str, Any]) -> Any:
|
|
529
540
|
"""Consume an async generator function by joining text chunks resolved from events."""
|
|
530
541
|
gen = func(**kwargs)
|
|
@@ -536,7 +547,8 @@ class ExperimentRunner:
|
|
|
536
547
|
Uses a copied contextvars context to ensure FluxLoop context is preserved
|
|
537
548
|
across async iteration boundaries created by upstream frameworks.
|
|
538
549
|
"""
|
|
539
|
-
path
|
|
550
|
+
# Prefer ChatKit-like path by default; configurable via runner.stream_output_path
|
|
551
|
+
path = (getattr(self.config.runner, "stream_output_path", None) or "update.delta").split(".")
|
|
540
552
|
chunks: List[str] = []
|
|
541
553
|
|
|
542
554
|
ctx = contextvars.copy_context()
|
|
@@ -599,7 +611,58 @@ class ExperimentRunner:
|
|
|
599
611
|
text_attr = getattr(event, "text", None)
|
|
600
612
|
if isinstance(text_attr, str) and text_attr:
|
|
601
613
|
return text_attr
|
|
614
|
+
# Final deep fallback: search common fields recursively
|
|
615
|
+
return ExperimentRunner._deep_extract_text(event)
|
|
602
616
|
|
|
617
|
+
@staticmethod
|
|
618
|
+
def _deep_extract_text(obj: Any, *, _depth: int = 0) -> Optional[str]:
|
|
619
|
+
if _depth > 3 or obj is None:
|
|
620
|
+
return None
|
|
621
|
+
if isinstance(obj, str):
|
|
622
|
+
return obj if obj else None
|
|
623
|
+
# dict-like
|
|
624
|
+
if isinstance(obj, dict):
|
|
625
|
+
for key in ("delta", "text"):
|
|
626
|
+
val = obj.get(key)
|
|
627
|
+
if isinstance(val, str) and val:
|
|
628
|
+
return val
|
|
629
|
+
# content as list of parts with text
|
|
630
|
+
content = obj.get("content")
|
|
631
|
+
if isinstance(content, list):
|
|
632
|
+
parts: List[str] = []
|
|
633
|
+
for piece in content:
|
|
634
|
+
txt = ExperimentRunner._deep_extract_text(piece, _depth=_depth + 1)
|
|
635
|
+
if txt:
|
|
636
|
+
parts.append(txt)
|
|
637
|
+
if parts:
|
|
638
|
+
return " ".join(parts)
|
|
639
|
+
# Recurse selected fields
|
|
640
|
+
for key in ("update", "message", "item", "data"):
|
|
641
|
+
val = obj.get(key)
|
|
642
|
+
txt = ExperimentRunner._deep_extract_text(val, _depth=_depth + 1)
|
|
643
|
+
if txt:
|
|
644
|
+
return txt
|
|
645
|
+
return None
|
|
646
|
+
# object with attributes
|
|
647
|
+
for attr in ("delta", "text"):
|
|
648
|
+
val = getattr(obj, attr, None)
|
|
649
|
+
if isinstance(val, str) and val:
|
|
650
|
+
return val
|
|
651
|
+
for attr in ("content",):
|
|
652
|
+
val = getattr(obj, attr, None)
|
|
653
|
+
if isinstance(val, list):
|
|
654
|
+
parts: List[str] = []
|
|
655
|
+
for piece in val:
|
|
656
|
+
txt = ExperimentRunner._deep_extract_text(piece, _depth=_depth + 1)
|
|
657
|
+
if txt:
|
|
658
|
+
parts.append(txt)
|
|
659
|
+
if parts:
|
|
660
|
+
return " ".join(parts)
|
|
661
|
+
for attr in ("update", "message", "item", "data"):
|
|
662
|
+
val = getattr(obj, attr, None)
|
|
663
|
+
txt = ExperimentRunner._deep_extract_text(val, _depth=_depth + 1)
|
|
664
|
+
if txt:
|
|
665
|
+
return txt
|
|
603
666
|
return None
|
|
604
667
|
|
|
605
668
|
@staticmethod
|
|
@@ -754,20 +817,20 @@ class ExperimentRunner:
|
|
|
754
817
|
for source_path in existing:
|
|
755
818
|
try:
|
|
756
819
|
with source_path.open("r", encoding="utf-8") as src:
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
820
|
+
for line in src:
|
|
821
|
+
if not line.strip():
|
|
822
|
+
continue
|
|
823
|
+
try:
|
|
824
|
+
record = json.loads(line)
|
|
825
|
+
except json.JSONDecodeError:
|
|
826
|
+
continue
|
|
827
|
+
if record.get("trace_id") in trace_ids:
|
|
765
828
|
key = (record.get("id"), record.get("start_time"))
|
|
766
829
|
if key in seen_lines:
|
|
767
830
|
continue
|
|
768
|
-
|
|
831
|
+
dst.write(json.dumps(record) + "\n")
|
|
769
832
|
seen_lines.add(key)
|
|
770
|
-
|
|
833
|
+
copied += 1
|
|
771
834
|
except OSError:
|
|
772
835
|
continue
|
|
773
836
|
|
|
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
|