goose-py 0.9.14__py3-none-any.whl → 0.9.16__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/_internal/agent.py CHANGED
@@ -7,7 +7,7 @@ from litellm import acompletion
7
7
  from pydantic import BaseModel, computed_field
8
8
 
9
9
  from .result import Result, TextResult
10
- from .types.agent import AIModel, AssistantMessage, SystemMessage, UserMessage
10
+ from .types.agent import AIModel, LLMMessage
11
11
 
12
12
 
13
13
  class AgentResponseDump(TypedDict):
@@ -51,8 +51,8 @@ class AgentResponse[R: BaseModel | str](BaseModel):
51
51
  flow_name: str
52
52
  task_name: str
53
53
  model: AIModel
54
- system: SystemMessage | None = None
55
- input_messages: list[UserMessage | AssistantMessage]
54
+ system: LLMMessage | None = None
55
+ input_messages: list[LLMMessage]
56
56
  input_tokens: int
57
57
  output_tokens: int
58
58
  start_time: datetime
@@ -82,13 +82,13 @@ class AgentResponse[R: BaseModel | str](BaseModel):
82
82
  if self.system is None:
83
83
  minimized_system_message = ""
84
84
  else:
85
- minimized_system_message = self.system.render()
85
+ minimized_system_message = self.system
86
86
  for part in minimized_system_message["content"]:
87
87
  if part["type"] == "image_url":
88
88
  part["image_url"] = "__MEDIA__"
89
89
  minimized_system_message = json.dumps(minimized_system_message)
90
90
 
91
- minimized_input_messages = [message.render() for message in self.input_messages]
91
+ minimized_input_messages = [message for message in self.input_messages]
92
92
  for message in minimized_input_messages:
93
93
  for part in message["content"]:
94
94
  if part["type"] == "image_url":
@@ -135,29 +135,24 @@ class Agent:
135
135
  async def __call__[R: Result](
136
136
  self,
137
137
  *,
138
- messages: list[UserMessage | AssistantMessage],
138
+ messages: list[LLMMessage],
139
139
  model: AIModel,
140
140
  task_name: str,
141
141
  response_model: type[R] = TextResult,
142
- system: SystemMessage | None = None,
142
+ system: LLMMessage | None = None,
143
143
  ) -> R:
144
144
  start_time = datetime.now()
145
- rendered_messages = [message.render() for message in messages]
146
145
  if system is not None:
147
- rendered_messages.insert(0, system.render())
146
+ messages.insert(0, system)
148
147
 
149
148
  if response_model is TextResult:
150
- response = await acompletion(model=model.value, messages=rendered_messages)
149
+ response = await acompletion(model=model.value, messages=messages)
151
150
  parsed_response = response_model.model_validate({"text": response.choices[0].message.content})
152
151
  else:
153
152
  response = await acompletion(
154
153
  model=model.value,
155
- messages=rendered_messages,
156
- response_format={
157
- "type": "json_object",
158
- "response_schema": response_model.model_json_schema(),
159
- "enforce_validation": True,
160
- },
154
+ messages=messages,
155
+ response_format=response_model,
161
156
  )
162
157
  parsed_response = response_model.model_validate_json(response.choices[0].message.content)
163
158
 
@@ -2,18 +2,20 @@ from typing import Self
2
2
 
3
3
  from pydantic import BaseModel
4
4
 
5
+ from goose.errors import Honk
6
+
5
7
  from .result import Result
6
8
  from .types.agent import AssistantMessage, LLMMessage, SystemMessage, UserMessage
7
9
 
8
10
 
9
11
  class Conversation[R: Result](BaseModel):
10
12
  user_messages: list[UserMessage]
11
- result_messages: list[R]
13
+ assistant_messages: list[R | str]
12
14
  context: SystemMessage | None = None
13
15
 
14
16
  @property
15
17
  def awaiting_response(self) -> bool:
16
- return len(self.user_messages) == len(self.result_messages)
18
+ return len(self.user_messages) == len(self.assistant_messages)
17
19
 
18
20
  def render(self) -> list[LLMMessage]:
19
21
  messages: list[LLMMessage] = []
@@ -21,15 +23,30 @@ class Conversation[R: Result](BaseModel):
21
23
  messages.append(self.context.render())
22
24
 
23
25
  for message_index in range(len(self.user_messages)):
24
- messages.append(AssistantMessage(text=self.result_messages[message_index].model_dump_json()).render())
26
+ message = self.assistant_messages[message_index]
27
+ if isinstance(message, str):
28
+ messages.append(AssistantMessage(text=message).render())
29
+ else:
30
+ messages.append(AssistantMessage(text=message.model_dump_json()).render())
31
+
25
32
  messages.append(self.user_messages[message_index].render())
