goose-py 0.11.25__py3-none-any.whl → 0.12.0__py3-none-any.whl

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/__init__.py CHANGED
@@ -1,3 +1,18 @@
1
+ """Goose: A framework for building LLM-based agents and workflows.
2
+
3
+ Goose provides tools for creating structured agent applications with support for:
4
+ - Task-based workflow orchestration
5
+ - Caching and state management
6
+ - Result validation and typing
7
+ - Agent conversations and refinement
8
+
9
+ Main components:
10
+ - Agent: Base agent for interacting with LLMs
11
+ - flow: Decorator for creating connected workflows
12
+ - task: Decorator for creating individual tasks
13
+ - Result: Base class for structured response types
14
+ """
15
+
1
16
  from goose._internal.agent import Agent
2
17
  from goose._internal.flow import FlowArguments, flow
3
18
  from goose._internal.result import Result, TextResult
goose/_internal/agent.py CHANGED
@@ -1,3 +1,9 @@
1
+ """Agent module for interacting with language models.
2
+
3
+ This module provides the Agent class for handling interactions with language models,
4
+ along with protocols for custom logging.
5
+ """
6
+
1
7
  import logging
2
8
  from datetime import datetime
3
9
  from typing import Any, Literal, Protocol, overload
@@ -22,10 +28,27 @@ ExpectedMessage = LLMUserMessage | LLMAssistantMessage | LLMSystemMessage | LLMT
22
28
 
23
29
 
24
30
  class IAgentLogger(Protocol):
31
+ """Protocol for custom agent response logging.
32
+
33
+ Implement this protocol to create custom loggers for agent responses.
34
+ """
35
+
25
36
  async def __call__(self, *, response: AgentResponse[Any]) -> None: ...
26
37
 
27
38
 
28
39
  class Agent:
40
+ """Agent for interacting with language models.
41
+
42
+ The Agent class handles interactions with language models, including generating
43
+ structured and unstructured responses, asking questions, and refining results.
44
+ It also manages logging of model interactions.
45
+
46
+ Attributes:
47
+ flow_name: The name of the flow this agent is part of
48
+ run_id: The ID of the current run
49
+ logger: Optional custom logger for agent responses
50
+ """
51
+
29
52
  def __init__(
30
53
  self,
31
54
  *,
@@ -33,6 +56,13 @@ class Agent:
33
56
  run_id: str,
34
57
  logger: IAgentLogger | None = None,
35
58
  ) -> None:
59
+ """Initialize an Agent.
60
+
61
+ Args:
62
+ flow_name: The name of the flow this agent is part of
63
+ run_id: The ID of the current run
64
+ logger: Optional custom logger for agent responses
65
+ """
36
66
  self.flow_name = flow_name
37
67
  self.run_id = run_id
38
68
  self.logger = logger
@@ -46,15 +76,33 @@ class Agent:
46
76
  router: LLMRouter[M],
47
77
  response_model: type[R] = TextResult,
48
78
  ) -> R:
79
+ """Generate a structured response from the language model.
80
+
81
+ This method sends a sequence of messages to the language model and expects
82
+ a structured response conforming to the provided response_model.
83
+
84
+ Args:
85
+ messages: List of messages to send to the language model
86
+ model: The language model alias to use
87
+ task_name: Name of the task for logging and tracking
88
+ router: LLM router for routing the request
89
+ response_model: Pydantic model class for the expected response structure
90
+
91
+ Returns:
92
+ A validated instance of the response_model
93
+
94
+ Raises:
95
+ ValidationError: If the response cannot be parsed into the response_model
96
+ """
49
97
  start_time = datetime.now()
50
98
  typed_messages: list[ExpectedMessage] = [*messages]
51
99
 
52
100
  if response_model is TextResult:
53
- response = await llm_unstructured(model=model, messages=typed_messages, router=router)
101
+ response = await llm_unstructured(messages=typed_messages, router=router)
54
102
  parsed_response = response_model.model_validate({"text": response.text})
55
103
  else:
56
104
  response = await llm_structured(
57
- model=model, messages=typed_messages, response_model=response_model, router=router
105
+ messages=typed_messages, response_model=response_model, router=router
58
106
  )
59
107
  parsed_response = response.structured_response
60
108
 
@@ -96,9 +144,23 @@ class Agent:
96
144
  task_name: str,
97
145
  router: LLMRouter[M],
98
146
  ) -> str:
147
+ """Ask the language model for an unstructured text response.
148
+
149
+ This method sends a sequence of messages to the language model and
150
+ receives a free-form text response.
151
+
152
+ Args:
153
+ messages: List of messages to send to the language model
154
+ model: The language model alias to use
155
+ task_name: Name of the task for logging and tracking
156
+ router: LLM router for routing the request
157
+
158
+ Returns:
159
+ The text response from the language model
160
+ """
99
161
  start_time = datetime.now()
100
162
  typed_messages: list[ExpectedMessage] = [*messages]
101
- response = await llm_unstructured(model=model, messages=typed_messages, router=router)
163
+ response = await llm_unstructured(messages=typed_messages, router=router)
102
164
  end_time = datetime.now()
103
165
 
104
166
  if isinstance(messages[0], LLMSystemMessage):
@@ -138,10 +200,30 @@ class Agent:
138
200
  task_name: str,
