flowstate-sdk 0.1.11__tar.gz → 0.1.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 (26) hide show
  1. {flowstate_sdk-0.1.11/src/flowstate_sdk.egg-info → flowstate_sdk-0.1.13}/PKG-INFO +1 -1
  2. {flowstate_sdk-0.1.11 → flowstate_sdk-0.1.13}/pyproject.toml +1 -1
  3. {flowstate_sdk-0.1.11 → flowstate_sdk-0.1.13}/src/flowstate_sdk/context.py +3 -0
  4. flowstate_sdk-0.1.13/src/flowstate_sdk/langchain/__init__.py +15 -0
  5. flowstate_sdk-0.1.13/src/flowstate_sdk/langchain/callback_handler.py +3 -0
  6. flowstate_sdk-0.1.13/src/flowstate_sdk/langchain/chat_models.py +91 -0
  7. flowstate_sdk-0.1.13/src/flowstate_sdk/langchain/flowstate_chat_base.py +51 -0
  8. flowstate_sdk-0.1.11/src/flowstate_sdk/langchain/callback_handler.py → flowstate_sdk-0.1.13/src/flowstate_sdk/langchain/telemetry.py +48 -18
  9. {flowstate_sdk-0.1.11 → flowstate_sdk-0.1.13}/src/flowstate_sdk/workflow.py +7 -0
  10. {flowstate_sdk-0.1.11 → flowstate_sdk-0.1.13/src/flowstate_sdk.egg-info}/PKG-INFO +1 -1
  11. {flowstate_sdk-0.1.11 → flowstate_sdk-0.1.13}/src/flowstate_sdk.egg-info/SOURCES.txt +5 -1
  12. {flowstate_sdk-0.1.11 → flowstate_sdk-0.1.13}/LICENSE +0 -0
  13. {flowstate_sdk-0.1.11 → flowstate_sdk-0.1.13}/README.md +0 -0
  14. {flowstate_sdk-0.1.11 → flowstate_sdk-0.1.13}/setup.cfg +0 -0
  15. {flowstate_sdk-0.1.11 → flowstate_sdk-0.1.13}/src/flowstate_sdk/__init__.py +0 -0
  16. {flowstate_sdk-0.1.11 → flowstate_sdk-0.1.13}/src/flowstate_sdk/constants.py +0 -0
  17. {flowstate_sdk-0.1.11 → flowstate_sdk-0.1.13}/src/flowstate_sdk/cost_table.py +0 -0
  18. {flowstate_sdk-0.1.11 → flowstate_sdk-0.1.13}/src/flowstate_sdk/decorators.py +0 -0
  19. {flowstate_sdk-0.1.11 → flowstate_sdk-0.1.13}/src/flowstate_sdk/enums.py +0 -0
  20. {flowstate_sdk-0.1.11 → flowstate_sdk-0.1.13}/src/flowstate_sdk/sdk_client.py +0 -0
  21. {flowstate_sdk-0.1.11 → flowstate_sdk-0.1.13}/src/flowstate_sdk/shared_dataclasses.py +0 -0
  22. {flowstate_sdk-0.1.11 → flowstate_sdk-0.1.13}/src/flowstate_sdk/task.py +0 -0
  23. {flowstate_sdk-0.1.11 → flowstate_sdk-0.1.13}/src/flowstate_sdk/task_context.py +0 -0
  24. {flowstate_sdk-0.1.11 → flowstate_sdk-0.1.13}/src/flowstate_sdk.egg-info/dependency_links.txt +0 -0
  25. {flowstate_sdk-0.1.11 → flowstate_sdk-0.1.13}/src/flowstate_sdk.egg-info/requires.txt +0 -0
  26. {flowstate_sdk-0.1.11 → flowstate_sdk-0.1.13}/src/flowstate_sdk.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: flowstate_sdk
3
- Version: 0.1.11
3
+ Version: 0.1.13
4
4
  Summary: SDK for Agentic AI workflow management with Flowstate
5
5
  Author: Flowstate
6
6
  License: Nobody can use this
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "flowstate_sdk"
7
- version = "0.1.11"
7
+ version = "0.1.13"
8
8
  description = "SDK for Agentic AI workflow management with Flowstate"
9
9
  authors = [{name="Flowstate"}]
10
10
  requires-python = ">=3.9"
