goose-py 0.7.1__tar.gz → 0.7.3__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 (36) hide show
  1. {goose_py-0.7.1 → goose_py-0.7.3}/PKG-INFO +1 -1
  2. {goose_py-0.7.1 → goose_py-0.7.3}/goose/_internal/conversation.py +8 -6
  3. {goose_py-0.7.1 → goose_py-0.7.3}/goose/_internal/state.py +12 -1
  4. {goose_py-0.7.1 → goose_py-0.7.3}/goose/_internal/task.py +14 -2
  5. {goose_py-0.7.1 → goose_py-0.7.3}/pyproject.toml +1 -1
  6. goose_py-0.7.3/tests/test_state.py +91 -0
  7. {goose_py-0.7.1 → goose_py-0.7.3}/uv.lock +1 -1
  8. goose_py-0.7.1/tests/test_state.py +0 -49
  9. {goose_py-0.7.1 → goose_py-0.7.3}/.envrc +0 -0
  10. {goose_py-0.7.1 → goose_py-0.7.3}/.github/workflows/publish.yml +0 -0
  11. {goose_py-0.7.1 → goose_py-0.7.3}/.gitignore +0 -0
  12. {goose_py-0.7.1 → goose_py-0.7.3}/.python-version +0 -0
  13. {goose_py-0.7.1 → goose_py-0.7.3}/.stubs/jsonpath_ng/__init__.pyi +0 -0
  14. {goose_py-0.7.1 → goose_py-0.7.3}/.stubs/litellm/__init__.pyi +0 -0
  15. {goose_py-0.7.1 → goose_py-0.7.3}/Makefile +0 -0
  16. {goose_py-0.7.1 → goose_py-0.7.3}/README.md +0 -0
  17. {goose_py-0.7.1 → goose_py-0.7.3}/goose/__init__.py +0 -0
  18. {goose_py-0.7.1 → goose_py-0.7.3}/goose/_internal/agent.py +0 -0
  19. {goose_py-0.7.1 → goose_py-0.7.3}/goose/_internal/flow.py +0 -0
  20. {goose_py-0.7.1 → goose_py-0.7.3}/goose/_internal/result.py +0 -0
  21. {goose_py-0.7.1 → goose_py-0.7.3}/goose/_internal/store.py +0 -0
  22. {goose_py-0.7.1 → goose_py-0.7.3}/goose/_internal/types/__init__.py +0 -0
  23. {goose_py-0.7.1 → goose_py-0.7.3}/goose/_internal/types/agent.py +0 -0
  24. {goose_py-0.7.1 → goose_py-0.7.3}/goose/agent.py +0 -0
  25. {goose_py-0.7.1 → goose_py-0.7.3}/goose/errors.py +0 -0
  26. {goose_py-0.7.1 → goose_py-0.7.3}/goose/flow.py +0 -0
  27. {goose_py-0.7.1 → goose_py-0.7.3}/goose/py.typed +0 -0
  28. {goose_py-0.7.1 → goose_py-0.7.3}/goose/runs.py +0 -0
  29. {goose_py-0.7.1 → goose_py-0.7.3}/tests/__init__.py +0 -0
  30. {goose_py-0.7.1 → goose_py-0.7.3}/tests/conftest.py +0 -0
  31. {goose_py-0.7.1 → goose_py-0.7.3}/tests/test_agent.py +0 -0
  32. {goose_py-0.7.1 → goose_py-0.7.3}/tests/test_complex_flow_arguments.py +0 -0
  33. {goose_py-0.7.1 → goose_py-0.7.3}/tests/test_downstream_task.py +0 -0
  34. {goose_py-0.7.1 → goose_py-0.7.3}/tests/test_jamming.py +0 -0
  35. {goose_py-0.7.1 → goose_py-0.7.3}/tests/test_looping.py +0 -0
  36. {goose_py-0.7.1 → goose_py-0.7.3}/tests/test_regenerate.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: goose-py
3
- Version: 0.7.1
3
+ Version: 0.7.3
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,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):
@@ -31,3 +28,8 @@ class Conversation[R: Result](BaseModel):
31
28
  messages.append(AssistantMessage(text=self.result_messages[-1].model_dump_json()).render())
32
29
 
33
30
  return messages
31
+
32
+ def undo(self) -> Self:
33
+ self.user_messages.pop()
34
+ self.result_messages.pop()
35
+ return self
@@ -61,6 +61,17 @@ class NodeState[ResultT: Result](BaseModel):
61
61
  self.conversation.user_messages.append(message)
62
62
  return self
63
63
 
64
+ def edit_last_result(self, *, result: ResultT) -> Self:
65
+ if len(self.conversation.result_messages) == 0:
66
+ raise Honk("Node awaiting response, has no result")
67
+
68
+ self.conversation.result_messages[-1] = result
69
+ return self
70
+
71
+ def undo(self) -> Self:
72
+ self.conversation.undo()
73
+ return self
74
+
64
75
 
65
76
  class FlowRun:
66
77
  def __init__(self) -> None:
@@ -115,7 +126,7 @@ class FlowRun:
115
126
  self._flow_args = args
116
127
  self._flow_kwargs = kwargs
117
128
 
118
- def add_node_state(self, node_state: NodeState[Any], /) -> None:
129
+ def upsert_node_state(self, node_state: NodeState[Any], /) -> None:
119
130
  key = (node_state.task_name, node_state.index)
120
131
  self._node_states[key] = node_state.model_dump_json()
121
132
 