139
201
  response_model: type[R],
140
202
  ) -> R:
203
+ """Refine a previous structured response based on feedback.
204
+
205
+ This method uses a find-and-replace approach to refine a previous structured
206
+ response. It identifies parts of the previous response to change and applies
207
+ these changes to create an updated response.
208
+
209
+ Args:
210
+ messages: List of messages including the previous response and feedback
211
+ model: The language model alias to use
212
+ router: LLM router for routing the request
213
+ task_name: Name of the task for logging and tracking
214
+ response_model: The model class of the response to refine
215
+
216
+ Returns:
217
+ A refined instance of the response_model
218
+
219
+ Raises:
220
+ Honk: If no previous result is found in the message history
221
+ ValidationError: If the refined result cannot be validated against the response_model
222
+ """
141
223
  start_time = datetime.now()
142
224
  typed_messages: list[ExpectedMessage] = [*messages]
143
225
  find_replace_response = await llm_structured(
144
- model=model, messages=typed_messages, response_model=FindReplaceResponse, router=router
226
+ messages=typed_messages, response_model=FindReplaceResponse, router=router
145
227
  )
146
228
  parsed_find_replace_response = find_replace_response.structured_response
147
229
  end_time = datetime.now()
@@ -252,6 +334,19 @@ class Agent:
252
334
  def __apply_find_replace[R: Result](
253
335
  self, *, result: R, find_replace_response: FindReplaceResponse, response_model: type[R]
254
336
  ) -> R:
337
+ """Apply find-replace operations to a result.
338
+
339
+ Takes a result object and a set of replacements, applies the replacements,
340
+ and validates the new result against the response model.
341
+
342
+ Args:
343
+ result: The original result to modify
344
+ find_replace_response: Object containing the replacements to apply
345
+ response_model: The model class to validate the result against
346
+
347
+ Returns:
348
+ A new instance of the response model with replacements applied
349
+ """
255
350
  dumped_result = result.model_dump_json()
256
351
  for replacement in find_replace_response.replacements:
257
352
  dumped_result = dumped_result.replace(replacement.find, replacement.replace)
@@ -261,6 +356,21 @@ class Agent:
261
356
  def __find_last_result[R: Result](
262
357
  self, *, messages: list[LLMUserMessage | LLMAssistantMessage | LLMSystemMessage], response_model: type[R]
263
358
  ) -> R:
359
+ """Find the last result in a conversation history.
360
+
361
+ Searches through messages in reverse order to find the most recent
362
+ assistant message that can be parsed as the given response model.
363
+
364
+ Args:
365
+ messages: List of messages to search through
366
+ response_model: The model class to validate found results against
367
+
368
+ Returns:
369
+ The last result that can be validated as the response model
370
+
371
+ Raises:
372
+ Honk: If no valid result is found in the message history
373
+ """
264
374
  for message in reversed(messages):
265
375
  if isinstance(message, LLMAssistantMessage):
266
376
  try:
goose/_internal/flow.py CHANGED
@@ -1,3 +1,10 @@
1
+ """Flow module for orchestrating sequences of tasks.
2
+
3
+ This module provides the Flow class and flow decorator for building
4
+ workflows that coordinate multiple tasks with state management,
5
+ persistence, and run tracking.
6
+ """
7
+
1
8
  from collections.abc import AsyncIterator, Callable
2
9
  from contextlib import asynccontextmanager
3
10
  from types import CodeType
@@ -12,18 +19,46 @@ from .store import IFlowRunStore, InMemoryFlowRunStore
12
19
 
13
20
 
14
21
  class IGenerator[FlowArgumentsT: FlowArguments](Protocol):
22
+ """Protocol for flow generator functions.
23
+
24
+ This protocol defines the interface for functions that can be used
25
+ to create flows.
26
+
27
+ Type Parameters:
28
+ FlowArgumentsT: Type of flow arguments
29
+ """
30
+
15
31
  __name__: str
16
32
 
17
33
  async def __call__(self, *, flow_arguments: FlowArgumentsT, agent: Agent) -> None: ...
18
34
 
19
35
 
20
36
  class IAdapter[ResultT: Result](Protocol):
37
+ """Protocol for adapters that transform conversations to results.
38
+
39
+ This protocol defines the interface for functions that can adapt
40
+ conversations to structured results.
41
+
42
+ Type Parameters:
43
+ ResultT: Type of result
44
+ """
45
+
21
46
  __code__: CodeType
22
47
 
23
48
  async def __call__(self, *, conversation: Conversation, agent: Agent) -> ResultT: ...
24
49
 
25
50
 
26
51
  class Flow[FlowArgumentsT: FlowArguments]:
52
+ """Orchestrates a sequence of tasks with persistent state.
53
+
54
+ A Flow manages the execution of tasks, tracks state, and handles
55
+ persistence of results. It provides mechanisms for starting runs,
56
+ generating results, and regenerating flows.
57
+
58
+ Type Parameters:
59
+ FlowArgumentsT: Type of flow arguments
60
+ """
61
+
27
62
  def __init__(
28
63
  self,
29
64
  fn: IGenerator[FlowArgumentsT],
@@ -33,6 +68,14 @@ class Flow[FlowArgumentsT: FlowArguments]:
33
68
  store: IFlowRunStore | None = None,
34
69
  agent_logger: IAgentLogger | None = None,
35
70
  ) -> None:
71
+ """Initialize a Flow.
72
+
73
+ Args:
74
+ fn: The flow generator function
75
+ name: Optional custom name for the flow (defaults to function name)
76
+ store: Optional store for persisting flow runs
77
+ agent_logger: Optional logger for agent responses
78
+ """
36
79
  self._fn = fn
37
80
  self._name = name
38
81
  self._agent_logger = agent_logger
@@ -40,6 +83,14 @@ class Flow[FlowArgumentsT: FlowArguments]:
40
83
 
41
84
  @property
42
85
  def flow_arguments_model(self) -> type[FlowArgumentsT]:
86
+ """Get the flow arguments model type.
87
+
88
+ Returns:
89
+ The FlowArguments type used by this flow
90
+
91
+ Raises:
92
+ Honk: If the flow function has an invalid signature
93
+ """
43
94
  arguments_model = self._fn.__annotations__.get("flow_arguments")
44
95
  if arguments_model is None:
45
96
  raise Honk("Flow function has an invalid signature. Must accept `flow_arguments` and `agent` as arguments.")
@@ -48,10 +99,23 @@ class Flow[FlowArgumentsT: FlowArguments]:
48
99
 
49
100
  @property
50
101
  def name(self) -> str:
102
+ """Get the name of the flow.
103
+
104
+ Returns:
105
+ The name of the flow (custom name or function name)
106
+ """
51
107
  return self._name or self._fn.__name__
52
108
 
53
109
  @property
54
110
  def current_run(self) -> FlowRun[FlowArgumentsT]:
111
+ """Get the current flow run.
112
+
113
+ Returns:
114
+ The current flow run
115
+
116
+ Raises:
117
+ Honk: If there is no current flow run
118
+ """
55
119
  run = get_current_flow_run()
56
120
  if run is None:
57
121
  raise Honk("No current flow run")
@@ -59,6 +123,23 @@ class Flow[FlowArgumentsT: FlowArguments]:
59
123
 
60
124
  @asynccontextmanager
61
125
  async def start_run(self, *, run_id: str) -> AsyncIterator[FlowRun[FlowArgumentsT]]:
126
+ """Start a new run of this flow.
127
+
128
+ This context manager starts a new flow run or loads an existing one,
129
+ sets it as the current run, and handles persistence when the context exits.
130
+
131
+ Args:
132
+ run_id: Unique identifier for this run
133
+
134
+ Yields:
135
+ The flow run instance
136
+
137
+ Example:
138
+ ```python
139
+ async with my_flow.start_run(run_id="123") as run:
140
+ await my_flow.generate(MyFlowArguments(...))
141
+ ```
142
+ """
62
143
  existing_serialized_run = await self._store.get(run_id=run_id)
63
144
  if existing_serialized_run is not None:
64
145
  run = FlowRun.load(
@@ -78,6 +159,17 @@ class Flow[FlowArgumentsT: FlowArguments]:
78
159
  set_current_flow_run(old_run)
79
160
 
80
161
  async def generate(self, flow_arguments: FlowArgumentsT, /) -> None:
162
+ """Execute the flow with the given arguments.
163
+
164
+ This method runs the flow function with the provided arguments,
165
+ executing all tasks defined within the flow.
166
+
167
+ Args:
168
+ flow_arguments: Arguments for the flow
169
+
170
+ Raises:
171
+ Honk: If there is no current flow run
172
+ """
81
173
  flow_run = get_current_flow_run()
82
174
  if flow_run is None:
83
175
  raise Honk("No current flow run")
@@ -86,6 +178,14 @@ class Flow[FlowArgumentsT: FlowArguments]:
86
178
  await self._fn(flow_arguments=flow_arguments, agent=flow_run.agent)
87
179
 
88
180
  async def regenerate(self) -> None:
181
+ """Regenerate the flow using the same arguments.
182
+
183
+ This method re-executes the flow function with the same arguments
184
+ as the previous execution, which is useful for retrying a flow.
185
+
186
+ Raises:
187
+ Honk: If there is no current flow run
188
+ """
89
189
  flow_run = get_current_flow_run()
90
190
  if flow_run is None:
91
191
  raise Honk("No current flow run")
@@ -110,6 +210,32 @@ def flow[FlowArgumentsT: FlowArguments](
110
210
  store: IFlowRunStore | None = None,
111
211
  agent_logger: IAgentLogger | None = None,
112
212
  ) -> Flow[FlowArgumentsT] | Callable[[IGenerator[FlowArgumentsT]], Flow[FlowArgumentsT]]:
213
+ """Decorator for creating Flow instances.
214
+
215
+ This decorator transforms async functions into Flow objects that can
216
+ orchestrate task execution, manage state, and handle persistence.
217
+
218
+ Examples:
219
+ ```python
220
+ @flow
221
+ async def my_workflow(*, flow_arguments: MyFlowArguments, agent: Agent) -> None:
222
+ # Flow implementation...
223
+
224
+ # Or with parameters
225
+ @flow(name="custom_name", store=CustomStore())
226
+ async def my_workflow(*, flow_arguments: MyFlowArguments, agent: Agent) -> None:
227
+ # Flow implementation...
228
+ ```
229
+
230
+ Args:
231
+ fn: The function to decorate
232
+ name: Optional custom name for the flow
233
+ store: Optional store for persisting flow runs
234
+ agent_logger: Optional logger for agent responses
235
+
236
+ Returns:
237
+ A Flow instance, or a decorator function if used with parameters
238
+ """
113
239
  if fn is None:
114
240
 
115
241
  def decorator(fn: IGenerator[FlowArgumentsT]) -> Flow[FlowArgumentsT]:
goose/_internal/result.py CHANGED
@@ -1,20 +1,56 @@
1
+ """Result models for agent responses.
2
+
3
+ This module provides the base classes and models for structured results from LLM agents.
4
+ """
5
+
1
6
  from pydantic import BaseModel, ConfigDict, Field
2
7
 
3
8
 
4
9
  class Result(BaseModel):
10
+ """Base class for all result models.
11
+
12
+ All result models in Goose extend from this class to have consistent
13
+ behavior. Results are frozen (immutable) by default.
14
+ """
15
+
5
16
  model_config = ConfigDict(frozen=True)
6
17
 
7
18
 
8
19
  class TextResult(Result):
20
+ """Simple text result model.
21
+
22
+ A basic result type that contains unstructured text output from an agent.
23
+
24
+ Attributes:
25
+ text: The text content of the result
26
+ """
27
+
9
28
  text: str
10
29
 
11
30
 
12
31
  class Replacement(BaseModel):
32
+ """Represents a text replacement operation.
33
+
34
+ Used in find-and-replace operations to refine structured results.
35
+
36
+ Attributes:
37
+ find: The text to find in the original result
38
+ replace: The text to replace the found text with
39
+ """
40
+
13
41
  find: str = Field(description="Text to find, to be replaced with `replace`")
14
42
  replace: str = Field(description="Text to replace `find` with")
15
43
 
16
44
 
17
45
  class FindReplaceResponse(BaseModel):
46
+ """Model for find-and-replace operations on structured results.
47
+
48
+ Used to refine existing results by applying a series of replacements.
49
+
50
+ Attributes:
51
+ replacements: List of replacement operations to apply
52
+ """
53
+
18
54
  replacements: list[Replacement] = Field(
19
55
  description="List of replacements to make in the previous result to satisfy the user's request"
20
56
  )
goose/_internal/task.py CHANGED
@@ -1,3 +1,10 @@
1
+ """Task module for defining and executing LLM tasks.
2
+
3
+ This module provides the Task class and task decorator for creating and executing
4
+ individual LLM tasks within a flow. Tasks handle execution, caching, conversation
5
+ management, and result refinement.
6
+ """
7
+
1
8
  import hashlib
2
9
  from collections.abc import Awaitable, Callable
3
10
  from typing import Any, overload
@@ -12,6 +19,16 @@ from goose.errors import Honk
12
19
 
13
20
 
14
21
  class Task[**P, R: Result]:
22
+ """A task within a flow that produces a structured result.
23
+
24
+ Tasks are the building blocks of flows and represent individual LLM operations.
25
+ They handle execution, result caching, conversation management, and result refinement.
26
+
27
+ Type Parameters:
28
+ P: Parameter types for the task function
29
+ R: Result type for the task, must be a subclass of Result
30
+ """
31
+
15
32
  def __init__(
16
33
  self,
17
34
  generator: Callable[P, Awaitable[R]],
@@ -19,11 +36,25 @@ class Task[**P, R: Result]:
19
36
  *,
20
37
  retries: int = 0,
21
38
  ) -> None:
39
+ """Initialize a Task.
40
+
41
+ Args:
42
+ generator: The function that implements the task
43
+ retries: Number of automatic retries if the task fails
44
+ """
22
45
  self._generator = generator
23
46
  self._retries = retries
24
47
 
25
48
  @property
26
49
  def result_type(self) -> type[R]:
50
+ """Get the return type of the task.
51
+
52
+ Returns:
53
+ The result type class for this task
54
+
55
+ Raises:
56
+ Honk: If the task function has no return type annotation
57
+ """
27
58
  result_type = self._generator.__annotations__.get("return")
28
59
  if result_type is None:
29
60
  raise Honk(f"Task {self.name} has no return type annotation")
@@ -31,9 +62,27 @@ class Task[**P, R: Result]:
31
62
 
32
63
  @property
33
64
  def name(self) -> str:
65
+ """Get the name of the task.
66
+
67
+ Returns:
68
+ The name of the task, derived from the generator function name
69
+ """
34
70
  return self._generator.__name__
35
71
 
36
72
  async def generate(self, state: NodeState, *args: P.args, **kwargs: P.kwargs) -> R:
73
+ """Generate a result for this task.
74
+
75
+ Executes the task generator function to produce a result. Uses caching
76
+ based on input hashing to avoid redundant executions.
77
+
78
+ Args:
79
+ state: The current node state for this task execution
80
+ *args: Positional arguments for the task function
81
+ **kwargs: Keyword arguments for the task function
82
+
83
+ Returns:
84
+ The generated result, either freshly computed or from cache
85
+ """
37
86
  state_hash = self.__hash_task_call(*args, **kwargs)
38
87
  if state_hash != state.last_hash:
39
88
  result = await self._generator(*args, **kwargs)
@@ -51,6 +100,24 @@ class Task[**P, R: Result]:
51
100
  context: LLMSystemMessage | None = None,
52
101
  index: int = 0,
53
102
  ) -> str:
103
+ """Ask a follow-up question about a task's result.
104
+
105
+ This method allows for a conversational interaction with a task after
106
+ it has been executed, to ask questions or get explanations about the result.
107
+
108
+ Args:
109
+ user_message: The user's question or message
110
+ router: LLM router for routing the request
111
+ model: The model to use for generating the response
112
+ context: Optional system message to provide context
113
+ index: Index of the task instance when the same task appears multiple times
114
+
115
+ Returns:
116
+ The response text from the model
117
+
118
+ Raises:
119
+ Honk: If the task has not been initially generated
120
+ """
54
121
  flow_run = self.__get_current_flow_run()
55
122
  node_state = flow_run.get_state(task=self, index=index)
56
123
 
@@ -82,6 +149,24 @@ class Task[**P, R: Result]:
82
149
  context: LLMSystemMessage | None = None,
83
150
  index: int = 0,
84
151
  ) -> R:
152
+ """Refine a task's result based on feedback.
153
+
154
+ This method allows for iterative refinement of a task's result based on
155
+ user feedback or additional requirements.
156
+
157
+ Args:
158
+ user_message: The user's feedback or refinement request
159
+ router: LLM router for routing the request
160
+ model: The model to use for generating the response
161
+ context: Optional system message to provide context
162
+ index: Index of the task instance when the same task appears multiple times
163
+
164
+ Returns:
165
+ A refined result of the same type as the original result
166
+
167
+ Raises:
168
+ Honk: If the task has not been initially generated
169
+ """
85
170
  flow_run = self.__get_current_flow_run()
86
171
  node_state = flow_run.get_state(task=self, index=index)
87
172
 
@@ -106,18 +191,46 @@ class Task[**P, R: Result]:
106
191
  return result
107
192
 
108
193
  def edit(self, *, result: R, index: int = 0) -> None:
194
+ """Manually edit a task's result.
195
+
196
+ This method allows for direct editing of a task's result, bypassing
197
+ the usual generation process.
198
+
199
+ Args:
200
+ result: The new result to use
201
+ index: Index of the task instance to edit
202
+ """
109
203
  flow_run = self.__get_current_flow_run()
110
204
  node_state = flow_run.get_state(task=self, index=index)
111
205
  node_state.edit_last_result(result=result.model_dump_json())
112
206
  flow_run.upsert_node_state(node_state)
113
207
 
114
208
  def undo(self, *, index: int = 0) -> None:
209
+ """Undo the most recent change to a task's state.
210
+
211
+ This method reverts the task's state to its previous version.
212
+
213
+ Args:
214
+ index: Index of the task instance to undo
215
+ """
115
216
  flow_run = self.__get_current_flow_run()
116
217
  node_state = flow_run.get_state(task=self, index=index)
117
218
  node_state.undo()
118
219
  flow_run.upsert_node_state(node_state)
119
220
 
120
221
  async def __call__(self, *args: P.args, **kwargs: P.kwargs) -> R:
222
+ """Execute the task within a flow.
223
+
224
+ This method is called when the task is invoked as a function within a flow.
225
+ It manages state setup, generation, and state persistence.
226
+
227
+ Args:
228
+ *args: Positional arguments for the task function
229
+ **kwargs: Keyword arguments for the task function
230
+
231
+ Returns:
232
+ The result of the task
233
+ """
121
234
  flow_run = self.__get_current_flow_run()
122
235
  node_state = flow_run.get_next_state(task=self)
123
236
  result = await self.generate(node_state, *args, **kwargs)
@@ -125,6 +238,22 @@ class Task[**P, R: Result]:
125
238
  return result
126
239
 
127
240
  def __hash_task_call(self, *args: P.args, **kwargs: P.kwargs) -> int:
241
+ """Create a hash of task arguments for caching.
242
+
243
+ Generates a unique hash based on the task's input arguments
244
+ to determine when inputs have changed and results need to be regenerated.
245
+
246
+ Args:
247
+ *args: Positional arguments to hash
248
+ **kwargs: Keyword arguments to hash
249
+
250
+ Returns:
251
+ An integer hash of the arguments
252
+
253
+ Raises:
254
+ Honk: If an argument cannot be hashed
255
+ """
256
+
128
257
  def update_hash(argument: Any, current_hash: Any = hashlib.sha256()) -> None:
129
258
  try:
130
259
  if isinstance(argument, list | tuple | set):
@@ -152,6 +281,14 @@ class Task[**P, R: Result]:
152
281
  return int(result.hexdigest(), 16)
153
282
 
154
283
  def __get_current_flow_run(self) -> FlowRun[Any]:
284
+ """Get the current flow run.
285
+
286
+ Returns:
287
+ The current flow run
288
+
289
+ Raises:
290
+ Honk: If there is no current flow run
291
+ """
155
292
  run = get_current_flow_run()
156
293
  if run is None:
157
294
  raise Honk("No current flow run")
@@ -168,6 +305,30 @@ def task[**P, R: Result](
168
305
  *,
169
306
  retries: int = 0,
170
307
  ) -> Task[P, R] | Callable[[Callable[P, Awaitable[R]]], Task[P, R]]:
308
+ """Decorator for creating Task instances.
309
+
310
+ This decorator transforms async functions into Task objects that can be
311
+ used in flows. Tasks handle execution, caching, conversation, and result refinement.
312
+
313
+ Examples:
314
+ ```python
315
+ @task
316
+ async def generate_summary(text: str) -> TextResult:
317
+ # Task implementation...
318
+
319
+ # Or with parameters
320
+ @task(retries=2)
321
+ async def generate_summary(text: str) -> TextResult:
322
+ # Task implementation...
323
+ ```
324
+
325
+ Args:
326
+ generator: The function to decorate
327
+ retries: Number of automatic retries if the task fails
328
+
329
+ Returns:
330
+ A Task instance, or a decorator function if used with parameters
331
+ """
171
332
  if generator is None:
172
333
 
173
334
  def decorator(fn: Callable[P, Awaitable[R]]) -> Task[P, R]:
goose/errors.py CHANGED
@@ -1,2 +1,16 @@
1
+ """Errors module for Goose.
2
+
3
+ This module defines the custom exception classes used in the Goose framework.
4
+ """
5
+
6
+
1
7
  class Honk(Exception):
8
+ """Base exception class for Goose framework errors.
9
+
10
+ This exception is raised when an error occurs in the Goose framework,
11
+ such as invalid task configuration, missing requirements, or runtime errors.
12
+
13
+ The name "Honk" follows the goose theme of the framework.
14
+ """
15
+
2
16
  pass
