futurehouse-client 0.0.1__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.
- futurehouse_client/__init__.py +12 -0
- futurehouse_client/clients/__init__.py +12 -0
- futurehouse_client/clients/job_client.py +232 -0
- futurehouse_client/clients/rest_client.py +674 -0
- futurehouse_client/models/__init__.py +21 -0
- futurehouse_client/models/app.py +622 -0
- futurehouse_client/models/client.py +72 -0
- futurehouse_client/models/rest.py +19 -0
- futurehouse_client/utils/__init__.py +0 -0
- futurehouse_client/utils/module_utils.py +149 -0
- futurehouse_client-0.0.1.dist-info/METADATA +151 -0
- futurehouse_client-0.0.1.dist-info/RECORD +14 -0
- futurehouse_client-0.0.1.dist-info/WHEEL +5 -0
- futurehouse_client-0.0.1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,19 @@
|
|
1
|
+
from pydantic import BaseModel, JsonValue
|
2
|
+
|
3
|
+
|
4
|
+
class FinalEnvironmentRequest(BaseModel):
|
5
|
+
status: str
|
6
|
+
|
7
|
+
|
8
|
+
class StoreAgentStatePostRequest(BaseModel):
|
9
|
+
agent_id: str
|
10
|
+
step: str
|
11
|
+
state: JsonValue
|
12
|
+
trajectory_timestep: int
|
13
|
+
|
14
|
+
|
15
|
+
class StoreEnvironmentFrameRequest(BaseModel):
|
16
|
+
agent_state_point_in_time: str
|
17
|
+
current_agent_step: str
|
18
|
+
state: JsonValue
|
19
|
+
trajectory_timestep: int
|
File without changes
|
@@ -0,0 +1,149 @@
|
|
1
|
+
import ast
|
2
|
+
import importlib.util
|
3
|
+
import logging
|
4
|
+
import types
|
5
|
+
from pathlib import Path
|
6
|
+
|
7
|
+
logger = logging.getLogger(__name__)
|
8
|
+
|
9
|
+
|
10
|
+
def extract_docstring_from_file(
|
11
|
+
file_path: Path,
|
12
|
+
class_name: str,
|
13
|
+
function_name: str,
|
14
|
+
) -> str | None:
|
15
|
+
"""Extract the docstring for a specific function in a class without importing the module.
|
16
|
+
|
17
|
+
Args:
|
18
|
+
file_path (Path): Path to the Python file.
|
19
|
+
class_name (str): Name of the class containing the function.
|
20
|
+
function_name (str): Name of the function.
|
21
|
+
|
22
|
+
Returns:
|
23
|
+
str: The docstring of the function, or None if not found.
|
24
|
+
|
25
|
+
"""
|
26
|
+
with file_path.open(encoding="utf-8") as file:
|
27
|
+
source = file.read()
|
28
|
+
|
29
|
+
tree = ast.parse(source)
|
30
|
+
|
31
|
+
for node in tree.body:
|
32
|
+
if isinstance(node, ast.ClassDef) and node.name == class_name:
|
33
|
+
for class_node in node.body:
|
34
|
+
if (
|
35
|
+
isinstance(class_node, ast.FunctionDef)
|
36
|
+
and class_node.name == function_name
|
37
|
+
):
|
38
|
+
return ast.get_docstring(class_node)
|
39
|
+
return None
|
40
|
+
|
41
|
+
|
42
|
+
def load_module(file_path: Path, package_name: str) -> types.ModuleType | None:
|
43
|
+
"""Load a Python file as part of a package.
|
44
|
+
|
45
|
+
Args:
|
46
|
+
file_path (Path): Path to the Python file.
|
47
|
+
package_name (str): Full package name (e.g., "envs.dummy_env.env").
|
48
|
+
|
49
|
+
Returns:
|
50
|
+
Module: The loaded module.
|
51
|
+
|
52
|
+
"""
|
53
|
+
spec = importlib.util.spec_from_file_location(package_name, file_path)
|
54
|
+
if not spec:
|
55
|
+
return None
|
56
|
+
return importlib.util.module_from_spec(spec)
|
57
|
+
|
58
|
+
|
59
|
+
def fetch_environment_function_docstring(
|
60
|
+
environment_name: str,
|
61
|
+
directory: Path,
|
62
|
+
function_name: str,
|
63
|
+
) -> str | None:
|
64
|
+
"""Retrieve the docstring for a specific function within a class, identified by an environment-style module path.
|
65
|
+
|
66
|
+
The function attempts the following:
|
67
|
+
1. Parse the file inferred from the environment name (e.g., environment "my_env.module.MyClass"
|
68
|
+
attempts to find `directory/my_env/module.py`).
|
69
|
+
2. If not found there, recursively searches all `.py` files under `directory` for a class
|
70
|
+
with the given name containing the specified function.
|
71
|
+
|
72
|
+
Args:
|
73
|
+
environment_name (str): The environment name in dot notation
|
74
|
+
(e.g., "package.module.ClassName").
|
75
|
+
directory (Path): The base directory containing the environment files.
|
76
|
+
function_name (str): The name of the function to retrieve the docstring for.
|
77
|
+
|
78
|
+
Raises:
|
79
|
+
ValueError: If multiple classes with the same name are found in different files
|
80
|
+
(making the intended class ambiguous), an error is raised requiring
|
81
|
+
disambiguation.
|
82
|
+
|
83
|
+
Returns:
|
84
|
+
str | None: The docstring of the specified function, or None if not found.
|
85
|
+
|
86
|
+
"""
|
87
|
+
parts = environment_name.split(".")
|
88
|
+
class_name = parts[-1]
|
89
|
+
|
90
|
+
guessed_file_path = directory / ("/".join(parts[1:-1]) + ".py")
|
91
|
+
if guessed_file_path.exists():
|
92
|
+
doc = extract_docstring_from_file(guessed_file_path, class_name, function_name)
|
93
|
+
if doc:
|
94
|
+
return doc
|
95
|
+
|
96
|
+
matches = []
|
97
|
+
for py_file in directory.rglob("*.py"):
|
98
|
+
doc = extract_docstring_from_file(py_file, class_name, function_name)
|
99
|
+
if doc:
|
100
|
+
matches.append((py_file, doc))
|
101
|
+
|
102
|
+
if not matches:
|
103
|
+
return None
|
104
|
+
if len(matches) == 1:
|
105
|
+
return matches[0][1]
|
106
|
+
match_paths = [str(m[0]) for m in matches]
|
107
|
+
raise ValueError(
|
108
|
+
f"Multiple classes named '{class_name}' found in:\n"
|
109
|
+
+ "\n".join(match_paths)
|
110
|
+
+ "\nPlease specify a more explicit path or ensure unique class names.",
|
111
|
+
)
|
112
|
+
|
113
|
+
|
114
|
+
class OrganizationSelector:
|
115
|
+
@staticmethod
|
116
|
+
def select_organization(organizations: list[str]) -> str | None:
|
117
|
+
"""Prompts the user to select an organization from a list.
|
118
|
+
|
119
|
+
Args:
|
120
|
+
organizations: List of organization names/IDs
|
121
|
+
|
122
|
+
Returns:
|
123
|
+
Selected organization name/ID or None if selection was cancelled
|
124
|
+
|
125
|
+
"""
|
126
|
+
if not organizations:
|
127
|
+
raise ValueError("User does not belong to any organizations")
|
128
|
+
|
129
|
+
if len(organizations) == 1:
|
130
|
+
logger.debug(f"Only one organization available: {organizations[0]}")
|
131
|
+
return organizations[0]
|
132
|
+
|
133
|
+
print("\nAvailable organizations:")
|
134
|
+
for idx, org in enumerate(organizations, 1):
|
135
|
+
print(f"[{idx}]. {org}")
|
136
|
+
|
137
|
+
while True:
|
138
|
+
try:
|
139
|
+
selection = input("\nSelect an organization number (or 'q' to quit): ")
|
140
|
+
|
141
|
+
if selection.lower().strip() == "q":
|
142
|
+
return None
|
143
|
+
|
144
|
+
idx = int(selection)
|
145
|
+
if 1 <= idx <= len(organizations):
|
146
|
+
return organizations[idx - 1]
|
147
|
+
print(f"Please enter a number between 1 and {len(organizations)}")
|
148
|
+
except ValueError:
|
149
|
+
print("Please enter a valid number or 'q' to quit")
|
@@ -0,0 +1,151 @@
|
|
1
|
+
Metadata-Version: 2.4
|
2
|
+
Name: futurehouse-client
|
3
|
+
Version: 0.0.1
|
4
|
+
Summary: A client for interacting with endpoints of the FutureHouse service.
|
5
|
+
Author-email: FutureHouse technical staff <hello@futurehouse.org>
|
6
|
+
Classifier: Operating System :: OS Independent
|
7
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
8
|
+
Classifier: Programming Language :: Python :: 3.11
|
9
|
+
Classifier: Programming Language :: Python :: 3.12
|
10
|
+
Classifier: Programming Language :: Python
|
11
|
+
Requires-Python: <3.13,>=3.11
|
12
|
+
Description-Content-Type: text/markdown
|
13
|
+
Requires-Dist: cloudpickle
|
14
|
+
Requires-Dist: dm-tree<0.1.9
|
15
|
+
Requires-Dist: fhaviary
|
16
|
+
Requires-Dist: httpx
|
17
|
+
Requires-Dist: ldp>=0.22.0
|
18
|
+
Requires-Dist: pydantic
|
19
|
+
Requires-Dist: python-dotenv
|
20
|
+
Requires-Dist: tenacity
|
21
|
+
Provides-Extra: dev
|
22
|
+
Requires-Dist: black; extra == "dev"
|
23
|
+
Requires-Dist: jupyter; extra == "dev"
|
24
|
+
Requires-Dist: jupyterlab; extra == "dev"
|
25
|
+
Requires-Dist: mypy; extra == "dev"
|
26
|
+
Requires-Dist: notebook; extra == "dev"
|
27
|
+
Requires-Dist: pre-commit; extra == "dev"
|
28
|
+
Requires-Dist: pylint; extra == "dev"
|
29
|
+
Requires-Dist: pylint-per-file-ignores; extra == "dev"
|
30
|
+
Requires-Dist: pylint-pydantic; extra == "dev"
|
31
|
+
Requires-Dist: pytest; extra == "dev"
|
32
|
+
Requires-Dist: pytest-rerunfailures; extra == "dev"
|
33
|
+
Requires-Dist: pytest-subtests; extra == "dev"
|
34
|
+
Requires-Dist: pytest-timeout; extra == "dev"
|
35
|
+
Requires-Dist: pytest-xdist; extra == "dev"
|
36
|
+
Requires-Dist: ruff; extra == "dev"
|
37
|
+
Requires-Dist: setuptools_scm; extra == "dev"
|
38
|
+
|
39
|
+
# crow-client
|
40
|
+
|
41
|
+
A client for interacting with endpoints of the FutureHouse crow service.
|
42
|
+
|
43
|
+
## Installation
|
44
|
+
|
45
|
+
```bash
|
46
|
+
uv pip install crow-client
|
47
|
+
```
|
48
|
+
|
49
|
+
## Usage
|
50
|
+
|
51
|
+
The CrowClient provides simple functions to deploy and monitor your crow.
|
52
|
+
|
53
|
+
In the case of environments the deployment looks like this
|
54
|
+
|
55
|
+
```python
|
56
|
+
from pathlib import Path
|
57
|
+
from crow_client import CrowClient
|
58
|
+
from crow_client.models import CrowDeploymentConfig
|
59
|
+
|
60
|
+
client = CrowClient()
|
61
|
+
|
62
|
+
crow = CrowDeploymentConfig(
|
63
|
+
path=Path("../envs/dummy_env"),
|
64
|
+
environment="dummy_env.env.DummyEnv",
|
65
|
+
requires_aviary_internal=False,
|
66
|
+
environment_variables={"SAMPLE_ENV_VAR": "sample_val"},
|
67
|
+
agent="ldp.agent.SimpleAgent",
|
68
|
+
)
|
69
|
+
|
70
|
+
client.create_crow(crow)
|
71
|
+
|
72
|
+
# checks the status
|
73
|
+
client.get_build_status()
|
74
|
+
```
|
75
|
+
|
76
|
+
For functional environments we don't need to pass the file path and can pass the environment builder instead
|
77
|
+
|
78
|
+
```python
|
79
|
+
from aviary.core import fenv
|
80
|
+
import numpy as np
|
81
|
+
|
82
|
+
|
83
|
+
def function_to_use_here(inpste: str):
|
84
|
+
a = np.array(np.asmatrix("1 2; 3 4"))
|
85
|
+
return inpste
|
86
|
+
|
87
|
+
|
88
|
+
@fenv.start()
|
89
|
+
def my_env(topic: str):
|
90
|
+
"""
|
91
|
+
Here is the doc string describing the task.
|
92
|
+
"""
|
93
|
+
a = np.array(np.asmatrix("1 2; 3 4"))
|
94
|
+
return f"Write a sad story about {topic}", {"chosen_topic": topic}
|
95
|
+
|
96
|
+
|
97
|
+
@my_env.tool()
|
98
|
+
def print_story(story: str, state) -> None:
|
99
|
+
"""Print the story and complete the task"""
|
100
|
+
print(story)
|
101
|
+
print(function_to_use_here(story))
|
102
|
+
state.reward = 1
|
103
|
+
state.done = True
|
104
|
+
|
105
|
+
|
106
|
+
from crow_client import CrowClient
|
107
|
+
from crow_client.models import CrowDeploymentConfig, Stage
|
108
|
+
from crow_client.clients.rest_client import generate_requirements
|
109
|
+
|
110
|
+
client = CrowClient(stage=Stage.LOCAL)
|
111
|
+
|
112
|
+
crow = CrowDeploymentConfig(
|
113
|
+
functional_environment=my_env,
|
114
|
+
environment="my_env",
|
115
|
+
requires_aviary_internal=False,
|
116
|
+
environment_variables={"SAMPLE_ENV_VAR": "sample_val"},
|
117
|
+
agent="ldp.agent.SimpleAgent",
|
118
|
+
requirements=generate_requirements(my_env, globals()),
|
119
|
+
)
|
120
|
+
|
121
|
+
client.create_crow(crow)
|
122
|
+
```
|
123
|
+
|
124
|
+
This client also provides functions that let you send tasks to an existing crow:
|
125
|
+
|
126
|
+
```python
|
127
|
+
from crow_client import CrowJob
|
128
|
+
|
129
|
+
client = CrowClient()
|
130
|
+
|
131
|
+
job_data = {"name": "your-job-name", "query": "your task"}
|
132
|
+
client.create_job(job_data)
|
133
|
+
|
134
|
+
# checks the status
|
135
|
+
client.get_job()
|
136
|
+
```
|
137
|
+
|
138
|
+
The CrowJobClient provides an interface for managing environment states and agent interactions in the FutureHouse crow service.
|
139
|
+
|
140
|
+
```python
|
141
|
+
from crow_client import CrowJobClient
|
142
|
+
from crow_client.models.app import Stage
|
143
|
+
|
144
|
+
client = CrowJobClient(
|
145
|
+
environment="your_environment_name",
|
146
|
+
agent="your_agent_id",
|
147
|
+
auth_token="your_auth_token",
|
148
|
+
base_uri=Stage.DEV,
|
149
|
+
trajectory_id=None,
|
150
|
+
)
|
151
|
+
```
|
@@ -0,0 +1,14 @@
|
|
1
|
+
futurehouse_client/__init__.py,sha256=SaaBeLHh4K81dONiXxowhsgM-Zr12t1g-phDgwi5iWc,316
|
2
|
+
futurehouse_client/clients/__init__.py,sha256=OWJtj6WNJ1GvzA5v-Z8yiKlta47k4dprih-9n7mag40,300
|
3
|
+
futurehouse_client/clients/job_client.py,sha256=M0siUz-5Ffri4_fhXXeBb54qnELwMa1hYzosS8bx3ek,8833
|
4
|
+
futurehouse_client/clients/rest_client.py,sha256=dEes4lP0JOsuRoFgjhU9uXSogBGFOWgSw8ZfopUJ4w8,24734
|
5
|
+
futurehouse_client/models/__init__.py,sha256=VBKYb_2kmxtDwp9LvTL7BEFyioekggQsrQg5I_dhZoI,342
|
6
|
+
futurehouse_client/models/app.py,sha256=_MO31xvKJaAztWz4KrV13ty4POg-RuWrGgqmvS9w2bc,22298
|
7
|
+
futurehouse_client/models/client.py,sha256=n4HD0KStKLm6Ek9nL9ylP-bkK10yzAaD1uIDF83Qp_A,1828
|
8
|
+
futurehouse_client/models/rest.py,sha256=W-wNFTN7HALYFFphw-RQYRMm6_TSa1cl4T-mZ1msk90,393
|
9
|
+
futurehouse_client/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
10
|
+
futurehouse_client/utils/module_utils.py,sha256=aFyd-X-pDARXz9GWpn8SSViUVYdSbuy9vSkrzcVIaGI,4955
|
11
|
+
futurehouse_client-0.0.1.dist-info/METADATA,sha256=L7c7KYAcl5HgkRBNmjHS0xnWmZb4QHwmAS3-vrLmtsw,4076
|
12
|
+
futurehouse_client-0.0.1.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
|
13
|
+
futurehouse_client-0.0.1.dist-info/top_level.txt,sha256=TRuLUCt_qBnggdFHCX4O_BoCu1j2X43lKfIZC-ElwWY,19
|
14
|
+
futurehouse_client-0.0.1.dist-info/RECORD,,
|
@@ -0,0 +1 @@
|
|
1
|
+
futurehouse_client
|