26
33
 
27
- if len(self.result_messages) > len(self.user_messages):
28
- messages.append(AssistantMessage(text=self.result_messages[-1].model_dump_json()).render())
34
+ if len(self.assistant_messages) > len(self.user_messages):
35
+ message = self.assistant_messages[-1]
36
+ if isinstance(message, str):
37
+ messages.append(AssistantMessage(text=message).render())
38
+ else:
39
+ messages.append(AssistantMessage(text=message.model_dump_json()).render())
29
40
 
30
41
  return messages
31
42
 
32
43
  def undo(self) -> Self:
44
+ if len(self.user_messages) == 0:
45
+ raise Honk("Cannot undo, no user messages")
46
+
47
+ if len(self.assistant_messages) == 0:
48
+ raise Honk("Cannot undo, no assistant messages")
49
+
33
50
  self.user_messages.pop()
34
- self.result_messages.pop()
51
+ self.assistant_messages.pop()
35
52
  return self
goose/_internal/state.py CHANGED
@@ -4,15 +4,11 @@ from typing import TYPE_CHECKING, Any, NewType, Self
4
4
 
5
5
  from pydantic import BaseModel, ConfigDict
6
6
 
7
- from ..errors import Honk
8
- from .agent import (
9
- Agent,
10
- IAgentLogger,
11
- SystemMessage,
12
- UserMessage,
13
- )
14
- from .conversation import Conversation
15
- from .result import Result
7
+ from goose._internal.agent import Agent, IAgentLogger
8
+ from goose._internal.conversation import Conversation
9
+ from goose._internal.result import Result
10
+ from goose._internal.types.agent import SystemMessage, UserMessage
11
+ from goose.errors import Honk
16
12
 
17
13
  if TYPE_CHECKING:
18
14
  from goose._internal.task import Task
@@ -32,10 +28,11 @@ class NodeState[ResultT: Result](BaseModel):
32
28
 
33
29
  @property
34
30
  def result(self) -> ResultT:
35
- if len(self.conversation.result_messages) == 0:
36
- raise Honk("Node awaiting response, has no result")
31
+ for message in reversed(self.conversation.assistant_messages):
32
+ if isinstance(message, Result):
33
+ return message
37
34
 
38
- return self.conversation.result_messages[-1]
35
+ raise Honk("Node awaiting response, has no result")
39
36
 
40
37
  def set_context(self, *, context: SystemMessage) -> Self:
41
38
  self.conversation.context = context
@@ -48,24 +45,33 @@ class NodeState[ResultT: Result](BaseModel):
48
45
  new_hash: int | None = None,
49
46
  overwrite: bool = False,
50
47
  ) -> Self:
51
- if overwrite and len(self.conversation.result_messages) > 0:
52
- self.conversation.result_messages[-1] = result
48
+ if overwrite and len(self.conversation.assistant_messages) > 0:
49
+ self.conversation.assistant_messages[-1] = result
53
50
  else:
54
- self.conversation.result_messages.append(result)
51
+ self.conversation.assistant_messages.append(result)
55
52
  if new_hash is not None:
56
53
  self.last_hash = new_hash
57
54
  return self
58
55
 
56
+ def add_answer(self, *, answer: str) -> Self:
57
+ self.conversation.assistant_messages.append(answer)
58
+ return self
59
+
59
60
  def add_user_message(self, *, message: UserMessage) -> Self:
60
61
  self.conversation.user_messages.append(message)
61
62
  return self
62
63
 
63
64
  def edit_last_result(self, *, result: ResultT) -> Self:
64
- if len(self.conversation.result_messages) == 0:
65
+ if len(self.conversation.assistant_messages) == 0:
65
66
  raise Honk("Node awaiting response, has no result")
66
67
 
67
- self.conversation.result_messages[-1] = result
68
- return self
68
+ for message_index, message in enumerate(reversed(self.conversation.assistant_messages)):
69
+ if isinstance(message, Result):
70
+ index = len(self.conversation.assistant_messages) - message_index - 1
71
+ self.conversation.assistant_messages[index] = result
72
+ return self
73
+
74
+ raise Honk("Node awaiting response, has no result")
69
75
 
70
76
  def undo(self) -> Self:
71
77
  self.conversation.undo()
@@ -117,7 +123,7 @@ class FlowRun[FlowArgumentsT: FlowArguments]:
117
123
  return NodeState[task.result_type](
118
124
  task_name=task.name,
119
125
  index=index,
120
- conversation=Conversation[task.result_type](user_messages=[], result_messages=[]),
126
+ conversation=Conversation[task.result_type](user_messages=[], assistant_messages=[]),
121
127
  last_hash=0,
122
128
  )
