goose-py 0.7.0__tar.gz → 0.7.1__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/.github/workflows/publish.yml +48 -0
  2. goose_py-0.7.1/Makefile +9 -0
  3. {goose_py-0.7.0 → goose_py-0.7.1}/PKG-INFO +1 -1
  4. {goose_py-0.7.0 → goose_py-0.7.1}/goose/_internal/agent.py +7 -25
  5. {goose_py-0.7.0 → goose_py-0.7.1}/goose/_internal/conversation.py +2 -10
  6. {goose_py-0.7.0 → goose_py-0.7.1}/goose/_internal/flow.py +3 -4
  7. {goose_py-0.7.0 → goose_py-0.7.1}/goose/_internal/state.py +5 -15
  8. {goose_py-0.7.0 → goose_py-0.7.1}/goose/_internal/task.py +5 -12
  9. {goose_py-0.7.0 → goose_py-0.7.1}/pyproject.toml +3 -2
  10. {goose_py-0.7.0 → goose_py-0.7.1}/tests/conftest.py +1 -0
  11. {goose_py-0.7.0 → goose_py-0.7.1}/tests/test_agent.py +5 -11
  12. {goose_py-0.7.0 → goose_py-0.7.1}/tests/test_complex_flow_arguments.py +1 -2
  13. {goose_py-0.7.0 → goose_py-0.7.1}/tests/test_downstream_task.py +1 -3
  14. {goose_py-0.7.0 → goose_py-0.7.1}/tests/test_jamming.py +2 -4
  15. {goose_py-0.7.0 → goose_py-0.7.1}/tests/test_looping.py +2 -6
  16. {goose_py-0.7.0 → goose_py-0.7.1}/tests/test_state.py +1 -3
  17. {goose_py-0.7.0 → goose_py-0.7.1}/uv.lock +2 -2
  18. goose_py-0.7.0/.github/workflows/publish.yml +0 -75
  19. {goose_py-0.7.0 → goose_py-0.7.1}/.envrc +0 -0
  20. {goose_py-0.7.0 → goose_py-0.7.1}/.gitignore +0 -0
  21. {goose_py-0.7.0 → goose_py-0.7.1}/.python-version +0 -0
  22. {goose_py-0.7.0 → goose_py-0.7.1}/.stubs/jsonpath_ng/__init__.pyi +0 -0
  23. {goose_py-0.7.0 → goose_py-0.7.1}/.stubs/litellm/__init__.pyi +0 -0
  24. {goose_py-0.7.0 → goose_py-0.7.1}/README.md +0 -0
  25. {goose_py-0.7.0 → goose_py-0.7.1}/goose/__init__.py +0 -0
  26. {goose_py-0.7.0 → goose_py-0.7.1}/goose/_internal/result.py +0 -0
  27. {goose_py-0.7.0 → goose_py-0.7.1}/goose/_internal/store.py +0 -0
  28. {goose_py-0.7.0 → goose_py-0.7.1}/goose/_internal/types/__init__.py +0 -0
  29. {goose_py-0.7.0 → goose_py-0.7.1}/goose/_internal/types/agent.py +0 -0
  30. {goose_py-0.7.0 → goose_py-0.7.1}/goose/agent.py +0 -0
  31. {goose_py-0.7.0 → goose_py-0.7.1}/goose/errors.py +0 -0
  32. {goose_py-0.7.0 → goose_py-0.7.1}/goose/flow.py +0 -0
  33. {goose_py-0.7.0 → goose_py-0.7.1}/goose/py.typed +0 -0
  34. {goose_py-0.7.0 → goose_py-0.7.1}/goose/runs.py +0 -0
  35. {goose_py-0.7.0 → goose_py-0.7.1}/tests/__init__.py +0 -0
  36. {goose_py-0.7.0 → goose_py-0.7.1}/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
@@ -0,0 +1,9 @@
1
+ test:
2
+ uv run pytest tests
3
+ lint:
4
+ uv run ruff check .
5
+ uv run ruff format .
6
+ uv run pyright .
7
+ publish:
8
+ uv build
9
+ uv publish
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: goose-py
3
- Version: 0.7.0
3
+ Version: 0.7.1
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
- json.dumps(message) for message in minimized_input_messages
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(
@@ -24,18 +24,10 @@ class Conversation[R: Result](BaseModel):
24
24
  messages.append(self.context.render())
25
25
 
26
26
  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
- )
27
+ messages.append(AssistantMessage(text=self.result_messages[message_index].model_dump_json()).render())
32
28
  messages.append(self.user_messages[message_index].render())
33
29
 