@@ -0,0 +1,248 @@
1
+ Metadata-Version: 2.4
2
+ Name: goose-py
3
+ Version: 0.12.0
4
+ Summary: A tool for AI workflows based on human-computer collaboration and structured output.
5
+ Author-email: Nash Taylor <nash@chelle.ai>, Joshua Cook <joshua@chelle.ai>, Michael Sankur <michael@chelle.ai>
6
+ Requires-Python: >=3.12
7
+ Requires-Dist: aikernel==0.1.43
8
+ Requires-Dist: jsonpath-ng>=1.7.0
9
+ Requires-Dist: pydantic>=2.8.2
10
+ Description-Content-Type: text/markdown
11
+
12
+ # Goose
13
+
14
+ Goose is a framework for building LLM-based agents and workflows with strong typing and state management. Here's what's fundamentally possible:
15
+
16
+ 1. Structured LLM interactions - Organize model calls with typed inputs/outputs
17
+ 2. Task orchestration - Create reusable tasks that can be composed into flows
18
+ 3. Stateful conversations - Maintain conversation history and model outputs
19
+ 4. Result caching - Avoid redundant computation based on input hashing
20
+ 5. Iterative refinement - Enhance results through progressive feedback loops
21
+ 6. Result validation - Ensure model outputs conform to expected schemas
22
+ 7. Run persistence - Save and reload workflow executions
23
+ 8. Custom logging - Track telemetry and performance metrics
24
+
25
+ It enables building reliable, maintainable AI applications with proper error handling, state tracking, and flow control while ensuring type safety throughout.
26
+
27
+ ## Key Features
28
+
29
+ ### Structured LLM Interactions
30
+
31
+ Organize model calls with typed inputs and outputs using Pydantic models. This ensures that responses from language models conform to expected structures.
32
+
33
+ ```mermaid
34
+ graph LR
35
+ A[User Input] --> B[Agent]
36
+ B --> C[LLM Model]
37
+ C --> D[Structured Response]
38
+ D --> E[Validated Result]
39
+ E --> F[Application Logic]
40
+
41
+ classDef user fill:#f9f,stroke:#333,stroke-width:2px
42
+ classDef llm fill:#bbf,stroke:#333,stroke-width:2px
43
+ classDef validation fill:#bfb,stroke:#333,stroke-width:2px
44
+
45
+ class A user
46
+ class C llm
47
+ class D,E validation
48
+ ```
49
+
50
+ ### Task Orchestration
51
+
52
+ Create reusable tasks that can be composed into flows. Tasks are decorated functions that handle specific operations, while flows coordinate multiple tasks.
53
+
54
+ ```mermaid
55
+ graph TD
56
+ A[Flow] --> B[Task 1]
57
+ A --> C[Task 2]
58
+ A --> D[Task 3]
59
+ B --> E[Result 1]
60
+ C --> F[Result 2]
61
+ D --> G[Result 3]
62
+ E --> H[Flow Output]
63
+ F --> H
64
+ G --> H
65
+
66
+ classDef flow fill:#f9f,stroke:#333,stroke-width:2px
67
+ classDef task fill:#bbf,stroke:#333,stroke-width:2px
68
+ classDef result fill:#bfb,stroke:#333,stroke-width:2px
69
+
70
+ class A flow
71
+ class B,C,D task
72
+ class E,F,G,H result
73
+ ```
74
+
75
+ ### Stateful Conversations
76
+
77
+ Maintain conversation history and model outputs across multiple interactions. The framework tracks the state of each task in a flow.
78
+
79
+ ```mermaid
80
+ sequenceDiagram
81
+ participant User
82
+ participant Flow
83
+ participant Task
84
+ participant Agent
85
+ participant LLM
86
+
87
+ User->>Flow: Start Conversation
88
+ Flow->>Task: Execute
89
+ Task->>Agent: Generate Response
90
+ Agent->>LLM: Send Messages
91
+ LLM-->>Agent: Generate Response
92
+ Agent-->>Task: Store Result
93
+ Task-->>Flow: Update State
94
+ Flow-->>User: Return Result
95
+
96
+ User->>Flow: Follow-up Question
97
+ Flow->>Task: Get State
98
+ Task->>Agent: Send Previous Context + New Question
99
+ Agent->>LLM: Send Updated Messages
100
+ LLM-->>Agent: Generate Response
101
+ Agent-->>Task: Update Conversation
102
+ Task-->>Flow: Update State
103
+ Flow-->>User: Return Result
104
+ ```
105
+
106
+ ### Result Caching
107
+
108
+ Avoid redundant computation by caching results based on input hashing. The framework automatically detects when inputs change and only regenerates results when necessary.
109
+
110
+ ```mermaid
111
+ flowchart TD
112
+ A[Task Call] --> B{Inputs Changed?}
113
+ B -- Yes --> C[Execute Task]
114
+ B -- No --> D[Return Cached Result]
115
+ C --> E[Cache Result]
116
+ E --> F[Return Result]
117
+ D --> F
118
+
119
+ classDef decision fill:#f9f,stroke:#333,stroke-width:2px
120
+ classDef action fill:#bbf,stroke:#333,stroke-width:2px
121
+ classDef cache fill:#bfb,stroke:#333,stroke-width:2px
122
+
123
+ class B decision
124
+ class A,C,F action
125
+ class D,E cache
126
+ ```
127
+
128
+ ### Iterative Refinement
129
+
130
+ Enhance results through progressive feedback loops. The framework supports asking follow-up questions about results and refining them based on feedback.
131
+
132
+ ```mermaid
133
+ sequenceDiagram
134
+ participant User
135
+ participant Task
136
+ participant Agent
137
+ participant LLM
138
+
139
+ User->>Task: Generate Initial Result
140
+ Task->>Agent: Send Request
141
+ Agent->>LLM: Generate Structured Output
142
+ LLM-->>Agent: Return Output
143
+ Agent-->>Task: Store Result
144
+ Task-->>User: Return Result
145
+
146
+ User->>Task: Request Refinement
147
+ Task->>Agent: Send Feedback + Original Result
148
+ Agent->>LLM: Generate Find/Replace Operations
149
+ LLM-->>Agent: Return Changes
150
+ Agent-->>Task: Apply Changes to Result
151
+ Task-->>User: Return Refined Result
152
+ ```
153
+
154
+ ### Result Validation
155
+
156
+ Ensure model outputs conform to expected schemas using Pydantic validation. All results must conform to predefined models.
157
+
158
+ ```mermaid
159
+ flowchart LR
160
+ A[LLM Response] --> B[Parse JSON]
161
+ B --> C{Valid Schema?}
162
+ C -- Yes --> D[Return Validated Result]
163
+ C -- No --> E[Raise Error]
164
+
165
+ classDef input fill:#bbf,stroke:#333,stroke-width:2px
166
+ classDef validation fill:#f9f,stroke:#333,stroke-width:2px
167
+ classDef output fill:#bfb,stroke:#333,stroke-width:2px
168
+ classDef error fill:#fbb,stroke:#333,stroke-width:2px
169
+
170
+ class A input
171
+ class B,C validation
172
+ class D output
173
+ class E error
174
+ ```
175
+
176
+ ### Run Persistence
177
+
178
+ Save and reload workflow executions. The framework provides interfaces for storing flow runs, allowing for resuming work or reviewing past executions.
179
+
180
+ ```mermaid
181
+ graph TD
182
+ A[Start Flow] --> B[Create Flow Run]
183
+ B --> C[Execute Tasks]
184
+ C --> D[Save Run State]
185
+ D --> E[End Flow]
186
+
187
+ F[Later Time] --> G[Load Saved Run]
188
+ G --> H[Resume Execution]
189
+ H --> D
190
+
191
+ classDef flow fill:#f9f,stroke:#333,stroke-width:2px
192
+ classDef execution fill:#bbf,stroke:#333,stroke-width:2px
193
+ classDef storage fill:#bfb,stroke:#333,stroke-width:2px
194
+
195
+ class A,E,F flow
196
+ class B,C,H execution
197
+ class D,G storage
198
+ ```
199
+
200
+ ### Custom Logging
201
+
202
+ Track telemetry and performance metrics. The framework supports custom loggers to record model usage, token counts, and execution time.
203
+
204
+ ```mermaid
205
+ flowchart TD
206
+ A[Agent Call] --> B[Execute LLM Request]
207
+ B --> C[Record Metrics]
208
+ C --> D{Custom Logger?}
209
+ D -- Yes --> E[Send to Custom Logger]
210
+ D -- No --> F[Log to Default Logger]
211
+ E --> G[Return Result]
212
+ F --> G
213
+
214
+ classDef action fill:#bbf,stroke:#333,stroke-width:2px
215
+ classDef logging fill:#bfb,stroke:#333,stroke-width:2px
216
+ classDef decision fill:#f9f,stroke:#333,stroke-width:2px
217
+
218
+ class A,B,G action
219
+ class C,E,F logging
220
+ class D decision
221
+ ```
222
+
223
+ ## Building with Goose
224
+
225
+ Goose enables building reliable, maintainable AI applications with proper error handling, state tracking, and flow control while ensuring type safety throughout. This approach reduces common issues in LLM applications like:
226
+
227
+ - Type inconsistencies in model responses
228
+ - Loss of context between interactions
229
+ - Redundant LLM calls for identical inputs
230
+ - Difficulty in resuming interrupted workflows
231
+ - Lack of structured error handling
232
+
233
+ Start building more robust LLM applications with Goose's typed, stateful approach to agent development.
234
+
235
+ ## Installation and Package Management
236
+
237
+ Goose uses `uv` for package management. Never use pip with this project.
238
+
239
+ ```bash
240
+ # Install dependencies
241
+ uv add <package-name>
242
+
243
+ # Update dependencies file
244
+ uv sync
245
+
246
+ # Run commands
247
+ uv run <command>
248
+ ```
@@ -1,18 +1,18 @@
1
- goose/__init__.py,sha256=Muw7HCImZHk3kLCTWhV9Lg-Sfmhnwf_Tae-zCj7woyY,338
2
- goose/errors.py,sha256=-0OyZQJWYTRw5YgnCB2_uorVaUsL6Z0QYQO2FqzCiyg,32
1
+ goose/__init__.py,sha256=58IFJL7Zd4WMqlbWhOtDVbU4sUegFjTIYuGsDQJAZU4,842
2
+ goose/errors.py,sha256=FtCv1uGzsDijktcFePVvAQd0rC0MuSBLFIh8YwwJ_BU,429
3
3
  goose/flow.py,sha256=YsZLBa5I1W27_P6LYGWbtFX8ZYx9vJG3KtENYChHm5E,111
