goose-py 0.3.1__py3-none-any.whl → 0.3.3__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/agent.py CHANGED
@@ -1,8 +1,9 @@
1
1
  import base64
2
+ import json
2
3
  import logging
3
4
  from datetime import datetime
4
5
  from enum import StrEnum
5
- from typing import Any, Awaitable, Callable, ClassVar, Literal, NotRequired, TypedDict
6
+ from typing import Any, ClassVar, Literal, NotRequired, Protocol, TypedDict
6
7
 
7
8
  from litellm import acompletion
8
9
  from pydantic import BaseModel, computed_field
@@ -97,6 +98,24 @@ class SystemMessage(BaseModel):
97
98
  }
98
99
 
99
100
 
101
+ class AgentResponseDump(TypedDict):
102
+ run_id: str
103
+ flow_name: str
104
+ task_name: str
105
+ model: str
106
+ system_message: str
107
+ input_messages: str
108
+ output_message: str
109
+ input_cost: float
110
+ output_cost: float
111
+ total_cost: float
112
+ input_tokens: int
113
+ output_tokens: int
114
+ start_time: datetime
115
+ end_time: datetime
116
+ duration_ms: int
117
+
118
+
100
119
  class AgentResponse[R: BaseModel](BaseModel):
101
120
  INPUT_CENTS_PER_MILLION_TOKENS: ClassVar[dict[GeminiModel, float]] = {
102
121
  GeminiModel.FLASH_8B: 30,
@@ -143,6 +162,44 @@ class AgentResponse[R: BaseModel](BaseModel):
143
162
  def total_cost(self) -> float:
144
163
  return self.input_cost + self.output_cost
145
164
 
165
+ def minimized_dump(self) -> AgentResponseDump:
166
+ if self.system is None:
167
+ minimized_system_message = ""
168
+ else:
169
+ minimized_system_message = self.system.model_dump()
170
+ for part in minimized_system_message["parts"]:
171
+ if part["type"] == "image_url":
172
+ part["content"] = b"__MEDIA__"
173
+
174
+ minimized_input_messages = [
175
+ message.model_dump() for message in self.input_messages
176
+ ]
177
+ for message in minimized_input_messages:
178
+ if message["type"] == "image_url":
179
+ message["content"] = b"__MEDIA__"
180
+
181
+ return {
182
+ "run_id": self.run_id,
183
+ "flow_name": self.flow_name,
184
+ "task_name": self.task_name,
185
+ "model": self.model.value,
186
+ "system_message": json.dumps(minimized_system_message),
187
+ "input_messages": json.dumps(minimized_input_messages),
188
+ "output_message": self.response.model_dump_json(),
189
+ "input_tokens": self.input_tokens,
190
+ "output_tokens": self.output_tokens,
191
+ "input_cost": self.input_cost,
192
+ "output_cost": self.output_cost,
193
+ "total_cost": self.total_cost,
194
+ "start_time": self.start_time,
195
+ "end_time": self.end_time,
196
+ "duration_ms": self.duration_ms,
197
+ }
198
+
199
+
200
+ class IAgentLogger(Protocol):
201
+ async def __call__(self, *, response: AgentResponse[Any]) -> None: ...
202
+
146
203
 
147
204
  class Agent:
148
205
  def __init__(
@@ -150,7 +207,7 @@ class Agent:
150
207
  *,
151
208
  flow_name: str,
152
209
  run_id: str,
153
- logger: Callable[[AgentResponse[Any]], Awaitable[None]] | None = None,
210
+ logger: IAgentLogger | None = None,
154
211
  ) -> None:
155
212
  self.flow_name = flow_name
156
213
  self.run_id = run_id
@@ -202,7 +259,7 @@ class Agent:
202
259
  )
203
260
 
204
261
  if self.logger is not None:
205
- await self.logger(agent_response)
262
+ await self.logger(response=agent_response)
206
263
  else:
207
264
  logging.info(agent_response.model_dump())
208
265
 
goose/flow.py CHANGED
@@ -1,11 +1,11 @@
1
1
  import json
