goose-py 0.7.0__tar.gz → 0.7.2__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.2/.github/workflows/publish.yml +48 -0
- goose_py-0.7.2/Makefile +9 -0
- {goose_py-0.7.0 → goose_py-0.7.2}/PKG-INFO +1 -1
- {goose_py-0.7.0 → goose_py-0.7.2}/goose/_internal/agent.py +7 -25
- {goose_py-0.7.0 → goose_py-0.7.2}/goose/_internal/conversation.py +10 -16
- {goose_py-0.7.0 → goose_py-0.7.2}/goose/_internal/flow.py +3 -4
- {goose_py-0.7.0 → goose_py-0.7.2}/goose/_internal/state.py +9 -15
- {goose_py-0.7.0 → goose_py-0.7.2}/goose/_internal/task.py +11 -12
- {goose_py-0.7.0 → goose_py-0.7.2}/pyproject.toml +3 -2
- {goose_py-0.7.0 → goose_py-0.7.2}/tests/conftest.py +1 -0
- {goose_py-0.7.0 → goose_py-0.7.2}/tests/test_agent.py +5 -11
- {goose_py-0.7.0 → goose_py-0.7.2}/tests/test_complex_flow_arguments.py +1 -2
- {goose_py-0.7.0 → goose_py-0.7.2}/tests/test_downstream_task.py +1 -3
- {goose_py-0.7.0 → goose_py-0.7.2}/tests/test_jamming.py +2 -4
- {goose_py-0.7.0 → goose_py-0.7.2}/tests/test_looping.py +2 -6
- {goose_py-0.7.0 → goose_py-0.7.2}/tests/test_state.py +31 -2
- {goose_py-0.7.0 → goose_py-0.7.2}/uv.lock +2 -2
- goose_py-0.7.0/.github/workflows/publish.yml +0 -75
- {goose_py-0.7.0 → goose_py-0.7.2}/.envrc +0 -0
- {goose_py-0.7.0 → goose_py-0.7.2}/.gitignore +0 -0
- {goose_py-0.7.0 → goose_py-0.7.2}/.python-version +0 -0
- {goose_py-0.7.0 → goose_py-0.7.2}/.stubs/jsonpath_ng/__init__.pyi +0 -0
- {goose_py-0.7.0 → goose_py-0.7.2}/.stubs/litellm/__init__.pyi +0 -0
- {goose_py-0.7.0 → goose_py-0.7.2}/README.md +0 -0
- {goose_py-0.7.0 → goose_py-0.7.2}/goose/__init__.py +0 -0
- {goose_py-0.7.0 → goose_py-0.7.2}/goose/_internal/result.py +0 -0
- {goose_py-0.7.0 → goose_py-0.7.2}/goose/_internal/store.py +0 -0
- {goose_py-0.7.0 → goose_py-0.7.2}/goose/_internal/types/__init__.py +0 -0
- {goose_py-0.7.0 → goose_py-0.7.2}/goose/_internal/types/agent.py +0 -0
- {goose_py-0.7.0 → goose_py-0.7.2}/goose/agent.py +0 -0
- {goose_py-0.7.0 → goose_py-0.7.2}/goose/errors.py +0 -0
- {goose_py-0.7.0 → goose_py-0.7.2}/goose/flow.py +0 -0
- {goose_py-0.7.0 → goose_py-0.7.2}/goose/py.typed +0 -0
- {goose_py-0.7.0 → goose_py-0.7.2}/goose/runs.py +0 -0
- {goose_py-0.7.0 → goose_py-0.7.2}/tests/__init__.py +0 -0
- {goose_py-0.7.0 → goose_py-0.7.2}/tests/test_regenerate.py +0 -0
@@ -0,0 +1,48 @@
|
|
1
|
+
name: CI/CD
|
2
|
+
|
3
|
+
on:
|
4
|
+
push:
|
5
|
+
branches:
|
6
|
+
- main
|
7
|
+
tags:
|
8
|
+
- "v*"
|
9
|
+
pull_request:
|
10
|
+
branches:
|
11
|
+
- main
|
12
|
+
|
13
|
+
jobs:
|
14
|
+
publish:
|
15
|
+
runs-on: ubuntu-latest
|
16
|
+
|
17
|
+
permissions:
|
18
|
+
id-token: write
|
19
|
+
|
20
|
+
steps:
|
21
|
+
- uses: actions/checkout@v4
|
22
|
+
with:
|
23
|
+
fetch-depth: 0
|
24
|
+
|
25
|
+
- name: Setup Python
|
26
|
+
uses: actions/setup-python@v5
|
27
|
+
with:
|
28
|
+
python-version-file: .python-version
|
29
|
+
|
30
|
+
- name: Setup UV
|
31
|
+
uses: astral-sh/setup-uv@v5
|
32
|
+
with:
|
33
|
+
version: "0.5.25"
|
34
|
+
|
35
|
+
- name: Initialize environment
|
36
|
+
run: uv sync --all-extras --dev
|
37
|
+
|
38
|
+
- name: Run tests
|
39
|
+
run: uv run pytest
|
40
|
+
env:
|
41
|
+
GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }}
|
42
|
+
|
43
|
+
- name: Build package
|
44
|
+
run: uv build
|
45
|
+
|
46
|
+
- name: Publish package
|
47
|
+
run: uv publish
|
48
|
+
continue-on-error: true
|
goose_py-0.7.2/Makefile
ADDED
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: goose-py
|
3
|
-
Version: 0.7.
|
3
|
+
Version: 0.7.2
|
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
|
@@ -65,20 +65,12 @@ class AgentResponse[R: BaseModel | str](BaseModel):
|
|
65
65
|
@computed_field
|
66
66
|
@property
|
67
67
|
def input_cost(self) -> float:
|
68
|
-
return
|
69
|
-
self.INPUT_CENTS_PER_MILLION_TOKENS[self.model]
|
70
|
-
* self.input_tokens
|
71
|
-
/ 1_000_000
|
72
|
-
)
|
68
|
+
return self.INPUT_CENTS_PER_MILLION_TOKENS[self.model] * self.input_tokens / 1_000_000
|
73
69
|
|
74
70
|
@computed_field
|
75
71
|
@property
|
76
72
|
def output_cost(self) -> float:
|
77
|
-
return
|
78
|
-
self.OUTPUT_CENTS_PER_MILLION_TOKENS[self.model]
|
79
|
-
* self.output_tokens
|
80
|
-
/ 1_000_000
|
81
|
-
)
|
73
|
+
return self.OUTPUT_CENTS_PER_MILLION_TOKENS[self.model] * self.output_tokens / 1_000_000
|
82
74
|
|
83
75
|
@computed_field
|
84
76
|
@property
|
@@ -100,15 +92,9 @@ class AgentResponse[R: BaseModel | str](BaseModel):
|
|
100
92
|
for part in message["content"]:
|
101
93
|
if part["type"] == "image_url":
|
102
94
|
part["image_url"] = "__MEDIA__"
|
103
|
-
minimized_input_messages = [
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
output_message = (
|
108
|
-
self.response.model_dump_json()
|
109
|
-
if isinstance(self.response, BaseModel)
|
110
|
-
else self.response
|
111
|
-
)
|
95
|
+
minimized_input_messages = [json.dumps(message) for message in minimized_input_messages]
|
96
|
+
|
97
|
+
output_message = self.response.model_dump_json() if isinstance(self.response, BaseModel) else self.response
|
112
98
|
|
113
99
|
return {
|
114
100
|
"run_id": self.run_id,
|
@@ -161,9 +147,7 @@ class Agent:
|
|
161
147
|
|
162
148
|
if response_model is TextResult:
|
163
149
|
response = await acompletion(model=model.value, messages=rendered_messages)
|
164
|
-
parsed_response = response_model.model_validate(
|
165
|
-
{"text": response.choices[0].message.content}
|
166
|
-
)
|
150
|
+
parsed_response = response_model.model_validate({"text": response.choices[0].message.content})
|
167
151
|
else:
|
168
152
|
response = await acompletion(
|
169
153
|
model=model.value,
|
@@ -174,9 +158,7 @@ class Agent:
|
|
174
158
|
"enforce_validation": True,
|
175
159
|
},
|
176
160
|
)
|
177
|
-
parsed_response = response_model.model_validate_json(
|
178
|
-
response.choices[0].message.content
|
179
|
-
)
|
161
|
+
parsed_response = response_model.model_validate_json(response.choices[0].message.content)
|
180
162
|
|
181
163
|
end_time = datetime.now()
|
182
164
|
agent_response = AgentResponse(
|
@@ -1,12 +1,9 @@
|
|
1
|
+
from typing import Self
|
2
|
+
|
1
3
|
from pydantic import BaseModel
|
2
4
|
|
3
5
|
from goose._internal.result import Result
|
4
|
-
from goose._internal.types.agent import
|
5
|
-
AssistantMessage,
|
6
|
-
LLMMessage,
|
7
|
-
SystemMessage,
|
8
|
-
UserMessage,
|
9
|
-
)
|
6
|
+
from goose._internal.types.agent import AssistantMessage, LLMMessage, SystemMessage, UserMessage
|
10
7
|
|
11
8
|
|
12
9
|
class Conversation[R: Result](BaseModel):
|
@@ -24,18 +21,15 @@ class Conversation[R: Result](BaseModel):
|
|
24
21
|
messages.append(self.context.render())
|
25
22
|
|
26
23
|
for message_index in range(len(self.user_messages)):
|
27
|
-
messages.append(
|
28
|
-
AssistantMessage(
|
29
|
-
text=self.result_messages[message_index].model_dump_json()
|
30
|
-
).render()
|
31
|
-
)
|
24
|
+
messages.append(AssistantMessage(text=self.result_messages[message_index].model_dump_json()).render())
|
32
25
|
messages.append(self.user_messages[message_index].render())
|
33
26
|
|
34
27
|
if len(self.result_messages) > len(self.user_messages):
|
35
|
-
messages.append(
|
36
|
-
AssistantMessage(
|
37
|
-
text=self.result_messages[-1].model_dump_json()
|
38
|
-
).render()
|
39
|
-
)
|
28
|
+
messages.append(AssistantMessage(text=self.result_messages[-1].model_dump_json()).render())
|
40
29
|
|
41
30
|
return messages
|
31
|
+
|
32
|
+
def undo(self) -> Self:
|
33
|
+
self.user_messages.pop()
|
34
|
+
self.result_messages.pop()
|
35
|
+
return self
|
@@ -1,6 +1,7 @@
|
|
1
|
+
from collections.abc import AsyncIterator, Awaitable, Callable
|
1
2
|
from contextlib import asynccontextmanager
|
2
3
|
from types import CodeType
|
3
|
-
from typing import
|
4
|
+
from typing import Protocol, overload
|
4
5
|
|
5
6
|
from goose._internal.agent import Agent, IAgentLogger
|
6
7
|
from goose._internal.conversation import Conversation
|
@@ -13,9 +14,7 @@ from goose.errors import Honk
|
|
13
14
|
class IAdapter[ResultT: Result](Protocol):
|
14
15
|
__code__: CodeType
|
15
16
|
|
16
|
-
async def __call__(
|
17
|
-
self, *, conversation: Conversation[ResultT], agent: Agent
|
18
|
-
) -> ResultT: ...
|
17
|
+
async def __call__(self, *, conversation: Conversation[ResultT], agent: Agent) -> ResultT: ...
|
19
18
|
|
20
19
|
|
21
20
|
class Flow[**P]:
|
@@ -61,6 +61,10 @@ class NodeState[ResultT: Result](BaseModel):
|
|
61
61
|
self.conversation.user_messages.append(message)
|
62
62
|
return self
|
63
63
|
|
64
|
+
def undo(self) -> Self:
|
65
|
+
self.conversation.undo()
|
66
|
+
return self
|
67
|
+
|
64
68
|
|
65
69
|
class FlowRun:
|
66
70
|
def __init__(self) -> None:
|
@@ -97,23 +101,17 @@ class FlowRun:
|
|
97
101
|
matching_nodes: list[NodeState[R]] = []
|
98
102
|
for key, node_state in self._node_states.items():
|
99
103
|
if key[0] == task.name:
|
100
|
-
matching_nodes.append(
|
101
|
-
NodeState[task.result_type].model_validate_json(node_state)
|
102
|
-
)
|
104
|
+
matching_nodes.append(NodeState[task.result_type].model_validate_json(node_state))
|
103
105
|
return sorted(matching_nodes, key=lambda node: node.index)
|
104
106
|
|
105
107
|
def get[R: Result](self, *, task: "Task[Any, R]", index: int = 0) -> NodeState[R]:
|
106
|
-
if (
|
107
|
-
existing_node_state := self._node_states.get((task.name, index))
|
108
|
-
) is not None:
|
108
|
+
if (existing_node_state := self._node_states.get((task.name, index))) is not None:
|
109
109
|
return NodeState[task.result_type].model_validate_json(existing_node_state)
|
110
110
|
else:
|
111
111
|
return NodeState[task.result_type](
|
112
112
|
task_name=task.name,
|
113
113
|
index=index,
|
114
|
-
conversation=Conversation[task.result_type](
|
115
|
-
user_messages=[], result_messages=[]
|
116
|
-
),
|
114
|
+
conversation=Conversation[task.result_type](user_messages=[], result_messages=[]),
|
117
115
|
last_hash=0,
|
118
116
|
)
|
119
117
|
|
@@ -143,9 +141,7 @@ class FlowRun:
|
|
143
141
|
self._last_requested_indices = {}
|
144
142
|
self._flow_name = flow_name
|
145
143
|
self._id = run_id
|
146
|
-
self._agent = Agent(
|
147
|
-
flow_name=self.flow_name, run_id=self.id, logger=agent_logger
|
148
|
-
)
|
144
|
+
self._agent = Agent(flow_name=self.flow_name, run_id=self.id, logger=agent_logger)
|
149
145
|
|
150
146
|
def end(self) -> None:
|
151
147
|
self._last_requested_indices = {}
|
@@ -177,9 +173,7 @@ class FlowRun:
|
|
177
173
|
return flow_run
|
178
174
|
|
179
175
|
|
180
|
-
_current_flow_run: ContextVar[FlowRun | None] = ContextVar(
|
181
|
-
"current_flow_run", default=None
|
182
|
-
)
|
176
|
+
_current_flow_run: ContextVar[FlowRun | None] = ContextVar("current_flow_run", default=None)
|
183
177
|
|
184
178
|
|
185
179
|
def get_current_flow_run() -> FlowRun | None:
|
@@ -1,4 +1,5 @@
|
|
1
|
-
from
|
1
|
+
from collections.abc import Awaitable, Callable
|
2
|
+
from typing import overload
|
2
3
|
|
3
4
|
from goose._internal.agent import Agent, GeminiModel, SystemMessage, UserMessage
|
4
5
|
from goose._internal.conversation import Conversation
|
@@ -33,9 +34,7 @@ class Task[**P, R: Result]:
|
|
33
34
|
def name(self) -> str:
|
34
35
|
return self._generator.__name__
|
35
36
|
|
36
|
-
async def generate(
|
37
|
-
self, state: NodeState[R], *args: P.args, **kwargs: P.kwargs
|
38
|
-
) -> R:
|
37
|
+
async def generate(self, state: NodeState[R], *args: P.args, **kwargs: P.kwargs) -> R:
|
39
38
|
state_hash = self.__hash_task_call(*args, **kwargs)
|
40
39
|
if state_hash != state.last_hash:
|
41
40
|
result = await self._generator(*args, **kwargs)
|
@@ -58,14 +57,18 @@ class Task[**P, R: Result]:
|
|
58
57
|
node_state.set_context(context=context)
|
59
58
|
node_state.add_user_message(message=user_message)
|
60
59
|
|
61
|
-
result = await self.__adapt(
|
62
|
-
conversation=node_state.conversation, agent=flow_run.agent
|
63
|
-
)
|
60
|
+
result = await self.__adapt(conversation=node_state.conversation, agent=flow_run.agent)
|
64
61
|
node_state.add_result(result=result)
|
65
62
|
flow_run.add_node_state(node_state)
|
66
63
|
|
67
64
|
return result
|
68
65
|
|
66
|
+
def undo(self, *, index: int = 0) -> None:
|
67
|
+
flow_run = self.__get_current_flow_run()
|
68
|
+
node_state = flow_run.get(task=self, index=index)
|
69
|
+
node_state.undo()
|
70
|
+
flow_run.add_node_state(node_state)
|
71
|
+
|
69
72
|
async def __call__(self, *args: P.args, **kwargs: P.kwargs) -> R:
|
70
73
|
flow_run = self.__get_current_flow_run()
|
71
74
|
node_state = flow_run.get_next(task=self)
|
@@ -97,11 +100,7 @@ class Task[**P, R: Result]:
|
|
97
100
|
|
98
101
|
def __hash_task_call(self, *args: P.args, **kwargs: P.kwargs) -> int:
|
99
102
|
try:
|
100
|
-
to_hash = str(
|
101
|
-
tuple(args)
|
102
|
-
+ tuple(kwargs.values())
|
103
|
-
+ (self._generator.__code__, self._adapter_model)
|
104
|
-
)
|
103
|
+
to_hash = str(tuple(args) + tuple(kwargs.values()) + (self._generator.__code__, self._adapter_model))
|
105
104
|
return hash(to_hash)
|
106
105
|
except TypeError:
|
107
106
|
raise Honk(f"Unhashable argument to task {self.name}: {args} {kwargs}")
|
@@ -1,6 +1,6 @@
|
|
1
1
|
[project]
|
2
2
|
name = "goose-py"
|
3
|
-
version = "0.7.
|
3
|
+
version = "0.7.2"
|
4
4
|
description = "A tool for AI workflows based on human-computer collaboration and structured output."
|
5
5
|
readme = "README.md"
|
6
6
|
authors = [
|
@@ -38,6 +38,7 @@ exclude = [
|
|
38
38
|
".venv",
|
39
39
|
"**/.venv",
|
40
40
|
"notebooks",
|
41
|
+
".stubs",
|
41
42
|
]
|
42
43
|
force-exclude = true
|
43
44
|
line-length = 120
|
@@ -61,12 +62,12 @@ pythonVersion = "3.12"
|
|
61
62
|
typeCheckingMode = "strict"
|
62
63
|
reportMissingModuleSource = false
|
63
64
|
useLibraryCodeForTypes = false
|
64
|
-
reportImportCycles = true
|
65
65
|
reportUnknownMemberType = false
|
66
66
|
reportUnknownVariableType = false
|
67
67
|
stubPath = ".stubs"
|
68
68
|
venvPath = "."
|
69
69
|
venv = ".venv"
|
70
|
+
exclude = ["goose/__init__.py", ".venv", "notebooks"]
|
70
71
|
|
71
72
|
[tool.pytest.ini_options]
|
72
73
|
filterwarnings = [
|
@@ -5,26 +5,20 @@ from pytest_mock import MockerFixture
|
|
5
5
|
|
6
6
|
from goose import TextResult, flow, task
|
7
7
|
from goose._internal.agent import Agent, AgentResponse, IAgentLogger
|
8
|
-
from goose.
|
8
|
+
from goose.agent import GeminiModel, TextMessagePart, UserMessage
|
9
9
|
|
10
10
|
|
11
11
|
class MockLiteLLMResponse:
|
12
|
-
def __init__(
|
13
|
-
self, *, response: str, prompt_tokens: int, completion_tokens: int
|
14
|
-
) -> None:
|
12
|
+
def __init__(self, *, response: str, prompt_tokens: int, completion_tokens: int) -> None:
|
15
13
|
self.choices = [Mock(message=Mock(content=response))]
|
16
|
-
self.usage = Mock(
|
17
|
-
prompt_tokens=prompt_tokens, completion_tokens=completion_tokens
|
18
|
-
)
|
14
|
+
self.usage = Mock(prompt_tokens=prompt_tokens, completion_tokens=completion_tokens)
|
19
15
|
|
20
16
|
|
21
17
|
@pytest.fixture
|
22
18
|
def mock_litellm(mocker: MockerFixture) -> Mock:
|
23
19
|
return mocker.patch(
|
24
|
-
"goose.
|
25
|
-
return_value=MockLiteLLMResponse(
|
26
|
-
response="Hello", prompt_tokens=10, completion_tokens=10
|
27
|
-
),
|
20
|
+
"goose._internal.agent.acompletion",
|
21
|
+
return_value=MockLiteLLMResponse(response="Hello", prompt_tokens=10, completion_tokens=10),
|
28
22
|
)
|
29
23
|
|
30
24
|
|
@@ -12,9 +12,7 @@ class GeneratedWord(Result):
|
|
12
12
|
|
13
13
|
@task
|
14
14
|
async def generate_random_word(*, n_characters: int) -> GeneratedWord:
|
15
|
-
return GeneratedWord(
|
16
|
-
word="".join(random.sample(string.ascii_lowercase, n_characters))
|
17
|
-
)
|
15
|
+
return GeneratedWord(word="".join(random.sample(string.ascii_lowercase, n_characters)))
|
18
16
|
|
19
17
|
|
20
18
|
@task
|
@@ -6,7 +6,7 @@ import pytest
|
|
6
6
|
from pytest_mock import MockerFixture
|
7
7
|
|
8
8
|
from goose import Result, flow, task
|
9
|
-
from goose.
|
9
|
+
from goose.agent import SystemMessage, TextMessagePart, UserMessage
|
10
10
|
|
11
11
|
|
12
12
|
class GeneratedWord(Result):
|
@@ -19,9 +19,7 @@ class GeneratedSentence(Result):
|
|
19
19
|
|
20
20
|
@task
|
21
21
|
async def generate_random_word(*, n_characters: int) -> GeneratedWord:
|
22
|
-
return GeneratedWord(
|
23
|
-
word="".join(random.sample(string.ascii_lowercase, n_characters))
|
24
|
-
)
|
22
|
+
return GeneratedWord(word="".join(random.sample(string.ascii_lowercase, n_characters)))
|
25
23
|
|
26
24
|
|
27
25
|
@pytest.fixture
|
@@ -31,9 +31,7 @@ async def number_of_learning_outcomes() -> NumberOfLearningOutcomes:
|
|
31
31
|
|
32
32
|
|
33
33
|
@task
|
34
|
-
async def learning_outcome(
|
35
|
-
*, objective: CourseObjective, previous_outcomes: list[LearningOutcome]
|
36
|
-
) -> LearningOutcome:
|
34
|
+
async def learning_outcome(*, objective: CourseObjective, previous_outcomes: list[LearningOutcome]) -> LearningOutcome:
|
37
35
|
return LearningOutcome(outcome="Learn Python")
|
38
36
|
|
39
37
|
|
@@ -48,9 +46,7 @@ async def course_content() -> None:
|
|
48
46
|
num_outcomes = await number_of_learning_outcomes()
|
49
47
|
outcomes: list[LearningOutcome] = []
|
50
48
|
for _ in range(num_outcomes.number):
|
51
|
-
outcomes.append(
|
52
|
-
await learning_outcome(objective=objective, previous_outcomes=outcomes)
|
53
|
-
)
|
49
|
+
outcomes.append(await learning_outcome(objective=objective, previous_outcomes=outcomes))
|
54
50
|
|
55
51
|
for outcome in outcomes:
|
56
52
|
await quiz_question(outcome=outcome.outcome)
|
@@ -1,9 +1,12 @@
|
|
1
1
|
import random
|
2
2
|
import string
|
3
|
+
from unittest.mock import Mock
|
3
4
|
|
4
5
|
import pytest
|
6
|
+
from pytest_mock import MockerFixture
|
5
7
|
|
6
8
|
from goose import Result, flow, task
|
9
|
+
from goose._internal.types.agent import SystemMessage, TextMessagePart, UserMessage
|
7
10
|
from goose.errors import Honk
|
8
11
|
|
9
12
|
|
@@ -17,8 +20,15 @@ class GeneratedSentence(Result):
|
|
17
20
|
|
18
21
|
@task
|
19
22
|
async def generate_random_word(*, n_characters: int) -> GeneratedWord:
|
20
|
-
return GeneratedWord(
|
21
|
-
|
23
|
+
return GeneratedWord(word="".join(random.sample(string.ascii_lowercase, n_characters)))
|
24
|
+
|
25
|
+
|
26
|
+
@pytest.fixture
|
27
|
+
def generate_random_word_adapter(mocker: MockerFixture) -> Mock:
|
28
|
+
return mocker.patch.object(
|
29
|
+
generate_random_word,
|
30
|
+
"_Task__adapt",
|
31
|
+
return_value=GeneratedWord(word="__ADAPTED__"),
|
22
32
|
)
|
23
33
|
|
24
34
|
|
@@ -49,3 +59,22 @@ async def test_state_causes_caching() -> None:
|
|
49
59
|
new_random_word = new_run.get(task=generate_random_word).result.word
|
50
60
|
|
51
61
|
assert random_word == new_random_word # unchanged node is not re-generated
|
62
|
+
|
63
|
+
|
64
|
+
@pytest.mark.asyncio
|
65
|
+
@pytest.mark.usefixtures("generate_random_word_adapter")
|
66
|
+
async def test_state_undo() -> None:
|
67
|
+
async with with_state.start_run(run_id="2"):
|
68
|
+
await with_state.generate()
|
69
|
+
|
70
|
+
async with with_state.start_run(run_id="2"):
|
71
|
+
await generate_random_word.jam(
|
72
|
+
index=0,
|
73
|
+
user_message=UserMessage(parts=[TextMessagePart(text="Change it")]),
|
74
|
+
context=SystemMessage(parts=[TextMessagePart(text="Extra info")]),
|
75
|
+
)
|
76
|
+
|
77
|
+
async with with_state.start_run(run_id="2") as run:
|
78
|
+
generate_random_word.undo()
|
79
|
+
|
80
|
+
assert run.get(task=generate_random_word).result.word != "__ADAPTED__"
|
@@ -1,75 +0,0 @@
|
|
1
|
-
name: CI/CD
|
2
|
-
|
3
|
-
on:
|
4
|
-
push:
|
5
|
-
branches:
|
6
|
-
- main
|
7
|
-
tags:
|
8
|
-
- "v*"
|
9
|
-
pull_request:
|
10
|
-
branches:
|
11
|
-
- main
|
12
|
-
|
13
|
-
jobs:
|
14
|
-
test:
|
15
|
-
runs-on: ubuntu-latest
|
16
|
-
|
17
|
-
steps:
|
18
|
-
- uses: actions/checkout@v4
|
19
|
-
with:
|
20
|
-
fetch-depth: 0
|
21
|
-
|
22
|
-
- name: Set up Python
|
23
|
-
uses: actions/setup-python@v4
|
24
|
-
with:
|
25
|
-
python-version: "3.12"
|
26
|
-
|
27
|
-
- name: Install Poetry
|
28
|
-
uses: snok/install-poetry@v1
|
29
|
-
with:
|
30
|
-
version: 1.8.5
|
31
|
-
virtualenvs-create: true
|
32
|
-
virtualenvs-in-project: true
|
33
|
-
|
34
|
-
- name: Install dependencies
|
35
|
-
run: poetry install --all-extras
|
36
|
-
|
37
|
-
- name: Run tests
|
38
|
-
run: poetry run pytest tests
|
39
|
-
env:
|
40
|
-
GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }}
|
41
|
-
|
42
|
-
publish:
|
43
|
-
needs: test
|
44
|
-
runs-on: ubuntu-latest
|
45
|
-
if: github.event_name == 'push' && (startsWith(github.ref, 'refs/tags/') || github.ref == 'refs/heads/main')
|
46
|
-
permissions:
|
47
|
-
id-token: write
|
48
|
-
contents: read
|
49
|
-
|
50
|
-
steps:
|
51
|
-
- uses: actions/checkout@v4
|
52
|
-
with:
|
53
|
-
fetch-depth: 0
|
54
|
-
|
55
|
-
- name: Set up Python
|
56
|
-
uses: actions/setup-python@v4
|
57
|
-
with:
|
58
|
-
python-version: "3.12"
|
59
|
-
|
60
|
-
- name: Install Poetry
|
61
|
-
uses: snok/install-poetry@v1
|
62
|
-
with:
|
63
|
-
version: 1.8.5
|
64
|
-
virtualenvs-create: true
|
65
|
-
virtualenvs-in-project: true
|
66
|
-
|
67
|
-
- name: Install dependencies
|
68
|
-
run: poetry install --all-extras
|
69
|
-
|
70
|
-
- name: Build package
|
71
|
-
run: poetry build
|
72
|
-
|
73
|
-
- name: Publish to PyPI
|
74
|
-
if: startsWith(github.ref, 'refs/tags/')
|
75
|
-
uses: pypa/gh-action-pypi-publish@release/v1
|
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
|