goose-py 0.7.3__tar.gz → 0.9.0__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 (38) hide show
  1. {goose_py-0.7.3 → goose_py-0.9.0}/PKG-INFO +1 -1
  2. {goose_py-0.7.3 → goose_py-0.9.0}/goose/__init__.py +1 -1
  3. {goose_py-0.7.3 → goose_py-0.9.0}/goose/_internal/flow.py +36 -23
  4. {goose_py-0.7.3 → goose_py-0.9.0}/goose/_internal/state.py +35 -33
  5. goose_py-0.9.0/goose/_internal/store.py +33 -0
  6. {goose_py-0.7.3 → goose_py-0.9.0}/goose/_internal/task.py +3 -3
  7. goose_py-0.9.0/goose/runs.py +4 -0
  8. {goose_py-0.7.3 → goose_py-0.9.0}/pyproject.toml +3 -2
  9. {goose_py-0.7.3 → goose_py-0.9.0}/tests/test_agent.py +11 -8
  10. goose_py-0.9.0/tests/test_complex_flow_arguments.py +21 -0
  11. {goose_py-0.7.3 → goose_py-0.9.0}/tests/test_downstream_task.py +10 -5
  12. {goose_py-0.7.3 → goose_py-0.9.0}/tests/test_looping.py +7 -3
  13. goose_py-0.7.3/tests/test_jamming.py → goose_py-0.9.0/tests/test_refining.py +12 -9
  14. {goose_py-0.7.3 → goose_py-0.9.0}/tests/test_regenerate.py +10 -5
  15. {goose_py-0.7.3 → goose_py-0.9.0}/tests/test_state.py +12 -8
  16. {goose_py-0.7.3 → goose_py-0.9.0}/uv.lock +1 -1
  17. goose_py-0.7.3/goose/_internal/store.py +0 -30
  18. goose_py-0.7.3/goose/runs.py +0 -4
  19. goose_py-0.7.3/tests/conftest.py +0 -10
  20. goose_py-0.7.3/tests/test_complex_flow_arguments.py +0 -22
  21. {goose_py-0.7.3 → goose_py-0.9.0}/.envrc +0 -0
  22. {goose_py-0.7.3 → goose_py-0.9.0}/.github/workflows/publish.yml +0 -0
  23. {goose_py-0.7.3 → goose_py-0.9.0}/.gitignore +0 -0
  24. {goose_py-0.7.3 → goose_py-0.9.0}/.python-version +0 -0
  25. {goose_py-0.7.3 → goose_py-0.9.0}/.stubs/jsonpath_ng/__init__.pyi +0 -0
  26. {goose_py-0.7.3 → goose_py-0.9.0}/.stubs/litellm/__init__.pyi +0 -0
  27. {goose_py-0.7.3 → goose_py-0.9.0}/Makefile +0 -0
  28. {goose_py-0.7.3 → goose_py-0.9.0}/README.md +0 -0
  29. {goose_py-0.7.3 → goose_py-0.9.0}/goose/_internal/agent.py +0 -0
  30. {goose_py-0.7.3 → goose_py-0.9.0}/goose/_internal/conversation.py +0 -0
  31. {goose_py-0.7.3 → goose_py-0.9.0}/goose/_internal/result.py +0 -0
  32. {goose_py-0.7.3 → goose_py-0.9.0}/goose/_internal/types/__init__.py +0 -0
  33. {goose_py-0.7.3 → goose_py-0.9.0}/goose/_internal/types/agent.py +0 -0
  34. {goose_py-0.7.3 → goose_py-0.9.0}/goose/agent.py +0 -0
  35. {goose_py-0.7.3 → goose_py-0.9.0}/goose/errors.py +0 -0
  36. {goose_py-0.7.3 → goose_py-0.9.0}/goose/flow.py +0 -0
  37. {goose_py-0.7.3 → goose_py-0.9.0}/goose/py.typed +0 -0
  38. {goose_py-0.7.3 → goose_py-0.9.0}/tests/__init__.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: goose-py
3
- Version: 0.7.3
3
+ Version: 0.9.0
4
4
  Summary: A tool for AI workflows based on human-computer collaboration and structured output.
5
5
  Author-email: Nash Taylor <nash@chelle.ai>, Joshua Cook <joshua@chelle.ai>, Michael Sankur <michael@chelle.ai>
6
6
  Requires-Python: >=3.12
@@ -1,4 +1,4 @@
1
1
  from goose._internal.agent import Agent
2
- from goose._internal.flow import flow
2
+ from goose._internal.flow import flow, FlowArguments
3
3
  from goose._internal.result import Result, TextResult
4
4
  from goose._internal.task import task
@@ -1,4 +1,4 @@
1
- from collections.abc import AsyncIterator, Awaitable, Callable
1
+ from collections.abc import AsyncIterator, Callable
2
2
  from contextlib import asynccontextmanager