@@ -7,3 +7,6 @@ run_stack: ContextVar[List[str]] = ContextVar("run_stack", default=[])
7
7
  parent_children_mappings: ContextVar[Dict[str, Set[str]]] = ContextVar(
8
8
  "parent_children_mappings", default=defaultdict(set)
9
9
  )
10
+ is_replay: ContextVar[bool] = ContextVar("is_replay", default=False)
11
+ replay_counter: ContextVar[int] = ContextVar("replay_counter", default=0)
12
+ replay_step_list: ContextVar[List[str]] = ContextVar("replay_step_list", default=[])
@@ -0,0 +1,15 @@
1
+ from flowstate_sdk.langchain.callback_handler import FlowstateCallbackHandler
2
+ from flowstate_sdk.langchain.chat_models import (
3
+ FlowstateChatAnthropic,
4
+ FlowstateChatClaude,
5
+ FlowstateChatGoogle,
6
+ FlowstateChatOpenAI,
7
+ )
8
+
9
+ __all__ = [
10
+ "FlowstateCallbackHandler",
11
+ "FlowstateChatOpenAI",
12
+ "FlowstateChatAnthropic",
13
+ "FlowstateChatClaude",
14
+ "FlowstateChatGoogle",
15
+ ]
@@ -0,0 +1,3 @@
1
+ from flowstate_sdk.langchain.telemetry import FlowstateCallbackHandler
2
+
3
+ __all__ = ["FlowstateCallbackHandler"]
@@ -0,0 +1,91 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any
4
+
5
+ from flowstate_sdk.langchain.flowstate_chat_base import FlowstateChatModelMixin
6
+
7
+ try:
8
+ from langchain_openai import ChatOpenAI
9
+ except Exception: # pragma: no cover - optional dependency
10
+ ChatOpenAI = None
11
+
12
+ try:
13
+ from langchain_anthropic import ChatAnthropic
14
+ except Exception: # pragma: no cover - optional dependency
15
+ ChatAnthropic = None
16
+
17
+ try:
18
+ from langchain_google_genai import ChatGoogleGenerativeAI
19
+ except Exception: # pragma: no cover - optional dependency
20
+ ChatGoogleGenerativeAI = None
21
+
22
+
23
+ def _missing_dependency(name: str, package: str) -> None:
24
+ raise ImportError(
25
+ f"{name} requires optional dependency '{package}'. "
26
+ f"Install it to use this Flowstate wrapper."
27
+ )
28
+
29
+
30
+ if ChatOpenAI is not None:
31
+
32
+ class FlowstateChatOpenAI(FlowstateChatModelMixin, ChatOpenAI):
33
+ provider = "openai"
34
+
35
+ def __init__(self, *args: Any, **kwargs: Any) -> None:
36
+ kwargs = self._flowstate_init_kwargs(kwargs)
37
+ super().__init__(*args, **kwargs)
38
+
39
+ else:
40
+
41
+ class FlowstateChatOpenAI(FlowstateChatModelMixin): # type: ignore[misc]
42
+ provider = "openai"
43
+
44
+ def __init__(self, *args: Any, **kwargs: Any) -> None:
45
+ _missing_dependency("FlowstateChatOpenAI", "langchain-openai")
46
+
47
+
48
+ if ChatAnthropic is not None:
49
+
50
+ class FlowstateChatAnthropic(FlowstateChatModelMixin, ChatAnthropic):
51
+ provider = "anthropic"
52
+
53
+ def __init__(self, *args: Any, **kwargs: Any) -> None:
54
+ kwargs = self._flowstate_init_kwargs(kwargs)
55
+ super().__init__(*args, **kwargs)
56
+
57
+ else:
58
+
59
+ class FlowstateChatAnthropic(FlowstateChatModelMixin): # type: ignore[misc]
60
+ provider = "anthropic"
61
+
62
+ def __init__(self, *args: Any, **kwargs: Any) -> None:
63
+ _missing_dependency("FlowstateChatAnthropic", "langchain-anthropic")
64
+
65
+
66
+ if ChatGoogleGenerativeAI is not None:
67
+
68
+ class FlowstateChatGoogle(FlowstateChatModelMixin, ChatGoogleGenerativeAI):
69
+ provider = "google"
70
+
71
+ def __init__(self, *args: Any, **kwargs: Any) -> None:
72
+ kwargs = self._flowstate_init_kwargs(kwargs)
73
+ super().__init__(*args, **kwargs)
74
+
75
+ else:
76
+
77
+ class FlowstateChatGoogle(FlowstateChatModelMixin): # type: ignore[misc]
78
+ provider = "google"
79
+
80
+ def __init__(self, *args: Any, **kwargs: Any) -> None:
81
+ _missing_dependency("FlowstateChatGoogle", "langchain-google-genai")
82
+
83
+
84
+ FlowstateChatClaude = FlowstateChatAnthropic
85
+
86
+ __all__ = [
87
+ "FlowstateChatOpenAI",
88
+ "FlowstateChatAnthropic",
89
+ "FlowstateChatClaude",
90
+ "FlowstateChatGoogle",
91
+ ]
@@ -0,0 +1,51 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any, Dict, Iterable, List, Optional
4
+
5
+ from flowstate_sdk.langchain.telemetry import FlowstateCallbackHandler
6
+
7
+
8
+ def _extract_model_name(kwargs: Dict[str, Any]) -> Optional[str]:
9
+ for key in ("model", "model_name", "model_id"):
10
+ val = kwargs.get(key)
11
+ if isinstance(val, str) and val.strip():
12
+ return val
13
+ return None
14
+
15
+
16
+ def _normalize_callbacks(callbacks: Any) -> List[Any]:
17
+ if callbacks is None:
18
+ return []
19
+ if isinstance(callbacks, list):
20
+ return list(callbacks)
21
+ if isinstance(callbacks, tuple):
22
+ return list(callbacks)
23
+ return [callbacks]
24
+
25
+
26
+ def _has_flowstate_callback(callbacks: Iterable[Any]) -> bool:
27
+ for cb in callbacks:
28
+ if isinstance(cb, FlowstateCallbackHandler):
29
+ return True
30
+ return False
31
+
32
+
33
+ def _merge_callbacks(existing: Any, additional: List[Any]) -> List[Any]:
34
+ existing_list = _normalize_callbacks(existing)
35
+ if _has_flowstate_callback(existing_list):
36
+ return existing_list
37
+ return existing_list + list(additional)
38
+
39
+
40
+ class FlowstateChatModelMixin:
41
+ provider: str = ""
42
+
43
+ @classmethod
44
+ def _flowstate_build_callbacks(cls, kwargs: Dict[str, Any]) -> List[Any]:
45
+ model = _extract_model_name(kwargs) or "unknown"
46
+ handler = FlowstateCallbackHandler(cls.provider, model)
47
+ return _merge_callbacks(kwargs.pop("callbacks", None), [handler])
48
+
49
+ def _flowstate_init_kwargs(self, kwargs: Dict[str, Any]) -> Dict[str, Any]:
50
+ kwargs["callbacks"] = self._flowstate_build_callbacks(kwargs)
51
+ return kwargs
@@ -252,6 +252,31 @@ def _is_tool_call_message(message: Any, gen_info: Optional[Dict[str, Any]]) -> b
252
252
  return False