4
4
  goose/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
5
  goose/runs.py,sha256=ub-r_gzbUbaIzWXX-jc-dncNxEh6zTfzIkmnDfCSbRI,160
6
6
  goose/task.py,sha256=95rspdxETJoY12IHBl3KjnVIdqQnf1jDKlnGWNWOTvQ,53
7
- goose/_internal/agent.py,sha256=9Hz15SOrhaJWgOXhfQYYW4nrolB2xbMG5G3RwHX_k90,9187
7
+ goose/_internal/agent.py,sha256=K1dKXsfI1a7W8RUNoDcEOs4zX_1-LCHvOEcxzsTiicg,13405
8
8
  goose/_internal/conversation.py,sha256=vhJwe1pHk2lV60DaB9Tz9KbpzQo7_thRYInPjbIoUTE,1437
9
- goose/_internal/flow.py,sha256=8MJxlhHYSAzUHZefpF_sRJc37o532OF0X7l3KRopDmc,4115
10
- goose/_internal/result.py,sha256=vtJMfBxb9skfl8st2tn4hBmEq6qmXiJTme_B5QTgu2M,538
9
+ goose/_internal/flow.py,sha256=nfmy-RyWnt1jh0TBMpy-YpX8ayltjUcPtLOZ4mDFfds,7947
10
+ goose/_internal/result.py,sha256=MXH1jhZSzN_T1ZkslJk6b49otovBlWLhiyr3pAmT1OM,1522
11
11
  goose/_internal/state.py,sha256=kA116MpsetsQz6nYodsXOqE3uYz37OTgjC9Vcy_3Qvg,8065
12
12
  goose/_internal/store.py,sha256=tWmKfa1-yq1jU6lT3l6kSOmVt2m3H7I1xLMTrxnUDI8,889
13
- goose/_internal/task.py,sha256=A13TsBQJlo1yuAqbUpDY8k15JAd5WZxqgrz32F18H2U,6311
13
+ goose/_internal/task.py,sha256=I2COwqtRFkO46jvberh_XMMnPCOlJu4Y5-JajMUjaG4,11672
14
14
  goose/_internal/types/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
15
15
  goose/_internal/types/telemetry.py,sha256=xpfkhx7zCdZjjKU8rPuUJ2UHgqmZ084ZupzbTU36gSM,3791
16
- goose_py-0.11.25.dist-info/METADATA,sha256=varvcbhfLhYcbexYd9BFx_Usp2PMxsgboBWspwXOc1I,444
17
- goose_py-0.11.25.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
18
- goose_py-0.11.25.dist-info/RECORD,,
16
+ goose_py-0.12.0.dist-info/METADATA,sha256=qCPOuLUaoejhbU6C-EhVt0aqPPpE3xpi8VSWf9tyrbM,7621
17
+ goose_py-0.12.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
18
+ goose_py-0.12.0.dist-info/RECORD,,
@@ -1,14 +0,0 @@
1
- Metadata-Version: 2.4
2
- Name: goose-py
3
- Version: 0.11.25
4
- Summary: A tool for AI workflows based on human-computer collaboration and structured output.
5
- Author-email: Nash Taylor <nash@chelle.ai>, Joshua Cook <joshua@chelle.ai>, Michael Sankur <michael@chelle.ai>
6
- Requires-Python: >=3.12
7
- Requires-Dist: aikernel==0.1.42
8
- Requires-Dist: jsonpath-ng>=1.7.0
9
- Requires-Dist: pydantic>=2.8.2
10
- Description-Content-Type: text/markdown
11
-
12
- # Goose
13
-
14
- Docs to come.