3
3
  from types import CodeType
4
4
  from typing import Protocol, overload
@@ -6,48 +6,62 @@ from typing import Protocol, overload
6
6
  from goose._internal.agent import Agent, IAgentLogger
7
7
  from goose._internal.conversation import Conversation
8
8
  from goose._internal.result import Result
9
- from goose._internal.state import FlowRun, get_current_flow_run, set_current_flow_run
9
+ from goose._internal.state import FlowArguments, FlowRun, get_current_flow_run, set_current_flow_run
10
10
  from goose._internal.store import IFlowRunStore, InMemoryFlowRunStore
11
11
  from goose.errors import Honk
12
12
 
13
13
 
14
+ class IGenerator[FlowArgumentsT: FlowArguments](Protocol):
15
+ __name__: str
16
+
17
+ async def __call__(self, *, flow_arguments: FlowArgumentsT, agent: Agent) -> None: ...
18
+
19
+
14
20
  class IAdapter[ResultT: Result](Protocol):
15
21
  __code__: CodeType
16
22
 
17
23
  async def __call__(self, *, conversation: Conversation[ResultT], agent: Agent) -> ResultT: ...
18
24
 
19
25
 
20
- class Flow[**P]:
26
+ class Flow[FlowArgumentsT: FlowArguments]:
21
27
  def __init__(
22
28
  self,
23
- fn: Callable[P, Awaitable[None]],
29
+ fn: IGenerator[FlowArgumentsT],
24
30
  /,
25
31
  *,
26
32
  name: str | None = None,
27
- store: IFlowRunStore | None = None,
33
+ store: IFlowRunStore[FlowArgumentsT] | None = None,
28
34
  agent_logger: IAgentLogger | None = None,
29
35
  ) -> None:
30
36
  self._fn = fn
31
37
  self._name = name
32
38
  self._agent_logger = agent_logger
33
- self._store = store or InMemoryFlowRunStore(flow_name=self.name)
39
+ self._store = store or InMemoryFlowRunStore(flow_name=self.name, flow_arguments_model=self.flow_arguments_model)
40
+
41
+ @property
42
+ def flow_arguments_model(self) -> type[FlowArgumentsT]:
43
+ arguments_model = self._fn.__annotations__.get("flow_arguments")
44
+ if arguments_model is None:
45
+ raise Honk("Flow function has an invalid signature. Must accept `flow_arguments` and `agent` as arguments.")
46
+
47
+ return arguments_model
34
48
 
35
49
  @property
36
50
  def name(self) -> str:
37
51
  return self._name or self._fn.__name__
38
52
 
39
53
  @property
40
- def current_run(self) -> FlowRun:
54
+ def current_run(self) -> FlowRun[FlowArgumentsT]:
41
55
  run = get_current_flow_run()
42
56
  if run is None:
43
57
  raise Honk("No current flow run")
44
58
  return run
45
59
 
46
60
  @asynccontextmanager
47
- async def start_run(self, *, run_id: str) -> AsyncIterator[FlowRun]:
61
+ async def start_run(self, *, run_id: str) -> AsyncIterator[FlowRun[FlowArgumentsT]]:
48
62
  existing_run = await self._store.get(run_id=run_id)
49
63
  if existing_run is None:
50
- run = FlowRun()
64
+ run = FlowRun(flow_arguments_model=self.flow_arguments_model)
51
65
  else:
52
66
  run = existing_run
53
67
 
@@ -61,43 +75,42 @@ class Flow[**P]:
61
75
 
62
76
  set_current_flow_run(old_run)
63
77
 
64
- async def generate(self, *args: P.args, **kwargs: P.kwargs) -> None:
78
+ async def generate(self, flow_arguments: FlowArgumentsT, /) -> None:
65
79
  flow_run = get_current_flow_run()
66
80
  if flow_run is None:
67
81
  raise Honk("No current flow run")
68
82
 
69
- flow_run.set_flow_inputs(*args, **kwargs)
70
- await self._fn(*args, **kwargs)
83
+ flow_run.set_flow_arguments(flow_arguments)
84
+ await self._fn(flow_arguments=flow_arguments, agent=flow_run.agent)
71
85
 
72
86
  async def regenerate(self) -> None:
73
87
  flow_run = get_current_flow_run()
74
88
  if flow_run is None:
75
89
  raise Honk("No current flow run")
76
90
 
77
- flow_args, flow_kwargs = flow_run.flow_inputs
78
- await self._fn(*flow_args, **flow_kwargs)
91
+ await self._fn(flow_arguments=flow_run.flow_arguments, agent=flow_run.agent)
79
92
 
80
93
 
81
94
  @overload