253
253
 
254
254
 
255
+ def _estimate_token_count(text: Optional[str], model: Optional[str]) -> Optional[int]:
256
+ if not text:
257
+ return 0
258
+ try:
259
+ import tiktoken # type: ignore
260
+ except Exception:
261
+ tiktoken = None
262
+
263
+ if tiktoken is not None:
264
+ try:
265
+ try:
266
+ enc = tiktoken.encoding_for_model(model or "")
267
+ except Exception:
268
+ enc = tiktoken.get_encoding("cl100k_base")
269
+ return len(enc.encode(text))
270
+ except Exception:
271
+ return None
272
+
273
+ if os.getenv("FLOWSTATE_APPROX_TOKENS"):
274
+ approx = (len(text) + 3) // 4
275
+ return approx if approx > 0 else 0
276
+
277
+ return None
278
+
279
+
255
280
  class FlowstateCallbackHandler(BaseCallbackHandler):
256
281
  def __init__(self, provider: str, model: str) -> None:
257
282
  self.provider = provider
@@ -341,6 +366,25 @@ class FlowstateCallbackHandler(BaseCallbackHandler):
341
366
  if finish_reason is None and _is_tool_call_message(ai_message_chunk, gen_info):
342
367
  return
343
368
 
369
+ llm_output = getattr(response, "llm_output", None)
370
+ resolved_model = self.model
371
+ if isinstance(llm_output, dict):
372
+ for key in ("model_name", "model", "model_id"):
373
+ val = llm_output.get(key)
374
+ if isinstance(val, str) and val.strip():
375
+ resolved_model = val
376
+ break
377
+ resolved_model = _normalize_model_name(resolved_model)
378
+
379
+ # Extract output text robustly (chat message content or plain .text)
380
+ output_text = ""
381
+ if response.generations and response.generations[0]:
382
+ generation = response.generations[0][0]
383
+ if getattr(generation, "message", None) is not None:
384
+ output_text = _content_to_text(generation.message.content)
385
+ else:
386
+ output_text = generation.text or ""
387
+
344
388
  usage_candidates: List[Dict[str, Any]] = []