@@ -59,15 +59,27 @@ class Task[**P, R: Result]:
59
59
 
60
60
  result = await self.__adapt(conversation=node_state.conversation, agent=flow_run.agent)
61
61
  node_state.add_result(result=result)
62
- flow_run.add_node_state(node_state)
62
+ flow_run.upsert_node_state(node_state)
63
63
 
64
64
  return result
65
65
 
66
+ def edit(self, *, result: R, 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.edit_last_result(result=result)
70
+ flow_run.upsert_node_state(node_state)
71
+
72
+ def undo(self, *, index: int = 0) -> None:
73
+ flow_run = self.__get_current_flow_run()
74
+ node_state = flow_run.get(task=self, index=index)
75
+ node_state.undo()
76
+ flow_run.upsert_node_state(node_state)
77
+
66
78
  async def __call__(self, *args: P.args, **kwargs: P.kwargs) -> R:
67
79
  flow_run = self.__get_current_flow_run()
68
80
  node_state = flow_run.get_next(task=self)
69
81
  result = await self.generate(node_state, *args, **kwargs)
70
- flow_run.add_node_state(node_state)
82
+ flow_run.upsert_node_state(node_state)
71
83
  return result
72
84
 
73
85
  async def __adapt(self, *, conversation: Conversation[R], agent: Agent) -> R:
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "goose-py"
3
- version = "0.7.1"
3
+ version = "0.7.3"
4
4
  description = "A tool for AI workflows based on human-computer collaboration and structured output."
5
5
  readme = "README.md"
6
6
  authors = [
@@ -0,0 +1,91 @@
1
+ import random
2
+ import string
3
+ from unittest.mock import Mock
4
+
5
+ import pytest
6
+ from pytest_mock import MockerFixture
7
+
8
+ from goose import Result, flow, task
9
+ from goose._internal.types.agent import SystemMessage, TextMessagePart, UserMessage
10
+ from goose.errors import Honk
11
+
12
+
13
+ class GeneratedWord(Result):
14
+ word: str
15
+
16
+
17
+ class GeneratedSentence(Result):
18
+ sentence: str
19
+
20
+
21
+ @task
22
+ async def generate_random_word(*, n_characters: int) -> GeneratedWord:
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__"),
32
+ )
33
+
34
+
35
+ @task
36
+ async def make_sentence(*, words: list[GeneratedWord]) -> GeneratedSentence:
37
+ return GeneratedSentence(sentence=" ".join([word.word for word in words]))
38
+
39
+
40
+ @flow
41
+ async def with_state() -> None:
42
+ word = await generate_random_word(n_characters=10)
43
+ await make_sentence(words=[word])
44
+
45
+
46
+ @pytest.mark.asyncio
47
+ async def test_state_causes_caching() -> None:
48
+ async with with_state.start_run(run_id="1") as run:
49
+ await with_state.generate()
50
+
51
+ random_word = run.get(task=generate_random_word).result.word
52
+
53
+ with pytest.raises(Honk):
54
+ with_state.current_run
55
+
56
+ async with with_state.start_run(run_id="1") as new_run:
57
+ await with_state.generate()
58
+
59
+ new_random_word = new_run.get(task=generate_random_word).result.word
60
+
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__"
81
+
82
+
83
+ @pytest.mark.asyncio
84
+ async def test_state_edit() -> None:
85
+ async with with_state.start_run(run_id="3"):
86
+ await with_state.generate()
87
+
88
+ async with with_state.start_run(run_id="3") as run:
89
+ generate_random_word.edit(result=GeneratedWord(word="__EDITED__"), index=0)
90
+
91
+ assert run.get(task=generate_random_word).result.word == "__EDITED__"
@@ -234,7 +234,7 @@ wheels = [
234
234
 
235
235
  [[package]]
236
236
  name = "goose-py"
237
- version = "0.7.1"
237
+ version = "0.7.3"
238
238
  source = { editable = "." }
239
239
  dependencies = [
240
240
  { name = "jsonpath-ng" },
@@ -1,49 +0,0 @@
1
- import random
2
- import string
3
-
4
- import pytest
5
-
6
- from goose import Result, flow, task
7
- from goose.errors import Honk
8
-
9
-
10
- class GeneratedWord(Result):
11
- word: str
12
-
13
-
14
- class GeneratedSentence(Result):
15
- sentence: str
16
-
17
-
18
- @task
19
- async def generate_random_word(*, n_characters: int) -> GeneratedWord:
20
- return GeneratedWord(word="".join(random.sample(string.ascii_lowercase, n_characters)))
21
-
22
-
23
- @task
24
- async def make_sentence(*, words: list[GeneratedWord]) -> GeneratedSentence:
25
- return GeneratedSentence(sentence=" ".join([word.word for word in words]))
26
-
27
-
28
- @flow
29
- async def with_state() -> None:
30
- word = await generate_random_word(n_characters=10)
31
- await make_sentence(words=[word])
32
-
33
-
34
- @pytest.mark.asyncio
35
- async def test_state_causes_caching() -> None:
36
- async with with_state.start_run(run_id="1") as run:
37
- await with_state.generate()
38
-
39
- random_word = run.get(task=generate_random_word).result.word
40
-
41
- with pytest.raises(Honk):
42
- with_state.current_run
43
-
44
- async with with_state.start_run(run_id="1") as new_run:
45
- await with_state.generate()
46
-
47
- new_random_word = new_run.get(task=generate_random_word).result.word
48
-
49
- assert random_word == new_random_word # unchanged node is not re-generated
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