goose-py 0.9.15__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 +10 -11
- goose/_internal/conversation.py +23 -6
- goose/_internal/state.py +25 -19
- goose/_internal/task.py +36 -35
- {goose_py-0.9.15.dist-info → goose_py-0.9.16.dist-info}/METADATA +1 -1
- {goose_py-0.9.15.dist-info → goose_py-0.9.16.dist-info}/RECORD +7 -7
- {goose_py-0.9.15.dist-info → goose_py-0.9.16.dist-info}/WHEEL +0 -0
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,
|
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:
|
55
|
-
input_messages: list[
|
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
|
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
|
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,24 +135,23 @@ class Agent:
|
|
135
135
|
async def __call__[R: Result](
|
136
136
|
self,
|
137
137
|
*,
|
138
|
-
messages: list[
|
138
|
+
messages: list[LLMMessage],
|
139
139
|
model: AIModel,
|
140
140
|
task_name: str,
|
141
141
|
response_model: type[R] = TextResult,
|
142
|
-
system:
|
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
|
-
|
146
|
+
messages.insert(0, system)
|
148
147
|
|
149
148
|
if response_model is TextResult:
|
150
|
-
response = await acompletion(model=model.value, 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=
|
154
|
+
messages=messages,
|
156
155
|
response_format=response_model,
|
157
156
|
)
|
158
157
|
parsed_response = response_model.model_validate_json(response.choices[0].message.content)
|
goose/_internal/conversation.py
CHANGED
@@ -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
|
-
|
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.
|
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
|
-
|
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.
|
28
|
-
|
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.
|
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
|
8
|
-
from .
|
9
|
-
|
10
|
-
|
11
|
-
|
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
|
-
|
36
|
-
|
31
|
+
for message in reversed(self.conversation.assistant_messages):
|
32
|
+
if isinstance(message, Result):
|
33
|
+
return message
|
37
34
|
|
38
|
-
|
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.
|
52
|
-
self.conversation.
|
48
|
+
if overwrite and len(self.conversation.assistant_messages) > 0:
|
49
|
+
self.conversation.assistant_messages[-1] = result
|
53
50
|
else:
|
54
|
-
self.conversation.
|
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.
|
65
|
+
if len(self.conversation.assistant_messages) == 0:
|
65
66
|
raise Honk("Node awaiting response, has no result")
|
66
67
|
|
67
|
-
self.conversation.
|
68
|
-
|
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=[],
|
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
|
9
|
-
from .
|
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
|
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
|
-
|
21
|
+
refinement_model: AIModel = AIModel.GEMINI_FLASH,
|
23
22
|
) -> None:
|
24
23
|
self._generator = generator
|
25
24
|
self._retries = retries
|
26
|
-
self.
|
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.
|
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
|
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,
|
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
|
-
|
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,
|
164
|
+
return Task(fn, retries=retries, refinement_model=refinement_model)
|
164
165
|
|
165
166
|
return decorator
|
166
167
|
|
167
|
-
return Task(generator, retries=retries,
|
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.
|
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=
|
9
|
-
goose/_internal/conversation.py,sha256=
|
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=
|
12
|
+
goose/_internal/state.py,sha256=U4gM0K4MAlRFTpqenCYHX9TYGuhWVKIfa4yBeZ9Qc9s,7090
|
13
13
|
goose/_internal/store.py,sha256=tWmKfa1-yq1jU6lT3l6kSOmVt2m3H7I1xLMTrxnUDI8,889
|
14
|
-
goose/_internal/task.py,sha256=
|
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.
|
18
|
-
goose_py-0.9.
|
19
|
-
goose_py-0.9.
|
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,,
|
File without changes
|