futurehouse-client 0.4.5.dev119__py3-none-any.whl → 0.4.5.dev160__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.
@@ -8,6 +8,13 @@ from .models.app import (
8
8
  TaskResponse,
9
9
  TaskResponseVerbose,
10
10
  )
11
+ from .models.job_event import (
12
+ CostComponent,
13
+ ExecutionType,
14
+ JobEventCreateRequest,
15
+ JobEventCreateResponse,
16
+ JobEventUpdateRequest,
17
+ )
11
18
  from .utils.world_model_tools import (
12
19
  create_world_model_tool,
13
20
  make_world_model_tools,
@@ -15,9 +22,14 @@ from .utils.world_model_tools import (
15
22
  )
16
23
 
17
24
  __all__ = [
25
+ "CostComponent",
26
+ "ExecutionType",
18
27
  "FinchTaskResponse",
19
28
  "FutureHouseClient",
20
29
  "JobClient",
30
+ "JobEventCreateRequest",
31
+ "JobEventCreateResponse",
32
+ "JobEventUpdateRequest",
21
33
  "JobNames",
22
34
  "PQATaskResponse",
23
35
  "PhoenixTaskResponse",
@@ -52,6 +52,11 @@ from futurehouse_client.models.app import (
52
52
  TaskResponseVerbose,
53
53
  TrajectoryQueryParams,
54
54
  )
55
+ from futurehouse_client.models.job_event import (
56
+ JobEventCreateRequest,
57
+ JobEventCreateResponse,
58
+ JobEventUpdateRequest,
59
+ )
55
60
  from futurehouse_client.models.rest import (
56
61
  DiscoveryResponse,
57
62
  ExecutionStatus,
@@ -160,6 +165,18 @@ class FileUploadError(RestClientError):
160
165
  """Raised when there's an error uploading a file."""
161
166
 
162
167
 
168
+ class JobEventClientError(RestClientError):
169
+ """Raised when there's an error with job event operations."""
170
+
171
+
172
+ class JobEventCreationError(JobEventClientError):
173
+ """Raised when there's an error creating a job event."""
174
+
175
+
176
+ class JobEventUpdateError(JobEventClientError):
177
+ """Raised when there's an error updating a job event."""
178
+
179
+
163
180
  retry_if_connection_error = create_retry_if_connection_error(FileUploadError)
164
181
 
165
182
  DEFAULT_AGENT_TIMEOUT: int = 2400 # seconds
@@ -2609,6 +2626,176 @@ class RestClient(DataStorageMethods):
2609
2626
  f"Error fetching discoveries for project: {e!r}"
2610
2627
  ) from e
2611
2628
 
2629
+ @retry(
2630
+ stop=stop_after_attempt(MAX_RETRY_ATTEMPTS),
2631
+ wait=wait_exponential(multiplier=RETRY_MULTIPLIER, max=MAX_RETRY_WAIT),
2632
+ retry=retry_if_connection_error,
2633
+ before_sleep=before_sleep_log(logger, logging.WARNING),
2634
+ )
2635
+ def create_job_event(
2636
+ self, request: JobEventCreateRequest
2637
+ ) -> JobEventCreateResponse:
2638
+ """Create a new job event.
2639
+
2640
+ Args:
2641
+ request: Job event creation request
2642
+
2643
+ Returns:
2644
+ Job event creation response
2645
+
2646
+ Raises:
2647
+ JobEventCreationError: If the API call fails
2648
+ """
2649
+ try:
2650
+ response = self.client.post(
2651
+ "/v0.1/job-events",
2652
+ json=request.model_dump(exclude_none=True, mode="json"),
2653
+ )
2654
+ response.raise_for_status()
2655
+ return JobEventCreateResponse(**response.json())
2656
+ except HTTPStatusError as e:
2657
+ if e.response.status_code == codes.BAD_REQUEST:
2658
+ raise JobEventCreationError(
2659
+ f"Invalid job event creation request: {e.response.text}."
2660
+ ) from e
2661
+ if e.response.status_code == codes.NOT_FOUND:
2662
+ raise JobEventCreationError(
2663
+ f"Execution not found for job event creation: {e.response.text}."
2664
+ ) from e
2665
+ raise JobEventCreationError(
2666
+ f"Error creating job event: {e.response.status_code} - {e.response.text}."
2667
+ ) from e
2668
+ except Exception as e:
2669
+ raise JobEventCreationError(
2670
+ f"An unexpected error occurred during job event creation: {e!r}."
2671
+ ) from e
2672
+
2673
+ @retry(
2674
+ stop=stop_after_attempt(MAX_RETRY_ATTEMPTS),
2675
+ wait=wait_exponential(multiplier=RETRY_MULTIPLIER, max=MAX_RETRY_WAIT),
2676
+ retry=retry_if_connection_error,
2677
+ before_sleep=before_sleep_log(logger, logging.WARNING),
2678
+ )
2679
+ async def acreate_job_event(
2680
+ self, request: JobEventCreateRequest
2681
+ ) -> JobEventCreateResponse:
2682
+ """Asynchronously create a new job event.
2683
+
2684
+ Args:
2685
+ request: Job event creation request
2686
+
2687
+ Returns:
2688
+ Job event creation response
2689
+
2690
+ Raises:
2691
+ JobEventCreationError: If the API call fails
2692
+ """
2693
+ try:
2694
+ response = await self.async_client.post(
2695
+ "/v0.1/job-events",
2696
+ json=request.model_dump(exclude_none=True, mode="json"),
2697
+ )
2698
+ response.raise_for_status()
2699
+ return JobEventCreateResponse(**response.json())
2700
+ except HTTPStatusError as e:
2701
+ if e.response.status_code == codes.BAD_REQUEST:
2702
+ raise JobEventCreationError(
2703
+ f"Invalid job event creation request: {e.response.text}."
2704
+ ) from e
2705
+ if e.response.status_code == codes.NOT_FOUND:
2706
+ raise JobEventCreationError(
2707
+ f"Execution not found for job event creation: {e.response.text}."
2708
+ ) from e
2709
+ raise JobEventCreationError(
2710
+ f"Error creating job event: {e.response.status_code} - {e.response.text}."
2711
+ ) from e
2712
+ except Exception as e:
2713
+ raise JobEventCreationError(
2714
+ f"An unexpected error occurred during job event creation: {e!r}."
2715
+ ) from e
2716
+
2717
+ @retry(
2718
+ stop=stop_after_attempt(MAX_RETRY_ATTEMPTS),
2719
+ wait=wait_exponential(multiplier=RETRY_MULTIPLIER, max=MAX_RETRY_WAIT),
2720
+ retry=retry_if_connection_error,
2721
+ before_sleep=before_sleep_log(logger, logging.WARNING),
2722
+ )
2723
+ def update_job_event(
2724
+ self, job_event_id: UUID, request: JobEventUpdateRequest
2725
+ ) -> None:
2726
+ """Update an existing job event.
2727
+
2728
+ Args:
2729
+ job_event_id: ID of the job event to update
2730
+ request: Job event update request
2731
+
2732
+ Raises:
2733
+ JobEventUpdateError: If the API call fails
2734
+ """
2735
+ try:
2736
+ response = self.client.patch(
2737
+ f"/v0.1/job-events/{job_event_id}",
2738
+ json=request.model_dump(exclude_none=True, mode="json"),
2739
+ )
2740
+ response.raise_for_status()
2741
+ except HTTPStatusError as e:
2742
+ if e.response.status_code == codes.NOT_FOUND:
2743
+ raise JobEventUpdateError(
2744
+ f"Job event with ID {job_event_id} not found."
2745
+ ) from e
2746
+ if e.response.status_code == codes.BAD_REQUEST:
2747
+ raise JobEventUpdateError(
2748
+ f"Invalid job event update request: {e.response.text}."
2749
+ ) from e
2750
+ raise JobEventUpdateError(
2751
+ f"Error updating job event: {e.response.status_code} - {e.response.text}."
2752
+ ) from e
2753
+ except Exception as e:
2754
+ raise JobEventUpdateError(
2755
+ f"An unexpected error occurred during job event update: {e!r}."
2756
+ ) from e
2757
+
2758
+ @retry(
2759
+ stop=stop_after_attempt(MAX_RETRY_ATTEMPTS),
2760
+ wait=wait_exponential(multiplier=RETRY_MULTIPLIER, max=MAX_RETRY_WAIT),
2761
+ retry=retry_if_connection_error,
2762
+ before_sleep=before_sleep_log(logger, logging.WARNING),
2763
+ )
2764
+ async def aupdate_job_event(
2765
+ self, job_event_id: UUID, request: JobEventUpdateRequest
2766
+ ) -> None:
2767
+ """Asynchronously update an existing job event.
2768
+
2769
+ Args:
2770
+ job_event_id: ID of the job event to update
2771
+ request: Job event update request
2772
+
2773
+ Raises:
2774
+ JobEventUpdateError: If the API call fails
2775
+ """
2776
+ try:
2777
+ response = await self.async_client.patch(
2778
+ f"/v0.1/job-events/{job_event_id}",
2779
+ json=request.model_dump(exclude_none=True, mode="json"),
2780
+ )
2781
+ response.raise_for_status()
2782
+ except HTTPStatusError as e:
2783
+ if e.response.status_code == codes.NOT_FOUND:
2784
+ raise JobEventUpdateError(
2785
+ f"Job event with ID {job_event_id} not found."
2786
+ ) from e
2787
+ if e.response.status_code == codes.BAD_REQUEST:
2788
+ raise JobEventUpdateError(
2789
+ f"Invalid job event update request: {e.response.text}."
2790
+ ) from e
2791
+ raise JobEventUpdateError(
2792
+ f"Error updating job event: {e.response.status_code} - {e.response.text}."
2793
+ ) from e
2794
+ except Exception as e:
2795
+ raise JobEventUpdateError(
2796
+ f"An unexpected error occurred during job event update: {e!r}."
2797
+ ) from e
2798
+
2612
2799
 
2613
2800
  def get_installed_packages() -> dict[str, str]:
2614
2801
  """Returns a dictionary of installed packages and their versions."""
@@ -13,13 +13,25 @@ from .app import (
13
13
  TaskResponse,
14
14
  TaskResponseVerbose,
15
15
  )
16
+ from .job_event import (
17
+ CostComponent,
18
+ ExecutionType,
19
+ JobEventCreateRequest,
20
+ JobEventCreateResponse,
21
+ JobEventUpdateRequest,
22
+ )
16
23
  from .rest import TrajectoryPatchRequest, WorldModel, WorldModelResponse
17
24
 
18
25
  __all__ = [
19
26
  "AuthType",
27
+ "CostComponent",
20
28
  "DockerContainerConfiguration",
29
+ "ExecutionType",
21
30
  "FramePath",
22
31
  "JobDeploymentConfig",
32
+ "JobEventCreateRequest",
33
+ "JobEventCreateResponse",
34
+ "JobEventUpdateRequest",
23
35
  "PQATaskResponse",
24
36
  "RuntimeConfig",
25
37
  "Stage",
@@ -2,7 +2,6 @@ from typing import Any, Generic, TypeAlias, TypeVar
2
2
 
3
3
  from aviary.message import Message
4
4
  from aviary.tools.base import Tool
5
- from ldp.agent import Agent
6
5
  from ldp.data_structures import Transition
7
6
  from ldp.graph.ops import OpResult
8
7
  from pydantic import BaseModel, ConfigDict, Field, field_serializer
@@ -35,6 +34,10 @@ class ASVState(BaseState, Generic[T]):
35
34
  def serialize_action(self, action: OpResult[T]) -> dict:
36
35
  return action.to_dict()
37
36
 
37
+ @field_serializer("next_state")
38
+ def serialize_next_state(self, state: Any) -> str:
39
+ return str(state)
40
+
38
41
 
39
42
  class EnvResetState(BaseState):
40
43
  observations: list[Message] = Field()
@@ -59,64 +62,6 @@ class TransitionState(BaseState):
59
62
  }
60
63
 
61
64
 
62
- class GlobalState(BaseState):
63
- agent: Agent | None = None
64
- env: Any | None = None
65
- agent_state: Any | None = None
66
- next_agent_state: Any | None = None
67
- observations: list = []
68
- action: Any | None = None
69
- value: float = 0.0
70
- last_step_state: Transition | None = None
71
-
72
- def update_observations(self, obs: list[Message]) -> list[Message]:
73
- previous_observations = self.observations or []
74
- self.observations = obs
75
- return previous_observations
76
-
77
- def store_step_state(self, step_state: Transition) -> None:
78
- self.last_step_state = step_state
79
-
80
- def update_trajectory_data(self, **kwargs) -> None:
81
- for key, value in kwargs.items():
82
- setattr(self, key, value)
83
-
84
- def _get_safe_previous_observations(
85
- self, current_obs: list[Message] | None = None
86
- ) -> list[Message]:
87
- if self.last_step_state:
88
- last_step_state = self.last_step_state
89
- if last_step_state.next_observation:
90
- return last_step_state.next_observation
91
- if self.observations:
92
- return self.observations
93
- return current_obs or []
94
-
95
- def create_step_state(self, callback_type: str, **kwargs) -> Transition:
96
- defaults = {
97
- "timestep": getattr(self.agent, "_timestep", 0) if self.agent else 0,
98
- "agent_state": self.agent_state,
99
- "next_agent_state": self.next_agent_state or self.agent_state,
100
- "observation": self._get_safe_previous_observations(),
101
- "next_observation": self.observations or [],
102
- "action": self.action,
103
- "reward": 0.0,
104
- "truncated": False,
105
- "done": False,
106
- "value": self.value or 0.0,
107
- "metadata": {"callback_type": callback_type},
108
- }
109
-
110
- for key, value in kwargs.items():
111
- if key == "metadata" and isinstance(value, dict):
112
- if isinstance(defaults["metadata"], dict):
113
- defaults["metadata"].update(value)
114
- else:
115
- defaults[key] = value
116
-
117
- return Transition(**defaults)
118
-
119
-
120
65
  StateType: TypeAlias = (
121
66
  BeforeTransitionState
122
67
  | InitialState
@@ -124,5 +69,4 @@ StateType: TypeAlias = (
124
69
  | EnvResetState
125
70
  | EnvStepState
126
71
  | TransitionState
127
- | GlobalState
128
72
  )
@@ -0,0 +1,75 @@
1
+ """Job event models for cost and usage tracking."""
2
+
3
+ from datetime import datetime
4
+ from enum import StrEnum, auto
5
+ from typing import Any
6
+ from uuid import UUID
7
+
8
+ from pydantic import BaseModel, Field
9
+
10
+
11
+ class ExecutionType(StrEnum):
12
+ """Type of execution for job events."""
13
+
14
+ TRAJECTORY = auto()
15
+ SESSION = auto()
16
+
17
+
18
+ class CostComponent(StrEnum):
19
+ """Cost component types for job events."""
20
+
21
+ LLM_USAGE = auto()
22
+ EXTERNAL_SERVICE = auto()
23
+ STEP = auto()
24
+
25
+
26
+ class JobEventCreateRequest(BaseModel):
27
+ """Request model for creating a job event matching crow-service schema."""
28
+
29
+ execution_id: UUID = Field(description="UUID for trajectory_id or session_id")
30
+ execution_type: ExecutionType = Field(
31
+ description="Either 'TRAJECTORY' or 'SESSION'"
32
+ )
33
+ cost_component: CostComponent = Field(
34
+ description="Cost component: 'LLM_USAGE', 'EXTERNAL_SERVICE', or 'STEP'"
35
+ )
36
+ started_at: datetime = Field(description="Start time of the job event")
37
+ ended_at: datetime = Field(description="End time of the job event")
38
+ crow: str | None = Field(default=None, description="unique identifier for the crow")
39
+ amount_acu: float | None = Field(default=None, description="Cost amount in ACUs")
40
+ amount_usd: float | None = Field(default=None, description="Cost amount in USD")
41
+ rate: float | None = Field(default=None, description="Rate per token/call in USD")
42
+ input_token_count: int | None = Field(
43
+ default=None, description="Input token count for LLM calls"
44
+ )
45
+ completion_token_count: int | None = Field(
46
+ default=None, description="Completion token count for LLM calls"
47
+ )
48
+ metadata: dict[str, Any] | None = Field(default=None)
49
+
50
+
51
+ class JobEventUpdateRequest(BaseModel):
52
+ """Request model for updating a job event matching crow-service schema."""
53
+
54
+ amount_acu: float | None = Field(default=None, description="Cost amount in ACUs")
55
+ amount_usd: float | None = Field(default=None, description="Cost amount in USD")
56
+ rate: float | None = Field(default=None, description="Rate per token/call in USD")
57
+ input_token_count: int | None = Field(
58
+ default=None, description="Input token count for LLM calls"
59
+ )
60
+ completion_token_count: int | None = Field(
61
+ default=None, description="Completion token count for LLM calls"
62
+ )
63
+ metadata: dict[str, Any] | None = Field(default=None)
64
+ started_at: datetime | None = Field(
65
+ default=None, description="Start time of the job event"
66
+ )
67
+ ended_at: datetime | None = Field(
68
+ default=None, description="End time of the job event"
69
+ )
70
+
71
+
72
+ class JobEventCreateResponse(BaseModel):
73
+ """Response model for job event creation."""
74
+
75
+ id: UUID = Field(description="UUID of the created job event")
@@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE
28
28
  commit_id: COMMIT_ID
29
29
  __commit_id__: COMMIT_ID
30
30
 
31
- __version__ = version = '0.4.5.dev119'
32
- __version_tuple__ = version_tuple = (0, 4, 5, 'dev119')
31
+ __version__ = version = '0.4.5.dev160'
32
+ __version_tuple__ = version_tuple = (0, 4, 5, 'dev160')
33
33
 
34
34
  __commit_id__ = commit_id = None
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: futurehouse-client
3
- Version: 0.4.5.dev119
3
+ Version: 0.4.5.dev160
4
4
  Summary: A client for interacting with endpoints of the FutureHouse service.
5
5
  Author-email: FutureHouse technical staff <hello@futurehouse.org>
6
6
  License: Apache License
@@ -1,14 +1,15 @@
1
- futurehouse_client/__init__.py,sha256=PvFTkocA-hobsWoDEBEdrUgLIbuVbDs_0nvMdImJmHk,707
1
+ futurehouse_client/__init__.py,sha256=q5cpcuPkhTaueXsySsgWpH0F-2EsRxcdJfP91ze6khU,991
2
2
  futurehouse_client/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
- futurehouse_client/version.py,sha256=ygTkTx_4WMa3DIXWf_ZxjBUe9cI-wOluDVgxQX8thfA,721
3
+ futurehouse_client/version.py,sha256=6BA6oRbUzdnpPhiNHEHGYWEa8NjSzYLRAwSlZ3RVS6Y,721
4
4
  futurehouse_client/clients/__init__.py,sha256=-HXNj-XJ3LRO5XM6MZ709iPs29YpApss0Q2YYg1qMZw,280
5
5
  futurehouse_client/clients/data_storage_methods.py,sha256=f8ZsVicEtO50pRXoPzEB2GpiyqosNofyoW8vJeYvFnM,119266
6
6
  futurehouse_client/clients/job_client.py,sha256=b5gpzulZpxpv9R337r3UKItnMdtd6CGlI1sV3_VQJso,13985
7
- futurehouse_client/clients/rest_client.py,sha256=RdyFEipvADDCHyY5XFy565IoL9-N1myJjF0G8x2wlK8,103183
8
- futurehouse_client/models/__init__.py,sha256=0YlzKGymbY1g4cXxnUc0BUnthTkVBf12bCZlGUcMQqk,701
7
+ futurehouse_client/clients/rest_client.py,sha256=kLCR4dYduwX_16jaOZ26RGCOR2A_6nk2gpBKUqQ-KVI,110247
8
+ futurehouse_client/models/__init__.py,sha256=N1MwDUYonsMN9NdaShsYcJspyL7H756MYj7VWFeD3fk,978
9
9
  futurehouse_client/models/app.py,sha256=UUg17I3zk6cH_7mrdojHGYvQfm_SeDkuUxsPlRyIYz0,31895
10
- futurehouse_client/models/client.py,sha256=3WLS0xdB7CYqHShi_gqyRa6PGj-QvP--0HzD1R93yvY,3868
10
+ futurehouse_client/models/client.py,sha256=n4HD0KStKLm6Ek9nL9ylP-bkK10yzAaD1uIDF83Qp_A,1828
11
11
  futurehouse_client/models/data_storage_methods.py,sha256=cpF2g4y_REECaz--WhaJeLqXA_3m3keRP5XOXiL8GOI,13811
12
+ futurehouse_client/models/job_event.py,sha256=lMrx-lV7BQkKl419ErWZ6Q1EjurmhBFSns0z6zwGaVo,2766
12
13
  futurehouse_client/models/rest.py,sha256=SbeXZSPUCM0lQ_gVUPa64vKzMxuUVgqmJ5YThfDWs8g,4726
13
14
  futurehouse_client/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
14
15
  futurehouse_client/utils/auth.py,sha256=tgWELjKfg8eWme_qdcRmc8TjQN9DVZuHHaVXZNHLchk,2960
@@ -16,8 +17,8 @@ futurehouse_client/utils/general.py,sha256=PIkGLCSA3kUvc6mwR-prEB7YnMdKILOIm6cPo
16
17
  futurehouse_client/utils/module_utils.py,sha256=aFyd-X-pDARXz9GWpn8SSViUVYdSbuy9vSkrzcVIaGI,4955
17
18
  futurehouse_client/utils/monitoring.py,sha256=UjRlufe67kI3VxRHOd5fLtJmlCbVA2Wqwpd4uZhXkQM,8728
18
19
  futurehouse_client/utils/world_model_tools.py,sha256=v2krZGrco0ur2a_pcRMtnQL05SxlIoBXuJ5R1JkQNws,2921
19
- futurehouse_client-0.4.5.dev119.dist-info/licenses/LICENSE,sha256=oQ9ZHjUi-_6GfP3gs14FlPb0OlGwE1QCCKFGnJ4LD2I,11341
20
- futurehouse_client-0.4.5.dev119.dist-info/METADATA,sha256=_GrNdEBxKiRCI4lXWI8WcIBjVRTEMF_FqvvrQcT3l_E,27101
21
- futurehouse_client-0.4.5.dev119.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
22
- futurehouse_client-0.4.5.dev119.dist-info/top_level.txt,sha256=TRuLUCt_qBnggdFHCX4O_BoCu1j2X43lKfIZC-ElwWY,19
23
- futurehouse_client-0.4.5.dev119.dist-info/RECORD,,
20
+ futurehouse_client-0.4.5.dev160.dist-info/licenses/LICENSE,sha256=oQ9ZHjUi-_6GfP3gs14FlPb0OlGwE1QCCKFGnJ4LD2I,11341
21
+ futurehouse_client-0.4.5.dev160.dist-info/METADATA,sha256=ulzDMOtoPKkLAJxL6JPcqSzmuTqOmP5wxiB7l3bm_qM,27101
22
+ futurehouse_client-0.4.5.dev160.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
23
+ futurehouse_client-0.4.5.dev160.dist-info/top_level.txt,sha256=TRuLUCt_qBnggdFHCX4O_BoCu1j2X43lKfIZC-ElwWY,19
24
+ futurehouse_client-0.4.5.dev160.dist-info/RECORD,,