345
389
  gen_response_metadata = getattr(generation_chunk, "response_metadata", None)
346
390
  if isinstance(gen_response_metadata, dict):
@@ -360,20 +404,15 @@ class FlowstateCallbackHandler(BaseCallbackHandler):
360
404
  usage_candidates.append(additional_kwargs)
361
405
  if isinstance(gen_info, dict) and gen_info:
362
406
  usage_candidates.append(gen_info)
363
- llm_output = getattr(response, "llm_output", None)
364
407
  if isinstance(llm_output, dict):
365
408
  usage_candidates.append(llm_output)
366
409
 
367
410
  input_tokens, output_tokens = _extract_token_usage(usage_candidates)
411
+ if input_tokens is None:
412
+ input_tokens = _estimate_token_count(self._input_str, resolved_model)
413
+ if output_tokens is None:
414
+ output_tokens = _estimate_token_count(output_text, resolved_model)
368
415
 
369
- resolved_model = self.model
370
- if isinstance(llm_output, dict):
371
- for key in ("model_name", "model", "model_id"):
372
- val = llm_output.get(key)
373
- if isinstance(val, str) and val.strip():
374
- resolved_model = val
375
- break
376
- resolved_model = _normalize_model_name(resolved_model)
377
416
  if os.getenv("FLOWSTATE_DEBUG_LLM_USAGE"):
378
417
  try:
379
418
  import json
@@ -410,15 +449,6 @@ class FlowstateCallbackHandler(BaseCallbackHandler):
410
449
  if cost_value != 0.0:
411
450
  cost_usd = cost_value
412
451
 
413
- # Extract output text robustly (chat message content or plain .text)
414
- output_text = ""
415
- if response.generations and response.generations[0]:
416
- generation = response.generations[0][0]
417
- if getattr(generation, "message", None) is not None:
418
- output_text = _content_to_text(generation.message.content)
419
- else:
420
- output_text = generation.text or ""
421
-
422
452
  provider_metrics = ProviderMetrics(
423
453
  run_id=context.current_run.get(),
424
454
  provider=self.provider,
@@ -1,6 +1,7 @@
1
1
  import uuid
2
2
  from collections import defaultdict
3
3
  from datetime import datetime
4
+ import os
4
5
 
5
6
  from . import context
6
7
  from .sdk_client import SDKClient
@@ -40,6 +41,12 @@ class Workflow:
40
41
  }
41
42
  self.client.emit(workflow_payload)
42
43
 
44
+ if os.getenv("IS_FLOWSTATE_REPLAY"):
45
+ replay_list = self.client.retrieve_replay_plan(os.getenv("FLOWSTATE_REPLAY_PARENT_ID"))
46
+ context.replay_counter.set(0)
47
+ context.replay_step_list.set(replay_list)
48
+ context.is_replay.set(True)
49
+
43
50
  run_payload = RunRecord(
44
51
  run_id=str(self.run_id),
45
52
  workflow_id=str(self.workflow_id),
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: flowstate_sdk
3
- Version: 0.1.11
3
+ Version: 0.1.13
4
4
  Summary: SDK for Agentic AI workflow management with Flowstate
5
5
  Author: Flowstate
6
6
  License: Nobody can use this
@@ -17,4 +17,8 @@ src/flowstate_sdk.egg-info/SOURCES.txt
17
17
  src/flowstate_sdk.egg-info/dependency_links.txt
18
18
  src/flowstate_sdk.egg-info/requires.txt
19
19
  src/flowstate_sdk.egg-info/top_level.txt
20
- src/flowstate_sdk/langchain/callback_handler.py
20
+ src/flowstate_sdk/langchain/__init__.py
21
+ src/flowstate_sdk/langchain/callback_handler.py
22
+ src/flowstate_sdk/langchain/chat_models.py
23
+ src/flowstate_sdk/langchain/flowstate_chat_base.py
24
+ src/flowstate_sdk/langchain/telemetry.py
File without changes
File without changes
File without changes