123
129
 
goose/_internal/task.py CHANGED
@@ -5,11 +5,10 @@ from typing import Any, overload
5
5
  from pydantic import BaseModel
6
6
 
7
7
  from ..errors import Honk
8
- from .agent import Agent, AIModel, SystemMessage, UserMessage
9
- from .conversation import Conversation
10
- from .result import Result, TextResult
8
+ from .agent import Agent, AIModel
9
+ from .result import Result
11
10
  from .state import FlowRun, NodeState, get_current_flow_run
12
- from .types.agent import AssistantMessage
11
+ from .types.agent import SystemMessage, UserMessage
13
12
 
14
13
 
15
14
  class Task[**P, R: Result]:
@@ -19,12 +18,11 @@ class Task[**P, R: Result]:
19
18
  /,
20
19
  *,
21
20
  retries: int = 0,
22
- adapter_model: AIModel = AIModel.GEMINI_FLASH,
21
+ refinement_model: AIModel = AIModel.GEMINI_FLASH,
23
22
  ) -> None:
24
23
  self._generator = generator
25
24
  self._retries = retries
26
- self._adapter_model = adapter_model
27
- self._adapter_model = adapter_model
25
+ self._refinement_model = refinement_model
28
26
 
29
27
  @property
30
28
  def result_type(self) -> type[R]:
@@ -46,6 +44,25 @@ class Task[**P, R: Result]:
46
44
  else:
47
45
  return state.result
48
46
 