82
- def flow[**P](fn: Callable[P, Awaitable[None]], /) -> Flow[P]: ...
95
+ def flow[FlowArgumentsT: FlowArguments](fn: IGenerator[FlowArgumentsT], /) -> Flow[FlowArgumentsT]: ...
83
96
  @overload
84
- def flow[**P](
97
+ def flow[FlowArgumentsT: FlowArguments](
85
98
  *,
86
99
  name: str | None = None,
87
- store: IFlowRunStore | None = None,
100
+ store: IFlowRunStore[FlowArgumentsT] | None = None,
88
101
  agent_logger: IAgentLogger | None = None,
89
- ) -> Callable[[Callable[P, Awaitable[None]]], Flow[P]]: ...
90
- def flow[**P](
91
- fn: Callable[P, Awaitable[None]] | None = None,
102
+ ) -> Callable[[IGenerator[FlowArgumentsT]], Flow[FlowArgumentsT]]: ...
103
+ def flow[FlowArgumentsT: FlowArguments](
104
+ fn: IGenerator[FlowArgumentsT] | None = None,
92
105
  /,
93
106
  *,
94
107
  name: str | None = None,
95
- store: IFlowRunStore | None = None,
108
+ store: IFlowRunStore[FlowArgumentsT] | None = None,
96
109
  agent_logger: IAgentLogger | None = None,
97
- ) -> Flow[P] | Callable[[Callable[P, Awaitable[None]]], Flow[P]]:
110
+ ) -> Flow[FlowArgumentsT] | Callable[[IGenerator[FlowArgumentsT]], Flow[FlowArgumentsT]]:
98
111
  if fn is None:
99
112
 
100
- def decorator(fn: Callable[P, Awaitable[None]]) -> Flow[P]:
113
+ def decorator(fn: IGenerator[FlowArgumentsT]) -> Flow[FlowArgumentsT]:
101
114
  return Flow(fn, name=name, store=store, agent_logger=agent_logger)
102
115
 
103
116
  return decorator
@@ -1,8 +1,8 @@
1
+ import json
1
2
  from contextvars import ContextVar
2
- from dataclasses import dataclass
3
- from typing import TYPE_CHECKING, Any, Self
3
+ from typing import TYPE_CHECKING, Any, NewType, Self
4
4
 
5
- from pydantic import BaseModel
5
+ from pydantic import BaseModel, ConfigDict
6
6
 
7
7
  from goose._internal.agent import (
8
8
  Agent,
@@ -17,12 +17,11 @@ from goose.errors import Honk
17
17
  if TYPE_CHECKING:
18
18
  from goose._internal.task import Task
19
19
 
20
+ SerializedFlowRun = NewType("SerializedFlowRun", str)
20
21
 
21
- @dataclass
22
- class FlowRunState:
23
- node_states: dict[tuple[str, int], str]
24
- flow_args: tuple[Any, ...]
25
- flow_kwargs: dict[str, Any]
22
+
23
+ class FlowArguments(BaseModel):
24
+ model_config = ConfigDict(frozen=True)
26
25
 
27
26
 
28
27
  class NodeState[ResultT: Result](BaseModel):
@@ -73,15 +72,15 @@ class NodeState[ResultT: Result](BaseModel):
73
72
  return self
74
73
 
75
74
 
76
- class FlowRun:
77
- def __init__(self) -> None:
75
+ class FlowRun[FlowArgumentsT: FlowArguments]:
76
+ def __init__(self, *, flow_arguments_model: type[FlowArgumentsT]) -> None:
78
77
  self._node_states: dict[tuple[str, int], str] = {}
79
78
  self._last_requested_indices: dict[str, int] = {}
80
79
  self._flow_name = ""
81
80
  self._id = ""
82
81
  self._agent: Agent | None = None
83
- self._flow_args: tuple[Any, ...] | None = None
84
- self._flow_kwargs: dict[str, Any] | None = None
82
+ self._flow_arguments: FlowArgumentsT | None = None
83
+ self._flow_arguments_model = flow_arguments_model
85
84
 
86
85
  @property
87
86
  def flow_name(self) -> str:
@@ -98,11 +97,11 @@ class FlowRun:
98
97
  return self._agent
99
98
 
100
99
  @property
101
- def flow_inputs(self) -> tuple[tuple[Any, ...], dict[str, Any]]:
102
- if self._flow_args is None or self._flow_kwargs is None:
100
+ def flow_arguments(self) -> FlowArgumentsT:
101
+ if self._flow_arguments is None:
103
102
  raise Honk("This Flow run has not been executed before")
104
103
 
105
- return self._flow_args, self._flow_kwargs
104
+ return self._flow_arguments
106
105
 
107
106
  def get_all[R: Result](self, *, task: "Task[Any, R]") -> list[NodeState[R]]:
108
107
  matching_nodes: list[NodeState[R]] = []
@@ -122,9 +121,8 @@ class FlowRun:
122
121
  last_hash=0,
123
122
  )