34
30
  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
- )
31
+ messages.append(AssistantMessage(text=self.result_messages[-1].model_dump_json()).render())
40
32
 
41
33
  return messages
@@ -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 AsyncIterator, Awaitable, Callable, Protocol, overload
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]:
@@ -97,23 +97,17 @@ class FlowRun:
97
97
  matching_nodes: list[NodeState[R]] = []
98
98
  for key, node_state in self._node_states.items():
99
99
  if key[0] == task.name:
100
- matching_nodes.append(
101
- NodeState[task.result_type].model_validate_json(node_state)
102
- )
100
+ matching_nodes.append(NodeState[task.result_type].model_validate_json(node_state))
103
101
  return sorted(matching_nodes, key=lambda node: node.index)
104
102
 
105
103
  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:
104
+ if (existing_node_state := self._node_states.get((task.name, index))) is not None:
109
105
  return NodeState[task.result_type].model_validate_json(existing_node_state)
110
106
  else:
111
107
  return NodeState[task.result_type](
112
108
  task_name=task.name,
113
109
  index=index,
114
- conversation=Conversation[task.result_type](
115
- user_messages=[], result_messages=[]
116
- ),
110
+ conversation=Conversation[task.result_type](user_messages=[], result_messages=[]),
117
111
  last_hash=0,
118
112
  )
119
113
 
@@ -143,9 +137,7 @@ class FlowRun:
143
137
  self._last_requested_indices = {}
144
138
  self._flow_name = flow_name
145
139
  self._id = run_id
146
- self._agent = Agent(
147
- flow_name=self.flow_name, run_id=self.id, logger=agent_logger
148
- )
140
+ self._agent = Agent(flow_name=self.flow_name, run_id=self.id, logger=agent_logger)
149
141
 
150
142
  def end(self) -> None:
151
143
  self._last_requested_indices = {}
@@ -177,9 +169,7 @@ class FlowRun:
177
169
  return flow_run
178
170
 
179
171
 
180
- _current_flow_run: ContextVar[FlowRun | None] = ContextVar(
181
- "current_flow_run", default=None
182
- )
172
+ _current_flow_run: ContextVar[FlowRun | None] = ContextVar("current_flow_run", default=None)
183
173
 
184
174
 
185
175
  def get_current_flow_run() -> FlowRun | None:
@@ -1,4 +1,5 @@
1
- from typing import Awaitable, Callable, overload
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,9 +57,7 @@ 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
 
@@ -97,11 +94,7 @@ class Task[**P, R: Result]:
97
94
 
98
95
  def __hash_task_call(self, *args: P.args, **kwargs: P.kwargs) -> int:
99
96
  try:
100
- to_hash = str(
101
- tuple(args)
102
- + tuple(kwargs.values())
103
- + (self._generator.__code__, self._adapter_model)
104
- )
97
+ to_hash = str(tuple(args) + tuple(kwargs.values()) + (self._generator.__code__, self._adapter_model))
105
98
  return hash(to_hash)
106
99
  except TypeError:
107
100
  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.0"
3
+ version = "0.7.1"
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 = [
@@ -1,4 +1,5 @@
1
1
  import asyncio
2
+
2
3
  import pytest
3
4
 
4
5
 
@@ -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.types.agent import GeminiModel, TextMessagePart, UserMessage
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._agent.acompletion",
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
 
@@ -1,8 +1,7 @@
1
1
  import pytest
2
2
  from pydantic import BaseModel
3
3
 
4
- from goose import flow
5
- from goose._agent import Agent
4
+ from goose import Agent, flow
6
5
 
7
6
 
8
7
  class MyMessage(BaseModel):
@@ -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.types.agent import SystemMessage, TextMessagePart, UserMessage
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)
@@ -17,9 +17,7 @@ class GeneratedSentence(Result):
17
17
 
18
18
  @task
19
19
  async def generate_random_word(*, n_characters: int) -> GeneratedWord:
20
- return GeneratedWord(
21
- word="".join(random.sample(string.ascii_lowercase, n_characters))
22
- )
20
+ return GeneratedWord(word="".join(random.sample(string.ascii_lowercase, n_characters)))
23
21
 
24
22
 
25
23
  @task
@@ -233,8 +233,8 @@ wheels = [
233
233
  ]
234
234
 
235
235
  [[package]]
236
- name = "goose"
237
- version = "0.6.1"
236
+ name = "goose-py"
237
+ version = "0.7.1"
238
238
  source = { editable = "." }
239
239
  dependencies = [
240
240
  { name = "jsonpath-ng" },
@@ -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