47
+ async def ask(self, *, user_message: UserMessage, context: SystemMessage | None = None, index: int = 0) -> str:
48
+ flow_run = self.__get_current_flow_run()
49
+ node_state = flow_run.get(task=self, index=index)
50
+
51
+ if len(node_state.conversation.assistant_messages) == 0:
52
+ raise Honk("Cannot ask about a task that has not been initially generated")
53
+
54
+ node_state.add_user_message(message=user_message)
55
+ answer = await flow_run.agent(
56
+ messages=node_state.conversation.render(),
57
+ model=self._refinement_model,
58
+ task_name=f"ask--{self.name}",
59
+ system=context.render() if context is not None else None,
60
+ )
61
+ node_state.add_answer(answer=answer.text)
62
+ flow_run.upsert_node_state(node_state)
63
+
64
+ return answer.text
65
+
49
66
  async def refine(
50
67
  self,
51
68
  *,
@@ -56,14 +73,20 @@ class Task[**P, R: Result]:
56
73
  flow_run = self.__get_current_flow_run()
57
74
  node_state = flow_run.get(task=self, index=index)
58
75
 
59
- if len(node_state.conversation.result_messages) == 0:
76
+ if len(node_state.conversation.assistant_messages) == 0:
60
77
  raise Honk("Cannot refine a task that has not been initially generated")
61
78
 
62
79
  if context is not None:
63
80
  node_state.set_context(context=context)
64
81
  node_state.add_user_message(message=user_message)
65
82
 
66
- result = await self.__adapt(conversation=node_state.conversation, agent=flow_run.agent)
83
+ result = await flow_run.agent(
84
+ messages=node_state.conversation.render(),
85
+ model=self._refinement_model,
86
+ task_name=f"refine--{self.name}",
87
+ system=context.render() if context is not None else None,
88
+ response_model=self.result_type,
89
+ )
67
90
  node_state.add_result(result=result)
68
91
  flow_run.upsert_node_state(node_state)
69
92
 
@@ -88,28 +111,6 @@ class Task[**P, R: Result]:
88
111
  flow_run.upsert_node_state(node_state)
89
112
  return result
90
113
 
91
- async def __adapt(self, *, conversation: Conversation[R], agent: Agent) -> R:
92
- messages: list[UserMessage | AssistantMessage] = []
93
- for message_index in range(len(conversation.user_messages)):
94
- user_message = conversation.user_messages[message_index]
95
- result = conversation.result_messages[message_index]
96
-
97
- if isinstance(result, TextResult):
98
- assistant_text = result.text
99
- else:
100
- assistant_text = result.model_dump_json()
101
- assistant_message = AssistantMessage(text=assistant_text)
102
- messages.append(assistant_message)
103
- messages.append(user_message)
104
-
105
- return await agent(
106
- messages=messages,
107
- model=self._adapter_model,
108
- task_name=f"adapt--{self.name}",
109
- system=conversation.context,
110
- response_model=self.result_type,
111
- )
112
-
113
114
  def __hash_task_call(self, *args: P.args, **kwargs: P.kwargs) -> int:
114
115
  def update_hash(argument: Any, current_hash: Any = hashlib.sha256()) -> None:
115
116
  try:
@@ -148,20 +149,20 @@ class Task[**P, R: Result]:
148
149
  def task[**P, R: Result](generator: Callable[P, Awaitable[R]], /) -> Task[P, R]: ...
149
150
  @overload
150
151
  def task[**P, R: Result](
151
- *, retries: int = 0, adapter_model: AIModel = AIModel.GEMINI_FLASH
152
+ *, retries: int = 0, refinement_model: AIModel = AIModel.GEMINI_FLASH
152
153
  ) -> Callable[[Callable[P, Awaitable[R]]], Task[P, R]]: ...
153
154
  def task[**P, R: Result](
154
155
  generator: Callable[P, Awaitable[R]] | None = None,
155
156
  /,
156
157
  *,
157
158
  retries: int = 0,
158
- adapter_model: AIModel = AIModel.GEMINI_FLASH,
159
+ refinement_model: AIModel = AIModel.GEMINI_FLASH,
159
160
  ) -> Task[P, R] | Callable[[Callable[P, Awaitable[R]]], Task[P, R]]:
160
161
  if generator is None:
161
162
 
162
163
  def decorator(fn: Callable[P, Awaitable[R]]) -> Task[P, R]:
163
- return Task(fn, retries=retries, adapter_model=adapter_model)
164
+ return Task(fn, retries=retries, refinement_model=refinement_model)
164
165
 
165
166
  return decorator
166
167
 
167
- return Task(generator, retries=retries, adapter_model=adapter_model)
168
+ return Task(generator, retries=retries, refinement_model=refinement_model)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: goose-py
3
- Version: 0.9.14
3
+ Version: 0.9.16
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
@@ -5,15 +5,15 @@ goose/flow.py,sha256=YsZLBa5I1W27_P6LYGWbtFX8ZYx9vJG3KtENYChHm5E,111
5
5
  goose/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
6
  goose/runs.py,sha256=ub-r_gzbUbaIzWXX-jc-dncNxEh6zTfzIkmnDfCSbRI,160
7
7
  goose/task.py,sha256=95rspdxETJoY12IHBl3KjnVIdqQnf1jDKlnGWNWOTvQ,53
8
- goose/_internal/agent.py,sha256=VRDEhBDTpKZS09B6xnWsOqLsxP7D5NHKVGlxwayPdoo,6030
9
- goose/_internal/conversation.py,sha256=zbMvP4oxhKAzATVEXZfGVKXWfEjh472MYKhmyJzSLgI,1172
8
+ goose/_internal/agent.py,sha256=v6v5Sno3Y8jkqjJ1zC6AZL6yFiarTU51AEKQvXFOIGg,5654
9
+ goose/_internal/conversation.py,sha256=zvKqLxJSCIIuhD7gjcSFhleYsLabu-ALl9woWFy3mQU,1766
10
10
  goose/_internal/flow.py,sha256=RShMsxgt49g1fZJ3rlwDHtI1j39lZzewx8hZ7DGN5kg,4124
11
11
  goose/_internal/result.py,sha256=-eZJn-2sPo7rHZ38Sz6IAHXqiJ-Ss39esEoFGimJEBI,155
12
- goose/_internal/state.py,sha256=pI-C37Ybazo7EJPbZklxbiCYFy3u4I031NKBr8Jm_CI,6534
12
+ goose/_internal/state.py,sha256=U4gM0K4MAlRFTpqenCYHX9TYGuhWVKIfa4yBeZ9Qc9s,7090
13
13
  goose/_internal/store.py,sha256=tWmKfa1-yq1jU6lT3l6kSOmVt2m3H7I1xLMTrxnUDI8,889
14
- goose/_internal/task.py,sha256=qjpX_wIQ2jKreMjrRy1SVedsuVzLXuOLgcyPstrgWfE,6176
14
+ goose/_internal/task.py,sha256=w4BW3VDDKGjXb3pqzaxRaWHxLpzDLF2ibdIuJRaT7pc,6211
15
15
  goose/_internal/types/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
16
16
  goose/_internal/types/agent.py,sha256=g0KD-aPWZlUGBx72AwQd3LeniFxHATeflZ7191QjFZA,2696
17
- goose_py-0.9.14.dist-info/METADATA,sha256=RGbV0Gcz9vC3n6TwurUc4BTT8y7NJ2d5p6OJZMKOJI8,442
18
- goose_py-0.9.14.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
19
- goose_py-0.9.14.dist-info/RECORD,,
17
+ goose_py-0.9.16.dist-info/METADATA,sha256=qLZ6JVeZqh9uKO6SLonDuq5UBciuO3_KpqKvJNno-9I,442
18
+ goose_py-0.9.16.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
19
+ goose_py-0.9.16.dist-info/RECORD,,