124
123
 
125
- def set_flow_inputs(self, *args: Any, **kwargs: Any) -> None:
126
- self._flow_args = args
127
- self._flow_kwargs = kwargs
124
+ def set_flow_arguments(self, flow_arguments: FlowArgumentsT, /) -> None:
125
+ self._flow_arguments = flow_arguments
128
126
 
129
127
  def upsert_node_state(self, node_state: NodeState[Any], /) -> None:
130
128
  key = (node_state.task_name, node_state.index)
@@ -161,31 +159,35 @@ class FlowRun:
161
159
  if key in self._node_states:
162
160
  del self._node_states[key]
163
161
 
164
- def dump(self) -> FlowRunState:
165
- flow_args, flow_kwargs = self.flow_inputs
166
-
167
- return FlowRunState(
168
- node_states=self._node_states,
169
- flow_args=flow_args,
170
- flow_kwargs=flow_kwargs,
162
+ def dump(self) -> SerializedFlowRun:
163
+ formatted_node_states = {f"{k[0]},{k[1]}": v for k, v in self._node_states.items()}
164
+ return SerializedFlowRun(
165
+ json.dumps({"node_states": formatted_node_states, "flow_arguments": self.flow_arguments.model_dump()})
171
166
  )
172
167
 
173
168
  @classmethod
174
- def load(cls, flow_run_state: FlowRunState, /) -> Self:
175
- flow_run = cls()
176
- flow_run._node_states = flow_run_state.node_states
177
- flow_run._flow_args = flow_run_state.flow_args
178
- flow_run._flow_kwargs = flow_run_state.flow_kwargs
169
+ def load(cls, *, serialized_flow_run: SerializedFlowRun, flow_arguments_model: type[FlowArgumentsT]) -> Self:
170
+ flow_run_state = json.loads(serialized_flow_run)
171
+ raw_node_states = flow_run_state["node_states"]
172
+ node_states: dict[tuple[str, int], str] = {}
173
+ for key, value in raw_node_states.items():
174
+ task_name, index = key.split(",")
175
+ node_states[(task_name, int(index))] = value
176
+ flow_arguments = flow_arguments_model.model_validate(flow_run_state["flow_arguments"])
177
+
178
+ flow_run = cls(flow_arguments_model=flow_arguments_model)
179
+ flow_run._node_states = node_states
180
+ flow_run._flow_arguments = flow_arguments
179
181
 
180
182
  return flow_run
181
183
 
182
184
 
183
- _current_flow_run: ContextVar[FlowRun | None] = ContextVar("current_flow_run", default=None)
185
+ _current_flow_run: ContextVar[FlowRun[Any] | None] = ContextVar("current_flow_run", default=None)
184
186
 
185
187
 
186
- def get_current_flow_run() -> FlowRun | None:
188
+ def get_current_flow_run() -> FlowRun[Any] | None:
187
189
  return _current_flow_run.get()
188
190
 
189
191
 
190
- def set_current_flow_run(flow_run: FlowRun | None) -> None:
192
+ def set_current_flow_run(flow_run: FlowRun[Any] | None) -> None:
191
193
  _current_flow_run.set(flow_run)
@@ -0,0 +1,33 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Protocol
4
+
5
+ from goose._internal.flow import FlowRun
6
+ from goose._internal.state import FlowArguments, SerializedFlowRun
7
+
8
+
9
+ class IFlowRunStore[FlowArgumentsT: FlowArguments](Protocol):
10
+ def __init__(self, *, flow_name: str) -> None: ...
11
+ async def get(self, *, run_id: str) -> FlowRun[FlowArgumentsT] | None: ...
12
+ async def save(self, *, run: FlowRun[FlowArgumentsT]) -> None: ...
13
+ async def delete(self, *, run_id: str) -> None: ...
14
+
15
+
16
+ class InMemoryFlowRunStore[FlowArgumentsT: FlowArguments](IFlowRunStore[FlowArgumentsT]):
17
+ def __init__(self, *, flow_name: str, flow_arguments_model: type[FlowArgumentsT]) -> None:
18
+ self._flow_name = flow_name
19
+ self._flow_arguments_model = flow_arguments_model
20
+ self._runs: dict[str, SerializedFlowRun] = {}
21
+
22
+ async def get(self, *, run_id: str) -> FlowRun[FlowArgumentsT] | None:
23
+ serialized_flow_run = self._runs.get(run_id)
24
+ if serialized_flow_run is not None:
25
+ return FlowRun.load(
26
+ serialized_flow_run=serialized_flow_run, flow_arguments_model=self._flow_arguments_model
27
+ )
28
+
29
+ async def save(self, *, run: FlowRun[FlowArgumentsT]) -> None:
30
+ self._runs[run.id] = run.dump()
31
+
32
+ async def delete(self, *, run_id: str) -> None:
33
+ self._runs.pop(run_id, None)
@@ -1,5 +1,5 @@
1
1
  from collections.abc import Awaitable, Callable
2
- from typing import overload
2
+ from typing import Any, overload
3
3
 
4
4
  from goose._internal.agent import Agent, GeminiModel, SystemMessage, UserMessage
5
5
  from goose._internal.conversation import Conversation
@@ -43,7 +43,7 @@ class Task[**P, R: Result]:
43
43
  else:
44
44
  return state.result
45
45
 
46
- async def jam(
46
+ async def refine(
47
47
  self,
48
48
  *,
49
49
  user_message: UserMessage,
@@ -111,7 +111,7 @@ class Task[**P, R: Result]:
111
111
  except TypeError:
112
112
  raise Honk(f"Unhashable argument to task {self.name}: {args} {kwargs}")
113
113
 
114
- def __get_current_flow_run(self) -> FlowRun:
114
+ def __get_current_flow_run(self) -> FlowRun[Any]:
115
115
  run = get_current_flow_run()
116
116
  if run is None:
117
117
  raise Honk("No current flow run")
@@ -0,0 +1,4 @@
1
+ from goose._internal.state import FlowRun, SerializedFlowRun
2
+ from goose._internal.store import IFlowRunStore
3
+
4
+ __all__ = ["FlowRun", "IFlowRunStore", "SerializedFlowRun"]
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "goose-py"
3
- version = "0.7.3"
3
+ version = "0.9.0"
4
4
  description = "A tool for AI workflows based on human-computer collaboration and structured output."
5
5
  readme = "README.md"
6
6
  authors = [
@@ -75,7 +75,8 @@ filterwarnings = [
75
75
  "ignore::SyntaxWarning",
76
76
  "ignore::UserWarning",
77
77
  ]
78
- addopts = "-v"
79
78
  testpaths = ["tests"]
80
79
  pythonpath = ["."]
81
80
  python_files = "test_*.py"
81
+ asyncio_default_fixture_loop_scope = "session"
82
+ asyncio_mode = "auto"
@@ -3,9 +3,12 @@ from unittest.mock import Mock
3
3
  import pytest
4
4
  from pytest_mock import MockerFixture
5
5
 
6
- from goose import TextResult, flow, task
7
- from goose._internal.agent import Agent, AgentResponse, IAgentLogger
8
- from goose.agent import GeminiModel, TextMessagePart, UserMessage
6
+ from goose import Agent, FlowArguments, TextResult, flow, task
7
+ from goose.agent import AgentResponse, GeminiModel, IAgentLogger, TextMessagePart, UserMessage
8
+
9
+
10
+ class TestFlowArguments(FlowArguments):
11
+ pass
9
12
 
10
13
 
11
14
  class MockLiteLLMResponse:
@@ -32,7 +35,7 @@ async def use_agent(*, agent: Agent) -> TextResult:
32
35
 
33
36
 
34
37
  @flow
35
- async def agent_flow(*, agent: Agent) -> None:
38
+ async def agent_flow(*, flow_arguments: TestFlowArguments, agent: Agent) -> None:
36
39
  await use_agent(agent=agent)
37
40
 
38
41
 
@@ -44,7 +47,7 @@ class CustomLogger(IAgentLogger):
44
47
 
45
48
 
46
49
  @flow(agent_logger=CustomLogger())
47
- async def agent_flow_with_custom_logger(*, agent: Agent) -> None:
50
+ async def agent_flow_with_custom_logger(*, flow_arguments: TestFlowArguments, agent: Agent) -> None:
48
51
  await use_agent(agent=agent)
49
52
 
50
53
 
@@ -52,7 +55,7 @@ async def agent_flow_with_custom_logger(*, agent: Agent) -> None:
52
55
  @pytest.mark.usefixtures("mock_litellm")
53
56
  async def test_agent() -> None:
54
57
  async with agent_flow.start_run(run_id="1") as run:
55
- await agent_flow.generate(agent=run.agent)
58
+ await agent_flow.generate(TestFlowArguments())
56
59
 
57
60
  assert run.get(task=use_agent).result.text == "Hello"
58
61
 
@@ -60,7 +63,7 @@ async def test_agent() -> None:
60
63
  @pytest.mark.asyncio
61
64
  @pytest.mark.usefixtures("mock_litellm")
62
65
  async def test_agent_custom_logger() -> None:
63
- async with agent_flow_with_custom_logger.start_run(run_id="1") as run:
64
- await agent_flow_with_custom_logger.generate(agent=run.agent)
66
+ async with agent_flow_with_custom_logger.start_run(run_id="1"):
67
+ await agent_flow_with_custom_logger.generate(TestFlowArguments())
65
68
 
66
69
  assert len(CustomLogger.logged_responses) == 1
@@ -0,0 +1,21 @@
1
+ import pytest
2
+
3
+ from goose import Agent, FlowArguments, flow
4
+
5
+
6
+ class MyFlowArguments(FlowArguments):
7
+ text: str
8
+
9
+
10
+ @flow
11
+ async def my_flow(*, flow_arguments: MyFlowArguments, agent: Agent) -> None:
12
+ pass
13
+
14
+
15
+ @pytest.mark.asyncio
16
+ async def test_my_flow() -> None:
17
+ async with my_flow.start_run(run_id="1"):
18
+ await my_flow.generate(MyFlowArguments(text="Hello"))
19
+
20
+ async with my_flow.start_run(run_id="1"):
21
+ await my_flow.generate(MyFlowArguments(text="Hello"))
@@ -3,7 +3,12 @@ import string
3
3
 
4
4
  import pytest
5
5
 
6
- from goose import Result, flow, task
6
+ from goose import Agent, FlowArguments, Result, flow, task
7
+
8
+
9
+ class MyFlowArguments(FlowArguments):
10
+ n_characters: int
11
+ n_duplicates: int
7
12
 
8
13
 
9
14
  class GeneratedWord(Result):
@@ -21,15 +26,15 @@ async def duplicate_word(*, word: str, times: int) -> GeneratedWord:
21
26
 
22
27
 
23
28
  @flow
24
- async def downstream_task() -> None:
25
- word = await generate_random_word(n_characters=10)
26
- await duplicate_word(word=word.word, times=10)
29
+ async def downstream_task(*, flow_arguments: MyFlowArguments, agent: Agent) -> None:
30
+ word = await generate_random_word(n_characters=flow_arguments.n_characters)
31
+ await duplicate_word(word=word.word, times=flow_arguments.n_duplicates)
27
32
 
28
33
 
29
34
  @pytest.mark.asyncio
30
35
  async def test_downstream_task() -> None:
31
36
  async with downstream_task.start_run(run_id="1") as run:
32
- await downstream_task.generate()
37
+ await downstream_task.generate(MyFlowArguments(n_characters=10, n_duplicates=10))
33
38
 
34
39
  duplicated_word = run.get(task=duplicate_word)
35
40
  assert len(duplicated_word.result.word) == 100
@@ -1,6 +1,10 @@
1
1
  import pytest
2
2
 
3
- from goose import Result, flow, task
3
+ from goose import Agent, FlowArguments, Result, flow, task
4
+
5
+
6
+ class MyFlowArguments(FlowArguments):
7
+ pass
4
8
 
5
9
 
6
10
  class CourseObjective(Result):
@@ -41,7 +45,7 @@ async def quiz_question(*, outcome: str) -> QuizQuestion:
41
45
 
42
46
 
43
47
  @flow
44
- async def course_content() -> None:
48
+ async def course_content(*, flow_arguments: MyFlowArguments, agent: Agent) -> None:
45
49
  objective = await course_objective()
46
50
  num_outcomes = await number_of_learning_outcomes()
47
51
  outcomes: list[LearningOutcome] = []
@@ -55,7 +59,7 @@ async def course_content() -> None:
55
59
  @pytest.mark.asyncio
56
60
  async def test_generate_course_content() -> None:
57
61
  async with course_content.start_run(run_id="1") as run:
58
- await course_content.generate()
62
+ await course_content.generate(MyFlowArguments())
59
63
 
60
64
  quiz_questions = run.get_all(task=quiz_question)
61
65
  assert quiz_questions[0].result.question == "What is the meaning of Learn Python?"
@@ -5,10 +5,14 @@ from unittest.mock import Mock
5
5
  import pytest
6
6
  from pytest_mock import MockerFixture
7
7
 
8
- from goose import Result, flow, task
8
+ from goose import Agent, FlowArguments, Result, flow, task
9
9
  from goose.agent import SystemMessage, TextMessagePart, UserMessage
10
10
 
11
11
 
12
+ class MyFlowArguments(FlowArguments):
13
+ pass
14
+
15
+
12
16
  class GeneratedWord(Result):
13
17
  word: str
14
18
 
@@ -37,7 +41,7 @@ async def make_sentence(*, words: list[GeneratedWord]) -> GeneratedSentence:
37
41
 
38
42
 
39
43
  @flow
40
- async def sentence() -> None:
44
+ async def sentence(*, flow_arguments: MyFlowArguments, agent: Agent) -> None:
41
45
  words = [await generate_random_word(n_characters=10) for _ in range(3)]
42
46
  await make_sentence(words=words)
43
47
 
@@ -46,31 +50,30 @@ async def sentence() -> None:
46
50
  @pytest.mark.usefixtures("generate_random_word_adapter")
47
51
  async def test_jamming() -> None:
48
52
  async with sentence.start_run(run_id="1") as first_run:
49
- await sentence.generate()
53
+ await sentence.generate(MyFlowArguments())
50
54
 
51
55
  initial_random_words = first_run.get_all(task=generate_random_word)
52
56
  assert len(initial_random_words) == 3
53
57
 
54
58
  # imagine this is a new process
55
59
  async with sentence.start_run(run_id="1") as second_run:
56
- await generate_random_word.jam(
57
- index=1,
60
+ await generate_random_word.refine(
58
61
  user_message=UserMessage(parts=[TextMessagePart(text="Change it")]),
59
62
  context=SystemMessage(parts=[TextMessagePart(text="Extra info")]),
60
63
  )
61
64
 
62
65
  random_words = second_run.get_all(task=generate_random_word)
63
66
  assert len(random_words) == 3
64
- assert random_words[0].result.word != "__ADAPTED__" # not adapted
65
- assert random_words[1].result.word == "__ADAPTED__" # adapted
67
+ assert random_words[0].result.word == "__ADAPTED__" # adapted
68
+ assert random_words[1].result.word != "__ADAPTED__" # not adapted
66
69
  assert random_words[2].result.word != "__ADAPTED__" # not adapted
67
70
 
68
71
  # imagine this is a new process
69
72
  async with sentence.start_run(run_id="1") as third_run:
70
- await sentence.generate()
73
+ await sentence.generate(MyFlowArguments())
71
74
 
72
75
  resulting_sentence = third_run.get(task=make_sentence)
73
76
  assert (
74
77
  resulting_sentence.result.sentence
75
- == f"{initial_random_words[0].result.word} __ADAPTED__ {initial_random_words[2].result.word}"
78
+ == f"__ADAPTED__ {initial_random_words[1].result.word} {initial_random_words[2].result.word}"
76
79
  )
@@ -3,10 +3,15 @@ import string
3
3
 
4
4
  import pytest
5
5
 
6
- from goose import Result, flow, task
6
+ from goose import Agent, FlowArguments, Result, flow, task
7
7
  from goose.errors import Honk
8
8
 
9
9
 
10
+ class MyFlowArguments(FlowArguments):
11
+ n_characters: int
12
+ times: int
13
+
14
+
10
15
  class GeneratedWord(Result):
11
16
  word: str
12
17
 
@@ -24,15 +29,15 @@ async def duplicate_word(*, word: str, times: int) -> GeneratedWord:
24
29
 
25
30
 
26
31
  @flow
27
- async def flow_with_arguments(*, n_characters: int, times: int) -> None:
28
- word = await generate_random_word(n_characters=n_characters)
29
- await duplicate_word(word=word.word, times=times)
32
+ async def flow_with_arguments(*, flow_arguments: MyFlowArguments, agent: Agent) -> None:
33
+ word = await generate_random_word(n_characters=flow_arguments.n_characters)
34
+ await duplicate_word(word=word.word, times=flow_arguments.times)
30
35
 
31
36
 
32
37
  @pytest.mark.asyncio
33
38
  async def test_flow_arguments_in_run() -> None:
34
39
  async with flow_with_arguments.start_run(run_id="1") as run:
35
- await flow_with_arguments.generate(n_characters=10, times=10)
40
+ await flow_with_arguments.generate(MyFlowArguments(n_characters=10, times=10))
36
41
 
37
42
  async with flow_with_arguments.start_run(run_id="1") as run:
38
43
  await flow_with_arguments.regenerate()
@@ -5,11 +5,15 @@ from unittest.mock import Mock
5
5
  import pytest
6
6
  from pytest_mock import MockerFixture
7
7
 
8
- from goose import Result, flow, task
8
+ from goose import Agent, FlowArguments, Result, flow, task
9
9
  from goose._internal.types.agent import SystemMessage, TextMessagePart, UserMessage
10
10
  from goose.errors import Honk
11
11
 
12
12
 
13
+ class MyFlowArguments(FlowArguments):
14
+ n_characters: int
15
+
16
+
13
17
  class GeneratedWord(Result):
14
18
  word: str
15
19
 
@@ -38,15 +42,15 @@ async def make_sentence(*, words: list[GeneratedWord]) -> GeneratedSentence:
38
42
 
39
43
 
40
44
  @flow
41
- async def with_state() -> None:
42
- word = await generate_random_word(n_characters=10)
45
+ async def with_state(*, flow_arguments: MyFlowArguments, agent: Agent) -> None:
46
+ word = await generate_random_word(n_characters=flow_arguments.n_characters)
43
47
  await make_sentence(words=[word])
44
48
 
45
49
 
46
50
  @pytest.mark.asyncio
47
51
  async def test_state_causes_caching() -> None:
48
52
  async with with_state.start_run(run_id="1") as run:
49
- await with_state.generate()
53
+ await with_state.generate(MyFlowArguments(n_characters=10))
50
54
 
51
55
  random_word = run.get(task=generate_random_word).result.word
52
56
 
@@ -54,7 +58,7 @@ async def test_state_causes_caching() -> None:
54
58
  with_state.current_run
55
59
 
56
60
  async with with_state.start_run(run_id="1") as new_run:
57
- await with_state.generate()
61
+ await with_state.generate(MyFlowArguments(n_characters=10))
58
62
 
59
63
  new_random_word = new_run.get(task=generate_random_word).result.word
60
64
 
@@ -65,10 +69,10 @@ async def test_state_causes_caching() -> None:
65
69
  @pytest.mark.usefixtures("generate_random_word_adapter")
66
70
  async def test_state_undo() -> None:
67
71
  async with with_state.start_run(run_id="2"):
68
- await with_state.generate()
72
+ await with_state.generate(MyFlowArguments(n_characters=10))
69
73
 
70
74
  async with with_state.start_run(run_id="2"):
71
- await generate_random_word.jam(
75
+ await generate_random_word.refine(
72
76
  index=0,
73
77
  user_message=UserMessage(parts=[TextMessagePart(text="Change it")]),
74
78
  context=SystemMessage(parts=[TextMessagePart(text="Extra info")]),
@@ -83,7 +87,7 @@ async def test_state_undo() -> None:
83
87
  @pytest.mark.asyncio
84
88
  async def test_state_edit() -> None:
85
89
  async with with_state.start_run(run_id="3"):
86
- await with_state.generate()
90
+ await with_state.generate(MyFlowArguments(n_characters=10))
87
91
 
88
92
  async with with_state.start_run(run_id="3") as run:
89
93
  generate_random_word.edit(result=GeneratedWord(word="__EDITED__"), index=0)
@@ -234,7 +234,7 @@ wheels = [
234
234
 
235
235
  [[package]]
236
236
  name = "goose-py"
237
- version = "0.7.3"
237
+ version = "0.9.0"
238
238
  source = { editable = "." }
239
239
  dependencies = [
240
240
  { name = "jsonpath-ng" },
@@ -1,30 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from typing import Protocol
4
-
5
- from goose._internal.flow import FlowRun
6
- from goose._internal.state import FlowRunState
7
-
8
-
9
- class IFlowRunStore(Protocol):
10
- def __init__(self, *, flow_name: str) -> None: ...
11
- async def get(self, *, run_id: str) -> FlowRun | None: ...
12
- async def save(self, *, run: FlowRun) -> None: ...
13
- async def delete(self, *, run_id: str) -> None: ...
14
-
15
-
16
- class InMemoryFlowRunStore(IFlowRunStore):
17
- def __init__(self, *, flow_name: str) -> None:
18
- self._flow_name = flow_name
19
- self._runs: dict[str, FlowRunState] = {}
20
-
21
- async def get(self, *, run_id: str) -> FlowRun | None:
22
- state = self._runs.get(run_id)
23
- if state is not None:
24
- return FlowRun.load(state)
25
-
26
- async def save(self, *, run: FlowRun) -> None:
27
- self._runs[run.id] = run.dump()
28
-
29
- async def delete(self, *, run_id: str) -> None:
30
- self._runs.pop(run_id, None)
@@ -1,4 +0,0 @@
1
- from goose._internal.state import FlowRun, FlowRunState
2
- from goose._internal.store import IFlowRunStore
3
-
4
- __all__ = ["FlowRun", "FlowRunState", "IFlowRunStore"]
@@ -1,10 +0,0 @@
1
- import asyncio
2
-
3
- import pytest
4
-
5
-
6
- @pytest.fixture(scope="session")
7
- def event_loop():
8
- loop = asyncio.get_event_loop()
9
- yield loop
10
- loop.close()
@@ -1,22 +0,0 @@
1
- import pytest
2
- from pydantic import BaseModel
3
-
4
- from goose import Agent, flow
5
-
6
-
7
- class MyMessage(BaseModel):
8
- text: str
9
-
10
-
11
- @flow
12
- async def my_flow(*, message: MyMessage, agent: Agent) -> None:
13
- pass
14
-
15
-
16
- @pytest.mark.asyncio
17
- async def test_my_flow() -> None:
18
- async with my_flow.start_run(run_id="1") as run:
19
- await my_flow.generate(message=MyMessage(text="Hello"), agent=run.agent)
20
-
21
- async with my_flow.start_run(run_id="1") as run:
22
- await my_flow.generate(message=MyMessage(text="Hello"), agent=run.agent)
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