2
- from contextlib import contextmanager
2
+ from contextlib import asynccontextmanager
3
3
  from contextvars import ContextVar
4
4
  from typing import (
5
5
  Any,
6
+ AsyncIterator,
6
7
  Awaitable,
7
8
  Callable,
8
- Iterator,
9
9
  NewType,
10
10
  Protocol,
11
11
  Self,
@@ -14,8 +14,16 @@ from typing import (
14
14
 
15
15
  from pydantic import BaseModel, ConfigDict, field_validator
16
16
 
17
- from goose.agent import Agent, AssistantMessage, LLMMessage, SystemMessage, UserMessage
17
+ from goose.agent import (
18
+ Agent,
19
+ AssistantMessage,
20
+ IAgentLogger,
21
+ LLMMessage,
22
+ SystemMessage,
23
+ UserMessage,
24
+ )
18
25
  from goose.errors import Honk
26
+ from goose.store import IFlowRunStore, InMemoryFlowRunStore
19
27
 
20
28
  SerializedFlowRun = NewType("SerializedFlowRun", str)
21
29
 
@@ -174,11 +182,19 @@ class FlowRun:
174
182
  last_input_hash=0,
175
183
  )
176
184
 
177
- def start(self, *, flow_name: str, run_id: str) -> None:
185
+ def start(
186
+ self,
187
+ *,
188
+ flow_name: str,
189
+ run_id: str,
190
+ agent_logger: IAgentLogger | None = None,
191
+ ) -> None:
178
192
  self._last_requested_indices = {}
179
193
  self._flow_name = flow_name
180
194
  self._id = run_id
181
- self._agent = Agent(flow_name=self.flow_name, run_id=self.id)
195
+ self._agent = Agent(
196
+ flow_name=self.flow_name, run_id=self.id, logger=agent_logger
197
+ )
182
198
 
183
199
  def end(self) -> None:
184
200
  self._last_requested_indices = {}
@@ -216,10 +232,18 @@ _current_flow_run: ContextVar[FlowRun | None] = ContextVar(
216
232
 
217
233
  class Flow[**P]:
218
234
  def __init__(
219
- self, fn: Callable[P, Awaitable[None]], /, *, name: str | None = None
235
+ self,
236
+ fn: Callable[P, Awaitable[None]],
237
+ /,
238
+ *,
239
+ store: IFlowRunStore | None = None,
240
+ name: str | None = None,
241
+ agent_logger: IAgentLogger | None = None,
220
242
  ) -> None:
221
243
  self._fn = fn
222
244
  self._name = name
245
+ self._agent_logger = agent_logger
246
+ self._store = store or InMemoryFlowRunStore(flow_name=self.name)
223
247
 
224
248
  @property
225
249
  def name(self) -> str:
@@ -232,20 +256,20 @@ class Flow[**P]:
232
256
  raise Honk("No current flow run")
233
257
  return run
234
258
 
235
- @contextmanager
236
- def start_run(
237
- self, *, run_id: str, preload: FlowRun | None = None
238
- ) -> Iterator[FlowRun]:
239
- if preload is None:
259
+ @asynccontextmanager
260
+ async def start_run(self, *, run_id: str) -> AsyncIterator[FlowRun]:
261
+ existing_run = await self._store.get(run_id=run_id)
262
+ if existing_run is None:
240
263
  run = FlowRun()
241
264
  else:
242
- run = preload
265
+ run = existing_run
243
266
 
244
267
  old_run = _current_flow_run.get()
245
268
  _current_flow_run.set(run)
246
269
 
247
- run.start(flow_name=self.name, run_id=run_id)
270
+ run.start(flow_name=self.name, run_id=run_id, agent_logger=self._agent_logger)
248
271
  yield run
272
+ await self._store.save(run=run)
249
273
  run.end()
250
274
 
251
275
  _current_flow_run.set(old_run)
@@ -365,16 +389,20 @@ def task[**P, R: Result](
365
389
  def flow[**P](fn: Callable[P, Awaitable[None]], /) -> Flow[P]: ...
366
390
  @overload
367
391
  def flow[**P](
368
- *, name: str | None = None
392
+ *, name: str | None = None, agent_logger: IAgentLogger | None = None
369
393
  ) -> Callable[[Callable[P, Awaitable[None]]], Flow[P]]: ...
370
394
  def flow[**P](
371
- fn: Callable[P, Awaitable[None]] | None = None, /, *, name: str | None = None
395
+ fn: Callable[P, Awaitable[None]] | None = None,
396
+ /,
397
+ *,
398
+ name: str | None = None,
399
+ agent_logger: IAgentLogger | None = None,
372
400
  ) -> Flow[P] | Callable[[Callable[P, Awaitable[None]]], Flow[P]]:
373
401
  if fn is None:
374
402
 
375
403
  def decorator(fn: Callable[P, Awaitable[None]]) -> Flow[P]:
376
- return Flow(fn, name=name)
404
+ return Flow(fn, name=name, agent_logger=agent_logger)
377
405
 
378
406
  return decorator
379
407
 
380
- return Flow(fn, name=name)
408
+ return Flow(fn, name=name, agent_logger=agent_logger)
goose/store.py ADDED
@@ -0,0 +1,24 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING, Protocol
4
+
5
+ if TYPE_CHECKING:
6
+ from goose.flow import FlowRun
7
+
8
+
9
+ class IFlowRunStore(Protocol):
10
+ def __init__(self, *, flow_name: str) -> None: ...
11
+ async def get(self, *, run_id: str) -> FlowRun | None: ...
12
+ async def save(self, *, run: FlowRun) -> None: ...
13
+
14
+
15
+ class InMemoryFlowRunStore(IFlowRunStore):
16
+ def __init__(self, *, flow_name: str) -> None:
17
+ self._flow_name = flow_name
18
+ self._runs: dict[str, FlowRun] = {}
19
+
20
+ async def get(self, *, run_id: str) -> FlowRun | None:
21
+ return self._runs.get(run_id)
22
+
23
+ async def save(self, *, run: FlowRun) -> None:
24
+ self._runs[run.id] = run
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: goose-py
3
- Version: 0.3.1
3
+ Version: 0.3.3
4
4
  Summary: A tool for AI workflows based on human-computer collaboration and structured output.
5
5
  Home-page: https://github.com/chelle-ai/goose
6
6
  Keywords: ai,yaml,configuration,llm
@@ -0,0 +1,9 @@
1
+ goose/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ goose/agent.py,sha256=uCqHl5BKUX5niPbuZBOqtwnmHf8IZeBcOMylVLUfQlY,7453
3
+ goose/errors.py,sha256=-0OyZQJWYTRw5YgnCB2_uorVaUsL6Z0QYQO2FqzCiyg,32
4
+ goose/flow.py,sha256=I7iWizu6MPHOJYJL-ZQyjbgvjxECBJekibkfO0bGKX0,12490
5
+ goose/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
+ goose/store.py,sha256=6IIGkIYt6CWiNENi2gaS9C0Z-62ia-Qdz29HhCg8njw,698
7
+ goose_py-0.3.3.dist-info/METADATA,sha256=KAvJyHSN3cfcjFUVQCRCGSpwGxKqk76JMkNWZRPCH_g,1106
8
+ goose_py-0.3.3.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
9
+ goose_py-0.3.3.dist-info/RECORD,,
@@ -1,8 +0,0 @@
1
- goose/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
- goose/agent.py,sha256=cJO6Jlh8En_FGLzjelm0w1pM4rsVqfIOpfKUdpkord4,5650
3
- goose/errors.py,sha256=-0OyZQJWYTRw5YgnCB2_uorVaUsL6Z0QYQO2FqzCiyg,32
4
- goose/flow.py,sha256=_ws3Jrp96E5UG7D0dgT0_c1RDs07H-hgJ0mSAmJf_yE,11735
5
- goose/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
- goose_py-0.3.1.dist-info/METADATA,sha256=wOP3QDcyVUJg-vjBw2KBb1xWdw_NA-50wgJtwg24WQc,1106
7
- goose_py-0.3.1.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
8
- goose_py-0.3.1.dist-info/RECORD,,