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.
- {goose_py-0.7.3 → goose_py-0.9.0}/PKG-INFO +1 -1
- {goose_py-0.7.3 → goose_py-0.9.0}/goose/__init__.py +1 -1
- {goose_py-0.7.3 → goose_py-0.9.0}/goose/_internal/flow.py +36 -23
- {goose_py-0.7.3 → goose_py-0.9.0}/goose/_internal/state.py +35 -33
- goose_py-0.9.0/goose/_internal/store.py +33 -0
- {goose_py-0.7.3 → goose_py-0.9.0}/goose/_internal/task.py +3 -3
- goose_py-0.9.0/goose/runs.py +4 -0
- {goose_py-0.7.3 → goose_py-0.9.0}/pyproject.toml +3 -2
- {goose_py-0.7.3 → goose_py-0.9.0}/tests/test_agent.py +11 -8
- goose_py-0.9.0/tests/test_complex_flow_arguments.py +21 -0
- {goose_py-0.7.3 → goose_py-0.9.0}/tests/test_downstream_task.py +10 -5
- {goose_py-0.7.3 → goose_py-0.9.0}/tests/test_looping.py +7 -3
- goose_py-0.7.3/tests/test_jamming.py → goose_py-0.9.0/tests/test_refining.py +12 -9
- {goose_py-0.7.3 → goose_py-0.9.0}/tests/test_regenerate.py +10 -5
- {goose_py-0.7.3 → goose_py-0.9.0}/tests/test_state.py +12 -8
- {goose_py-0.7.3 → goose_py-0.9.0}/uv.lock +1 -1
- goose_py-0.7.3/goose/_internal/store.py +0 -30
- goose_py-0.7.3/goose/runs.py +0 -4
- goose_py-0.7.3/tests/conftest.py +0 -10
- goose_py-0.7.3/tests/test_complex_flow_arguments.py +0 -22
- {goose_py-0.7.3 → goose_py-0.9.0}/.envrc +0 -0
- {goose_py-0.7.3 → goose_py-0.9.0}/.github/workflows/publish.yml +0 -0
- {goose_py-0.7.3 → goose_py-0.9.0}/.gitignore +0 -0
- {goose_py-0.7.3 → goose_py-0.9.0}/.python-version +0 -0
- {goose_py-0.7.3 → goose_py-0.9.0}/.stubs/jsonpath_ng/__init__.pyi +0 -0
- {goose_py-0.7.3 → goose_py-0.9.0}/.stubs/litellm/__init__.pyi +0 -0
- {goose_py-0.7.3 → goose_py-0.9.0}/Makefile +0 -0
- {goose_py-0.7.3 → goose_py-0.9.0}/README.md +0 -0
- {goose_py-0.7.3 → goose_py-0.9.0}/goose/_internal/agent.py +0 -0
- {goose_py-0.7.3 → goose_py-0.9.0}/goose/_internal/conversation.py +0 -0
- {goose_py-0.7.3 → goose_py-0.9.0}/goose/_internal/result.py +0 -0
- {goose_py-0.7.3 → goose_py-0.9.0}/goose/_internal/types/__init__.py +0 -0
- {goose_py-0.7.3 → goose_py-0.9.0}/goose/_internal/types/agent.py +0 -0
- {goose_py-0.7.3 → goose_py-0.9.0}/goose/agent.py +0 -0
- {goose_py-0.7.3 → goose_py-0.9.0}/goose/errors.py +0 -0
- {goose_py-0.7.3 → goose_py-0.9.0}/goose/flow.py +0 -0
- {goose_py-0.7.3 → goose_py-0.9.0}/goose/py.typed +0 -0
- {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.
|
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
|
-
from collections.abc import AsyncIterator,
|
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[
|
26
|
+
class Flow[FlowArgumentsT: FlowArguments]:
|
21
27
|
def __init__(
|
22
28
|
self,
|
23
|
-
fn:
|
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,
|
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.
|
70
|
-
await self._fn(
|
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
|
-
|
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[
|
95
|
+
def flow[FlowArgumentsT: FlowArguments](fn: IGenerator[FlowArgumentsT], /) -> Flow[FlowArgumentsT]: ...
|
83
96
|
@overload
|
84
|
-
def flow[
|
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[[
|
90
|
-
def flow[
|
91
|
-
fn:
|
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[
|
110
|
+
) -> Flow[FlowArgumentsT] | Callable[[IGenerator[FlowArgumentsT]], Flow[FlowArgumentsT]]:
|
98
111
|
if fn is None:
|
99
112
|
|
100
|
-
def decorator(fn:
|
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
|
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
|
-
|
22
|
-
class
|
23
|
-
|
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.
|
84
|
-
self.
|
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
|
102
|
-
if self.
|
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.
|
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
|
126
|
-
self.
|
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) ->
|
165
|
-
|
166
|
-
|
167
|
-
|
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,
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
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
|
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")
|
@@ -1,6 +1,6 @@
|
|
1
1
|
[project]
|
2
2
|
name = "goose-py"
|
3
|
-
version = "0.
|
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.
|
8
|
-
|
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(
|
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")
|
64
|
-
await agent_flow_with_custom_logger.generate(
|
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=
|
26
|
-
await duplicate_word(word=word.word, times=
|
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.
|
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
|
65
|
-
assert random_words[1].result.word
|
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[
|
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(*,
|
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=
|
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.
|
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)
|
@@ -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)
|
goose_py-0.7.3/goose/runs.py
DELETED
goose_py-0.7.3/tests/conftest.py
DELETED
@@ -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
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|