goose-py 0.5.1__py3-none-any.whl → 0.7.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/flow.py CHANGED
@@ -1,458 +1,3 @@
1
- import json
2
- from contextlib import asynccontextmanager
3
- from contextvars import ContextVar
4
- from types import CodeType
5
- from typing import (
6
- Any,
7
- AsyncIterator,
8
- Awaitable,
9
- Callable,
10
- NewType,
11
- Protocol,
12
- Self,
13
- overload,
14
- )
1
+ from goose._internal.flow import Flow
15
2
 
16
- from pydantic import BaseModel
17
-
18
- from goose.agent import (
19
- Agent,
20
- AssistantMessage,
21
- GeminiModel,
22
- IAgentLogger,
23
- LLMMessage,
24
- SystemMessage,
25
- UserMessage,
26
- )
27
- from goose.errors import Honk
28
- from goose.result import Result, TextResult
29
- from goose.store import IFlowRunStore, InMemoryFlowRunStore
30
-
31
- SerializedFlowRun = NewType("SerializedFlowRun", str)
32
-
33
-
34
- class Conversation[R: Result](BaseModel):
35
- user_messages: list[UserMessage]
36
- result_messages: list[R]
37
- context: SystemMessage | None = None
38
-
39
- @property
40
- def awaiting_response(self) -> bool:
41
- return len(self.user_messages) == len(self.result_messages)
42
-
43
- def render(self) -> list[LLMMessage]:
44
- messages: list[LLMMessage] = []
45
- if self.context is not None:
46
- messages.append(self.context.render())
47
-
48
- for message_index in range(len(self.user_messages)):
49
- messages.append(
50
- AssistantMessage(
51
- text=self.result_messages[message_index].model_dump_json()
52
- ).render()
53
- )
54
- messages.append(self.user_messages[message_index].render())
55
-
56
- if len(self.result_messages) > len(self.user_messages):
57
- messages.append(
58
- AssistantMessage(
59
- text=self.result_messages[-1].model_dump_json()
60
- ).render()
61
- )
62
-
63
- return messages
64
-
65
-
66
- class IAdapter[ResultT: Result](Protocol):
67
- __code__: CodeType
68
-
69
- async def __call__(
70
- self, *, conversation: Conversation[ResultT], agent: Agent
71
- ) -> ResultT: ...
72
-
73
-
74
- class NodeState[ResultT: Result](BaseModel):
75
- task_name: str
76
- index: int
77
- conversation: Conversation[ResultT]
78
- last_hash: int
79
-
80
- @property
81
- def result(self) -> ResultT:
82
- if len(self.conversation.result_messages) == 0:
83
- raise Honk("Node awaiting response, has no result")
84
-
85
- return self.conversation.result_messages[-1]
86
-
87
- def set_context(self, *, context: SystemMessage) -> Self:
88
- self.conversation.context = context
89
- return self
90
-
91
- def add_result(
92
- self,
93
- *,
94
- result: ResultT,
95
- new_hash: int | None = None,
96
- overwrite: bool = False,
97
- ) -> Self:
98
- if overwrite and len(self.conversation.result_messages) > 0:
99
- self.conversation.result_messages[-1] = result
100
- else:
101
- self.conversation.result_messages.append(result)
102
- if new_hash is not None:
103
- self.last_hash = new_hash
104
- return self
105
-
106
- def add_user_message(self, *, message: UserMessage) -> Self:
107
- self.conversation.user_messages.append(message)
108
- return self
109
-
110
-
111
- class FlowRun:
112
- def __init__(self) -> None:
113
- self._node_states: dict[tuple[str, int], str] = {}
114
- self._last_requested_indices: dict[str, int] = {}
115
- self._flow_name = ""
116
- self._id = ""
117
- self._agent: Agent | None = None
118
- self._flow_args: tuple[Any, ...] | None = None
119
- self._flow_kwargs: dict[str, Any] | None = None
120
-
121
- @property
122
- def flow_name(self) -> str:
123
- return self._flow_name
124
-
125
- @property
126
- def id(self) -> str:
127
- return self._id
128
-
129
- @property
130
- def agent(self) -> Agent:
131
- if self._agent is None:
132
- raise Honk("Agent is only accessible once a run is started")
133
- return self._agent
134
-
135
- @property
136
- def flow_inputs(self) -> tuple[tuple[Any, ...], dict[str, Any]]:
137
- if self._flow_args is None or self._flow_kwargs is None:
138
- raise Honk("This Flow run has not been executed before")
139
-
140
- return self._flow_args, self._flow_kwargs
141
-
142
- def get_all[R: Result](self, *, task: "Task[Any, R]") -> list[NodeState[R]]:
143
- matching_nodes: list[NodeState[R]] = []
144
- for key, node_state in self._node_states.items():
145
- if key[0] == task.name:
146
- matching_nodes.append(
147
- NodeState[task.result_type].model_validate_json(node_state)
148
- )
149
- return sorted(matching_nodes, key=lambda node: node.index)
150
-
151
- def get[R: Result](self, *, task: "Task[Any, R]", index: int = 0) -> NodeState[R]:
152
- if (
153
- existing_node_state := self._node_states.get((task.name, index))
154
- ) is not None:
155
- return NodeState[task.result_type].model_validate_json(existing_node_state)
156
- else:
157
- return NodeState[task.result_type](
158
- task_name=task.name,
159
- index=index,
160
- conversation=Conversation[task.result_type](
161
- user_messages=[], result_messages=[]
162
- ),
163
- last_hash=0,
164
- )
165
-
166
- def set_flow_inputs(self, *args: Any, **kwargs: Any) -> None:
167
- self._flow_args = args
168
- self._flow_kwargs = kwargs
169
-
170
- def add_node_state(self, node_state: NodeState[Any], /) -> None:
171
- key = (node_state.task_name, node_state.index)
172
- self._node_states[key] = node_state.model_dump_json()
173
-
174
- def get_next[R: Result](self, *, task: "Task[Any, R]") -> NodeState[R]:
175
- if task.name not in self._last_requested_indices:
176
- self._last_requested_indices[task.name] = 0
177
- else:
178
- self._last_requested_indices[task.name] += 1
179
-
180
- return self.get(task=task, index=self._last_requested_indices[task.name])
181
-
182
- def start(
183
- self,
184
- *,
185
- flow_name: str,
186
- run_id: str,
187
- agent_logger: IAgentLogger | None = None,
188
- ) -> None:
189
- self._last_requested_indices = {}
190
- self._flow_name = flow_name
191
- self._id = run_id
192
- self._agent = Agent(
193
- flow_name=self.flow_name, run_id=self.id, logger=agent_logger
194
- )
195
-
196
- def end(self) -> None:
197
- self._last_requested_indices = {}
198
- self._flow_name = ""
199
- self._id = ""
200
- self._agent = None
201
-
202
- def clear_node(self, *, task: "Task[Any, Result]", index: int) -> None:
203
- key = (task.name, index)
204
- if key in self._node_states:
205
- del self._node_states[key]
206
-
207
- def dump(self) -> SerializedFlowRun:
208
- flow_args, flow_kwargs = self.flow_inputs
209
-
210
- return SerializedFlowRun(
211
- json.dumps(
212
- {
213
- "node_states": {
214
- ":".join([task_name, str(index)]): value
215
- for (task_name, index), value in self._node_states.items()
216
- },
217
- "flow_args": list(flow_args),
218
- "flow_kwargs": flow_kwargs,
219
- }
220
- )
221
- )
222
-
223
- @classmethod
224
- def load(cls, serialized_flow_run: SerializedFlowRun, /) -> Self:
225
- flow_run = cls()
226
- run = json.loads(serialized_flow_run)
227
-
228
- new_node_states: dict[tuple[str, int], str] = {}
229
- for key, node_state in run["node_states"].items():
230
- task_name, index = tuple(key.split(":"))
231
- new_node_states[(task_name, int(index))] = node_state
232
- flow_run._node_states = new_node_states
233
-
234
- flow_run._flow_args = tuple(run["flow_args"])
235
- flow_run._flow_kwargs = run["flow_kwargs"]
236
-
237
- return flow_run
238
-
239
-
240
- _current_flow_run: ContextVar[FlowRun | None] = ContextVar(
241
- "current_flow_run", default=None
242
- )
243
-
244
-
245
- class Flow[**P]:
246
- def __init__(
247
- self,
248
- fn: Callable[P, Awaitable[None]],
249
- /,
250
- *,
251
- name: str | None = None,
252
- store: IFlowRunStore | None = None,
253
- agent_logger: IAgentLogger | None = None,
254
- ) -> None:
255
- self._fn = fn
256
- self._name = name
257
- self._agent_logger = agent_logger
258
- self._store = store or InMemoryFlowRunStore(flow_name=self.name)
259
-
260
- @property
261
- def name(self) -> str:
262
- return self._name or self._fn.__name__
263
-
264
- @property
265
- def current_run(self) -> FlowRun:
266
- run = _current_flow_run.get()
267
- if run is None:
268
- raise Honk("No current flow run")
269
- return run
270
-
271
- @asynccontextmanager
272
- async def start_run(self, *, run_id: str) -> AsyncIterator[FlowRun]:
273
- existing_run = await self._store.get(run_id=run_id)
274
- if existing_run is None:
275
- run = FlowRun()
276
- else:
277
- run = existing_run
278
-
279
- old_run = _current_flow_run.get()
280
- _current_flow_run.set(run)
281
-
282
- run.start(flow_name=self.name, run_id=run_id, agent_logger=self._agent_logger)
283
- yield run
284
- await self._store.save(run=run)
285
- run.end()
286
-
287
- _current_flow_run.set(old_run)
288
-
289
- async def generate(self, *args: P.args, **kwargs: P.kwargs) -> None:
290
- flow_run = _current_flow_run.get()
291
- if flow_run is None:
292
- raise Honk("No current flow run")
293
-
294
- flow_run.set_flow_inputs(*args, **kwargs)
295
- await self._fn(*args, **kwargs)
296
-
297
- async def regenerate(self) -> None:
298
- flow_run = _current_flow_run.get()
299
- if flow_run is None:
300
- raise Honk("No current flow run")
301
-
302
- flow_args, flow_kwargs = flow_run.flow_inputs
303
- await self._fn(*flow_args, **flow_kwargs)
304
-
305
-
306
- class Task[**P, R: Result]:
307
- def __init__(
308
- self,
309
- generator: Callable[P, Awaitable[R]],
310
- /,
311
- *,
312
- retries: int = 0,
313
- adapter_model: GeminiModel = GeminiModel.FLASH,
314
- ) -> None:
315
- self._generator = generator
316
- self._retries = retries
317
- self._adapter_model = adapter_model
318
- self._adapter_model = adapter_model
319
-
320
- @property
321
- def result_type(self) -> type[R]:
322
- result_type = self._generator.__annotations__.get("return")
323
- if result_type is None:
324
- raise Honk(f"Task {self.name} has no return type annotation")
325
- return result_type
326
-
327
- @property
328
- def name(self) -> str:
329
- return self._generator.__name__
330
-
331
- async def generate(
332
- self, state: NodeState[R], *args: P.args, **kwargs: P.kwargs
333
- ) -> R:
334
- state_hash = self.__hash_task_call(*args, **kwargs)
335
- if state_hash != state.last_hash:
336
- result = await self._generator(*args, **kwargs)
337
- state.add_result(result=result, new_hash=state_hash, overwrite=True)
338
- return result
339
- else:
340
- return state.result
341
-
342
- async def jam(
343
- self,
344
- *,
345
- user_message: UserMessage,
346
- context: SystemMessage | None = None,
347
- index: int = 0,
348
- ) -> R:
349
- flow_run = self.__get_current_flow_run()
350
- node_state = flow_run.get(task=self, index=index)
351
-
352
- if context is not None:
353
- node_state.set_context(context=context)
354
- node_state.add_user_message(message=user_message)
355
-
356
- result = await self.__adapt(
357
- conversation=node_state.conversation, agent=flow_run.agent
358
- )
359
- node_state.add_result(result=result)
360
- flow_run.add_node_state(node_state)
361
-
362
- return result
363
-
364
- async def __call__(self, *args: P.args, **kwargs: P.kwargs) -> R:
365
- flow_run = self.__get_current_flow_run()
366
- node_state = flow_run.get_next(task=self)
367
- result = await self.generate(node_state, *args, **kwargs)
368
- flow_run.add_node_state(node_state)
369
- return result
370
-
371
- async def __adapt(self, *, conversation: Conversation[R], agent: Agent) -> R:
372
- messages: list[UserMessage | AssistantMessage] = []
373
- for message_index in range(len(conversation.user_messages)):
374
- user_message = conversation.user_messages[message_index]
375
- result = conversation.result_messages[message_index]
376
-
377
- if isinstance(result, TextResult):
378
- assistant_text = result.text
379
- else:
380
- assistant_text = result.model_dump_json()
381
- assistant_message = AssistantMessage(text=assistant_text)
382
- messages.append(assistant_message)
383
- messages.append(user_message)
384
-
385
- return await agent(
386
- messages=messages,
387
- model=self._adapter_model,
388
- task_name=f"adapt--{self.name}",
389
- system=conversation.context,
390
- response_model=self.result_type,
391
- )
392
-
393
- def __hash_task_call(self, *args: P.args, **kwargs: P.kwargs) -> int:
394
- try:
395
- to_hash = str(
396
- tuple(args)
397
- + tuple(kwargs.values())
398
- + (self._generator.__code__, self._adapter_model)
399
- )
400
- return hash(to_hash)
401
- except TypeError:
402
- raise Honk(f"Unhashable argument to task {self.name}: {args} {kwargs}")
403
-
404
- def __get_current_flow_run(self) -> FlowRun:
405
- run = _current_flow_run.get()
406
- if run is None:
407
- raise Honk("No current flow run")
408
- return run
409
-
410
-
411
- @overload
412
- def task[**P, R: Result](generator: Callable[P, Awaitable[R]], /) -> Task[P, R]: ...
413
- @overload
414
- def task[**P, R: Result](
415
- *, retries: int = 0, adapter_model: GeminiModel = GeminiModel.FLASH
416
- ) -> Callable[[Callable[P, Awaitable[R]]], Task[P, R]]: ...
417
- def task[**P, R: Result](
418
- generator: Callable[P, Awaitable[R]] | None = None,
419
- /,
420
- *,
421
- retries: int = 0,
422
- adapter_model: GeminiModel = GeminiModel.FLASH,
423
- ) -> Task[P, R] | Callable[[Callable[P, Awaitable[R]]], Task[P, R]]:
424
- if generator is None:
425
-
426
- def decorator(fn: Callable[P, Awaitable[R]]) -> Task[P, R]:
427
- return Task(fn, retries=retries, adapter_model=adapter_model)
428
-
429
- return decorator
430
-
431
- return Task(generator, retries=retries, adapter_model=adapter_model)
432
-
433
-
434
- @overload
435
- def flow[**P](fn: Callable[P, Awaitable[None]], /) -> Flow[P]: ...
436
- @overload
437
- def flow[**P](
438
- *,
439
- name: str | None = None,
440
- store: IFlowRunStore | None = None,
441
- agent_logger: IAgentLogger | None = None,
442
- ) -> Callable[[Callable[P, Awaitable[None]]], Flow[P]]: ...
443
- def flow[**P](
444
- fn: Callable[P, Awaitable[None]] | None = None,
445
- /,
446
- *,
447
- name: str | None = None,
448
- store: IFlowRunStore | None = None,
449
- agent_logger: IAgentLogger | None = None,
450
- ) -> Flow[P] | Callable[[Callable[P, Awaitable[None]]], Flow[P]]:
451
- if fn is None:
452
-
453
- def decorator(fn: Callable[P, Awaitable[None]]) -> Flow[P]:
454
- return Flow(fn, name=name, store=store, agent_logger=agent_logger)
455
-
456
- return decorator
457
-
458
- return Flow(fn, name=name, store=store, agent_logger=agent_logger)
3
+ __all__ = ["Flow"]
goose/runs.py ADDED
@@ -0,0 +1,4 @@
1
+ from goose._internal.state import FlowRun, FlowRunState
2
+ from goose._internal.store import IFlowRunStore
3
+
4
+ __all__ = ["FlowRun", "FlowRunState", "IFlowRunStore"]
@@ -0,0 +1,14 @@
1
+ Metadata-Version: 2.4
2
+ Name: goose-py
3
+ Version: 0.7.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: jsonpath-ng>=1.7.0
8
+ Requires-Dist: litellm>=1.56.5
9
+ Requires-Dist: pydantic>=2.8.2
10
+ Description-Content-Type: text/markdown
11
+
12
+ # Goose
13
+
14
+ Docs to come.
@@ -0,0 +1,18 @@
1
+ goose/__init__.py,sha256=mppYCowcZw9ke_4y1d1ayHwI3502LBaY959jdOVBPp0,170
2
+ goose/agent.py,sha256=g2tPFqEhqBABEjmpNJ2ShfjHDGzmeUXIgOZCKDZ2-40,600
3
+ goose/errors.py,sha256=-0OyZQJWYTRw5YgnCB2_uorVaUsL6Z0QYQO2FqzCiyg,32
4
+ goose/flow.py,sha256=A1bzNIjnoVXRFm6LGhQglxVnKMP0vEVfvTubTol7Kww,58
5
+ goose/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
+ goose/runs.py,sha256=HHcky_IbmY9HWBjpXJgOcH2Ko0N39qADsGIPR8QYpek,160
7
+ goose/_internal/agent.py,sha256=lXx7tXS518Od7ASIAcFnaGxb_GyJjvm2DI-ufvPJqa8,6101
8
+ goose/_internal/conversation.py,sha256=lOJ3v4uXjM8l8_kRShFTPJiRo4bJr7JvCEl8gwOdres,1217
9
+ goose/_internal/flow.py,sha256=334uZEkgthWqe2kNloArnIPWUuY9TXSdfItrr82Yn7E,3253
10
+ goose/_internal/result.py,sha256=-eZJn-2sPo7rHZ38Sz6IAHXqiJ-Ss39esEoFGimJEBI,155
11
+ goose/_internal/state.py,sha256=3psalnzNN7lZkc5jKGqprCSjt31z6ZiY3wzcefpa-Go,5829
12
+ goose/_internal/store.py,sha256=vIxPIpechF_lEQlQ8JT1NDySDfHe3-eMHEWeTqVbscg,946
13
+ goose/_internal/task.py,sha256=Sru14gAoebW5LXWWCJxs_HWiivhO-zq8cXr6bOosAcA,4756
14
+ goose/_internal/types/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
15
+ goose/_internal/types/agent.py,sha256=rNVt2gEr_m4_8tGFgcdichpPp8xhOS5GY0kN2C4tiE8,2153
16
+ goose_py-0.7.0.dist-info/METADATA,sha256=Petgz53R3syhL8FAuSe32QUEMrtGFg_hNvCbC2ZoI7w,441
17
+ goose_py-0.7.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
18
+ goose_py-0.7.0.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: poetry-core 1.9.1
2
+ Generator: hatchling 1.27.0
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
@@ -1,31 +0,0 @@
1
- Metadata-Version: 2.1
2
- Name: goose-py
3
- Version: 0.5.1
4
- Summary: A tool for AI workflows based on human-computer collaboration and structured output.
5
- Home-page: https://github.com/chelle-ai/goose
6
- Keywords: ai,yaml,configuration,llm
7
- Author: Nash Taylor
8
- Author-email: nash@chelle.ai
9
- Requires-Python: >=3.12,<4.0
10
- Classifier: Development Status :: 4 - Beta
11
- Classifier: Intended Audience :: Developers
12
- Classifier: Operating System :: OS Independent
13
- Classifier: Programming Language :: Python :: 3
14
- Classifier: Programming Language :: Python :: 3.12
15
- Classifier: Programming Language :: Python :: 3.13
16
- Provides-Extra: test
17
- Requires-Dist: ipykernel ; extra == "test"
18
- Requires-Dist: jsonpath-ng (>=1.7.0,<2.0.0)
19
- Requires-Dist: litellm (>=1.56.5,<2.0.0)
20
- Requires-Dist: pydantic (>=2.8.2,<3.0.0)
21
- Requires-Dist: pytest (<8) ; extra == "test"
22
- Requires-Dist: pytest-asyncio ; extra == "test"
23
- Requires-Dist: pytest-mock ; extra == "test"
24
- Project-URL: Documentation, https://github.com/chelle-ai/goose
25
- Project-URL: Repository, https://github.com/chelle-ai/goose
26
- Description-Content-Type: text/markdown
27
-
28
- # Goose
29
-
30
- Docs to come.
31
-
@@ -1,10 +0,0 @@
1
- goose/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
- goose/agent.py,sha256=Wq29_xWgdeBaZ2tOyk5-6GydCOxwCrHs7hqAZnwShdc,8063
3
- goose/errors.py,sha256=-0OyZQJWYTRw5YgnCB2_uorVaUsL6Z0QYQO2FqzCiyg,32
4
- goose/flow.py,sha256=9_Oz9enLNAeRIMyatj26yRb1zvPNTQ6Pqk6DcigNbjc,14399
5
- goose/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
- goose/result.py,sha256=-eZJn-2sPo7rHZ38Sz6IAHXqiJ-Ss39esEoFGimJEBI,155
7
- goose/store.py,sha256=4p2BBVAEUS1_Z0iBk5Qk_fPxRQeph64DRzXOFmjIT38,844
8
- goose_py-0.5.1.dist-info/METADATA,sha256=SzB0laK56WqU_k8zWzK5aEKz66EczNYfgkDDLH80iro,1106
9
- goose_py-0.5.1.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
10
- goose_py-0.5.1.dist-info/RECORD,,
File without changes