openenv-agent 0.1.0__tar.gz
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.
- openenv_agent-0.1.0/LICENSE +21 -0
- openenv_agent-0.1.0/PKG-INFO +75 -0
- openenv_agent-0.1.0/README.md +48 -0
- openenv_agent-0.1.0/pyproject.toml +43 -0
- openenv_agent-0.1.0/setup.cfg +4 -0
- openenv_agent-0.1.0/src/openenv_agent/__init__.py +14 -0
- openenv_agent-0.1.0/src/openenv_agent/agent.py +64 -0
- openenv_agent-0.1.0/src/openenv_agent/cli.py +170 -0
- openenv_agent-0.1.0/src/openenv_agent/client.py +209 -0
- openenv_agent-0.1.0/src/openenv_agent/env_loader.py +76 -0
- openenv_agent-0.1.0/src/openenv_agent/moderation_agent.py +192 -0
- openenv_agent-0.1.0/src/openenv_agent.egg-info/PKG-INFO +75 -0
- openenv_agent-0.1.0/src/openenv_agent.egg-info/SOURCES.txt +15 -0
- openenv_agent-0.1.0/src/openenv_agent.egg-info/dependency_links.txt +1 -0
- openenv_agent-0.1.0/src/openenv_agent.egg-info/entry_points.txt +2 -0
- openenv_agent-0.1.0/src/openenv_agent.egg-info/requires.txt +9 -0
- openenv_agent-0.1.0/src/openenv_agent.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Croma
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: openenv-agent
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Agent client for OpenEnv environments - connects RL agents to OpenEnv servers for inference and training
|
|
5
|
+
Author: Croma
|
|
6
|
+
License: MIT
|
|
7
|
+
Keywords: openenv,reinforcement-learning,agent,rl,content-moderation
|
|
8
|
+
Classifier: Development Status :: 3 - Alpha
|
|
9
|
+
Classifier: Intended Audience :: Developers
|
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Requires-Python: >=3.10
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
License-File: LICENSE
|
|
18
|
+
Requires-Dist: openai>=1.3.0
|
|
19
|
+
Requires-Dist: httpx>=0.25.0
|
|
20
|
+
Requires-Dist: pyyaml>=6.0
|
|
21
|
+
Requires-Dist: click>=8.0.0
|
|
22
|
+
Provides-Extra: dev
|
|
23
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
24
|
+
Requires-Dist: pytest-asyncio>=0.21.0; extra == "dev"
|
|
25
|
+
Requires-Dist: ruff>=0.1.0; extra == "dev"
|
|
26
|
+
Dynamic: license-file
|
|
27
|
+
|
|
28
|
+
# OpenEnv Agent
|
|
29
|
+
|
|
30
|
+
Python client library for connecting RL agents to OpenEnv servers.
|
|
31
|
+
|
|
32
|
+
## Installation
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
pip install openenv-agent
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Quick Start
|
|
39
|
+
|
|
40
|
+
```python
|
|
41
|
+
from openenv_agent import OpenEnvClient, ModerationAgent
|
|
42
|
+
|
|
43
|
+
# Connect to an OpenEnv server
|
|
44
|
+
client = OpenEnvClient(base_url="http://localhost:8000")
|
|
45
|
+
|
|
46
|
+
# Reset environment
|
|
47
|
+
obs = client.reset()
|
|
48
|
+
|
|
49
|
+
# Use the moderation agent
|
|
50
|
+
agent = ModerationAgent()
|
|
51
|
+
action = agent.predict(obs)
|
|
52
|
+
|
|
53
|
+
# Step the environment
|
|
54
|
+
next_obs, reward, done, info = client.step(action)
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## CLI Usage
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
# Run agent against a server
|
|
61
|
+
openenv-agent run http://localhost:8000
|
|
62
|
+
|
|
63
|
+
# Interactive mode
|
|
64
|
+
openenv-agent interactive http://localhost:8000
|
|
65
|
+
|
|
66
|
+
# Evaluate on a dataset
|
|
67
|
+
openenv-agent eval http://localhost:8000 --dataset ./data.json
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Features
|
|
71
|
+
|
|
72
|
+
- Async/sync OpenEnv client with Gymnasium-style API
|
|
73
|
+
- Built-in ModerationAgent for content moderation environments
|
|
74
|
+
- Environment loader from openenv.yaml configs
|
|
75
|
+
- CLI tool for easy server interaction
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# OpenEnv Agent
|
|
2
|
+
|
|
3
|
+
Python client library for connecting RL agents to OpenEnv servers.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install openenv-agent
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Quick Start
|
|
12
|
+
|
|
13
|
+
```python
|
|
14
|
+
from openenv_agent import OpenEnvClient, ModerationAgent
|
|
15
|
+
|
|
16
|
+
# Connect to an OpenEnv server
|
|
17
|
+
client = OpenEnvClient(base_url="http://localhost:8000")
|
|
18
|
+
|
|
19
|
+
# Reset environment
|
|
20
|
+
obs = client.reset()
|
|
21
|
+
|
|
22
|
+
# Use the moderation agent
|
|
23
|
+
agent = ModerationAgent()
|
|
24
|
+
action = agent.predict(obs)
|
|
25
|
+
|
|
26
|
+
# Step the environment
|
|
27
|
+
next_obs, reward, done, info = client.step(action)
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## CLI Usage
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
# Run agent against a server
|
|
34
|
+
openenv-agent run http://localhost:8000
|
|
35
|
+
|
|
36
|
+
# Interactive mode
|
|
37
|
+
openenv-agent interactive http://localhost:8000
|
|
38
|
+
|
|
39
|
+
# Evaluate on a dataset
|
|
40
|
+
openenv-agent eval http://localhost:8000 --dataset ./data.json
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Features
|
|
44
|
+
|
|
45
|
+
- Async/sync OpenEnv client with Gymnasium-style API
|
|
46
|
+
- Built-in ModerationAgent for content moderation environments
|
|
47
|
+
- Environment loader from openenv.yaml configs
|
|
48
|
+
- CLI tool for easy server interaction
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "openenv-agent"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Agent client for OpenEnv environments - connects RL agents to OpenEnv servers for inference and training"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.10"
|
|
7
|
+
license = {text = "MIT"}
|
|
8
|
+
authors = [
|
|
9
|
+
{name = "Croma"}
|
|
10
|
+
]
|
|
11
|
+
keywords = ["openenv", "reinforcement-learning", "agent", "rl", "content-moderation"]
|
|
12
|
+
classifiers = [
|
|
13
|
+
"Development Status :: 3 - Alpha",
|
|
14
|
+
"Intended Audience :: Developers",
|
|
15
|
+
"License :: OSI Approved :: MIT License",
|
|
16
|
+
"Programming Language :: Python :: 3",
|
|
17
|
+
"Programming Language :: Python :: 3.10",
|
|
18
|
+
"Programming Language :: Python :: 3.11",
|
|
19
|
+
"Programming Language :: Python :: 3.12",
|
|
20
|
+
]
|
|
21
|
+
dependencies = [
|
|
22
|
+
"openai>=1.3.0",
|
|
23
|
+
"httpx>=0.25.0",
|
|
24
|
+
"pyyaml>=6.0",
|
|
25
|
+
"click>=8.0.0",
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
[project.optional-dependencies]
|
|
29
|
+
dev = [
|
|
30
|
+
"pytest>=7.0",
|
|
31
|
+
"pytest-asyncio>=0.21.0",
|
|
32
|
+
"ruff>=0.1.0",
|
|
33
|
+
]
|
|
34
|
+
|
|
35
|
+
[project.scripts]
|
|
36
|
+
openenv-agent = "openenv_agent.cli:main"
|
|
37
|
+
|
|
38
|
+
[build-system]
|
|
39
|
+
requires = ["setuptools>=61.0", "wheel"]
|
|
40
|
+
build-backend = "setuptools.build_meta"
|
|
41
|
+
|
|
42
|
+
[tool.setuptools.packages.find]
|
|
43
|
+
where = ["src"]
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"""OpenEnv Agent - Client library for OpenEnv RL environments."""
|
|
2
|
+
|
|
3
|
+
from openenv_agent.client import OpenEnvClient
|
|
4
|
+
from openenv_agent.agent import BaseAgent
|
|
5
|
+
from openenv_agent.moderation_agent import ModerationAgent
|
|
6
|
+
from openenv_agent.env_loader import load_env_from_yaml
|
|
7
|
+
|
|
8
|
+
__version__ = "0.1.0"
|
|
9
|
+
__all__ = [
|
|
10
|
+
"OpenEnvClient",
|
|
11
|
+
"BaseAgent",
|
|
12
|
+
"ModerationAgent",
|
|
13
|
+
"load_env_from_yaml",
|
|
14
|
+
]
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"""Base agent class for OpenEnv."""
|
|
2
|
+
|
|
3
|
+
from abc import ABC, abstractmethod
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from typing import Any, Dict, List, Optional
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@dataclass
|
|
9
|
+
class AgentConfig:
|
|
10
|
+
"""Configuration for an agent."""
|
|
11
|
+
name: str = "BaseAgent"
|
|
12
|
+
temperature: float = 0.0
|
|
13
|
+
max_tokens: int = 200
|
|
14
|
+
timeout: float = 10.0
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class BaseAgent(ABC):
|
|
18
|
+
"""
|
|
19
|
+
Abstract base class for OpenEnv agents.
|
|
20
|
+
|
|
21
|
+
Subclasses must implement the `predict` method.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
def __init__(self, config: Optional[AgentConfig] = None):
|
|
25
|
+
"""
|
|
26
|
+
Initialize the agent.
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
config: Agent configuration
|
|
30
|
+
"""
|
|
31
|
+
self.config = config or AgentConfig()
|
|
32
|
+
|
|
33
|
+
@abstractmethod
|
|
34
|
+
def predict(self, observation: Dict[str, Any]) -> Dict[str, Any]:
|
|
35
|
+
"""
|
|
36
|
+
Given an observation, return an action.
|
|
37
|
+
|
|
38
|
+
Args:
|
|
39
|
+
observation: Environment observation dict
|
|
40
|
+
|
|
41
|
+
Returns:
|
|
42
|
+
Action dict to send to the environment
|
|
43
|
+
"""
|
|
44
|
+
pass
|
|
45
|
+
|
|
46
|
+
def reset(self):
|
|
47
|
+
"""Reset agent state between episodes."""
|
|
48
|
+
pass
|
|
49
|
+
|
|
50
|
+
def train(self, episode_data: List[Dict[str, Any]]) -> Dict[str, float]:
|
|
51
|
+
"""
|
|
52
|
+
Train the agent on episode data.
|
|
53
|
+
|
|
54
|
+
Args:
|
|
55
|
+
episode_data: List of (observation, action, reward, done, info) tuples
|
|
56
|
+
|
|
57
|
+
Returns:
|
|
58
|
+
Dict of training metrics
|
|
59
|
+
"""
|
|
60
|
+
# Default implementation - override for actual training
|
|
61
|
+
return {"status": "no-op"}
|
|
62
|
+
|
|
63
|
+
def __repr__(self) -> str:
|
|
64
|
+
return f"{self.__class__.__name__}(name={self.config.name})"
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
"""CLI for openenv-agent."""
|
|
2
|
+
|
|
3
|
+
import sys
|
|
4
|
+
import logging
|
|
5
|
+
from typing import Optional
|
|
6
|
+
|
|
7
|
+
import click
|
|
8
|
+
|
|
9
|
+
from openenv_agent.client import OpenEnvClient
|
|
10
|
+
from openenv_agent.moderation_agent import ModerationAgent
|
|
11
|
+
|
|
12
|
+
logging.basicConfig(
|
|
13
|
+
level=logging.INFO,
|
|
14
|
+
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s"
|
|
15
|
+
)
|
|
16
|
+
logger = logging.getLogger(__name__)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@click.group()
|
|
20
|
+
@click.version_option(version="0.1.0")
|
|
21
|
+
def main():
|
|
22
|
+
"""OpenEnv Agent - Connect RL agents to OpenEnv servers."""
|
|
23
|
+
pass
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@main.command()
|
|
27
|
+
@click.argument("url")
|
|
28
|
+
@click.option("--max-steps", type=int, default=None, help="Max steps to run")
|
|
29
|
+
def run(url: str, max_steps: Optional[int]):
|
|
30
|
+
"""Run the moderation agent against an OpenEnv server."""
|
|
31
|
+
client = OpenEnvClient(base_url=url)
|
|
32
|
+
agent = ModerationAgent()
|
|
33
|
+
|
|
34
|
+
click.echo(f"[START] Connecting to {url}")
|
|
35
|
+
|
|
36
|
+
try:
|
|
37
|
+
obs = client.reset()
|
|
38
|
+
click.echo(f"[RESET] Initial observation received")
|
|
39
|
+
|
|
40
|
+
step = 0
|
|
41
|
+
total_reward = 0.0
|
|
42
|
+
|
|
43
|
+
while True:
|
|
44
|
+
if max_steps and step >= max_steps:
|
|
45
|
+
break
|
|
46
|
+
|
|
47
|
+
action = agent.predict(obs)
|
|
48
|
+
next_obs, reward, done, info = client.step(action)
|
|
49
|
+
|
|
50
|
+
total_reward += reward
|
|
51
|
+
step += 1
|
|
52
|
+
|
|
53
|
+
click.echo(
|
|
54
|
+
f"[STEP] step={step} action={action['decision']} "
|
|
55
|
+
f"reward={reward:.2f} done={done}"
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
if done:
|
|
59
|
+
break
|
|
60
|
+
|
|
61
|
+
obs = next_obs
|
|
62
|
+
|
|
63
|
+
avg_reward = total_reward / max(step, 1)
|
|
64
|
+
click.echo(f"[END] steps={step} avg_reward={avg_reward:.4f}")
|
|
65
|
+
|
|
66
|
+
except Exception as e:
|
|
67
|
+
click.echo(f"[ERROR] {e}", err=True)
|
|
68
|
+
sys.exit(1)
|
|
69
|
+
finally:
|
|
70
|
+
client.close()
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
@main.command()
|
|
74
|
+
@click.argument("url")
|
|
75
|
+
def interactive(url: str):
|
|
76
|
+
"""Run the agent in interactive debug mode."""
|
|
77
|
+
client = OpenEnvClient(base_url=url)
|
|
78
|
+
agent = ModerationAgent()
|
|
79
|
+
|
|
80
|
+
click.echo(f"Interactive mode - connecting to {url}")
|
|
81
|
+
click.echo("Press Ctrl+C to exit\n")
|
|
82
|
+
|
|
83
|
+
try:
|
|
84
|
+
obs = client.reset()
|
|
85
|
+
click.echo(f"Initial observation:\n{obs}\n")
|
|
86
|
+
|
|
87
|
+
step = 0
|
|
88
|
+
while True:
|
|
89
|
+
click.echo(f"--- Step {step} ---")
|
|
90
|
+
action = agent.predict(obs)
|
|
91
|
+
click.echo(f"Action: {action}")
|
|
92
|
+
|
|
93
|
+
next_obs, reward, done, info = client.step(action)
|
|
94
|
+
click.echo(f"Reward: {reward:.2f}, Done: {done}")
|
|
95
|
+
|
|
96
|
+
if done:
|
|
97
|
+
click.echo("Episode complete!")
|
|
98
|
+
break
|
|
99
|
+
|
|
100
|
+
if click.confirm("Continue?"):
|
|
101
|
+
obs = next_obs
|
|
102
|
+
step += 1
|
|
103
|
+
else:
|
|
104
|
+
break
|
|
105
|
+
|
|
106
|
+
except KeyboardInterrupt:
|
|
107
|
+
click.echo("\nExiting...")
|
|
108
|
+
finally:
|
|
109
|
+
client.close()
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
@main.command()
|
|
113
|
+
@click.argument("url")
|
|
114
|
+
@click.option("--dataset", type=click.Path(), help="Path to dataset JSON")
|
|
115
|
+
def eval(url: str, dataset: Optional[str]):
|
|
116
|
+
"""Evaluate the agent on a dataset."""
|
|
117
|
+
client = OpenEnvClient(base_url=url)
|
|
118
|
+
agent = ModerationAgent()
|
|
119
|
+
|
|
120
|
+
click.echo(f"[EVAL] Starting evaluation against {url}")
|
|
121
|
+
|
|
122
|
+
total_reward = 0.0
|
|
123
|
+
total_steps = 0
|
|
124
|
+
episodes = 0
|
|
125
|
+
|
|
126
|
+
try:
|
|
127
|
+
while True:
|
|
128
|
+
obs = client.reset()
|
|
129
|
+
step = 0
|
|
130
|
+
episode_reward = 0.0
|
|
131
|
+
|
|
132
|
+
while True:
|
|
133
|
+
action = agent.predict(obs)
|
|
134
|
+
next_obs, reward, done, info = client.step(action)
|
|
135
|
+
episode_reward += reward
|
|
136
|
+
step += 1
|
|
137
|
+
|
|
138
|
+
if done:
|
|
139
|
+
break
|
|
140
|
+
|
|
141
|
+
obs = next_obs
|
|
142
|
+
|
|
143
|
+
total_reward += episode_reward
|
|
144
|
+
total_steps += step
|
|
145
|
+
episodes += 1
|
|
146
|
+
|
|
147
|
+
click.echo(f"[EPISODE] {episodes}: steps={step} reward={episode_reward:.2f}")
|
|
148
|
+
|
|
149
|
+
# In real eval, would check against ground truth here
|
|
150
|
+
if dataset:
|
|
151
|
+
# TODO: evaluate against dataset
|
|
152
|
+
pass
|
|
153
|
+
|
|
154
|
+
if episodes >= 10: # Eval on 10 episodes by default
|
|
155
|
+
break
|
|
156
|
+
|
|
157
|
+
click.echo(f"\n[EVAL COMPLETE]")
|
|
158
|
+
click.echo(f"Episodes: {episodes}")
|
|
159
|
+
click.echo(f"Total steps: {total_steps}")
|
|
160
|
+
click.echo(f"Average reward: {total_reward / episodes:.4f}")
|
|
161
|
+
|
|
162
|
+
except Exception as e:
|
|
163
|
+
click.echo(f"[ERROR] {e}", err=True)
|
|
164
|
+
sys.exit(1)
|
|
165
|
+
finally:
|
|
166
|
+
client.close()
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
if __name__ == "__main__":
|
|
170
|
+
main()
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
"""OpenEnv client for connecting to OpenEnv servers."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import logging
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from typing import Any, Optional, Tuple
|
|
7
|
+
|
|
8
|
+
import httpx
|
|
9
|
+
|
|
10
|
+
logger = logging.getLogger(__name__)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@dataclass
|
|
14
|
+
class EnvResponse:
|
|
15
|
+
"""Response from an OpenEnv environment step."""
|
|
16
|
+
observation: dict
|
|
17
|
+
reward: float
|
|
18
|
+
done: bool
|
|
19
|
+
info: dict
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class OpenEnvClient:
|
|
23
|
+
"""
|
|
24
|
+
Client for connecting to OpenEnv servers.
|
|
25
|
+
|
|
26
|
+
Provides a Gymnasium-style API (reset, step, state) for interacting
|
|
27
|
+
with OpenEnv RL environments over HTTP.
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
def __init__(
|
|
31
|
+
self,
|
|
32
|
+
base_url: str,
|
|
33
|
+
timeout: float = 30.0,
|
|
34
|
+
api_key: Optional[str] = None,
|
|
35
|
+
):
|
|
36
|
+
"""
|
|
37
|
+
Initialize the OpenEnv client.
|
|
38
|
+
|
|
39
|
+
Args:
|
|
40
|
+
base_url: Base URL of the OpenEnv server (e.g., "http://localhost:8000")
|
|
41
|
+
timeout: Request timeout in seconds
|
|
42
|
+
api_key: Optional API key for authentication
|
|
43
|
+
"""
|
|
44
|
+
self.base_url = base_url.rstrip("/")
|
|
45
|
+
self.timeout = timeout
|
|
46
|
+
self._client = httpx.Client(timeout=timeout)
|
|
47
|
+
if api_key:
|
|
48
|
+
self._client.headers["Authorization"] = f"Bearer {api_key}"
|
|
49
|
+
|
|
50
|
+
def reset(self) -> dict:
|
|
51
|
+
"""
|
|
52
|
+
Reset the environment and return the initial observation.
|
|
53
|
+
|
|
54
|
+
Returns:
|
|
55
|
+
dict: Initial observation from the environment
|
|
56
|
+
"""
|
|
57
|
+
try:
|
|
58
|
+
response = self._client.post(f"{self.base_url}/reset")
|
|
59
|
+
response.raise_for_status()
|
|
60
|
+
data = response.json()
|
|
61
|
+
return data.get("observation", data)
|
|
62
|
+
except httpx.HTTPError as e:
|
|
63
|
+
logger.error(f"Failed to reset environment: {e}")
|
|
64
|
+
raise
|
|
65
|
+
|
|
66
|
+
def step(self, action: Any) -> Tuple[dict, float, bool, dict]:
|
|
67
|
+
"""
|
|
68
|
+
Execute an action in the environment.
|
|
69
|
+
|
|
70
|
+
Args:
|
|
71
|
+
action: Action to take (dict or object with as_dict method)
|
|
72
|
+
|
|
73
|
+
Returns:
|
|
74
|
+
Tuple of (observation, reward, done, info)
|
|
75
|
+
"""
|
|
76
|
+
if hasattr(action, "as_dict"):
|
|
77
|
+
action_dict = action.as_dict()
|
|
78
|
+
elif isinstance(action, dict):
|
|
79
|
+
action_dict = action
|
|
80
|
+
else:
|
|
81
|
+
raise ValueError(f"Action must be dict or have as_dict() method, got {type(action)}")
|
|
82
|
+
|
|
83
|
+
try:
|
|
84
|
+
response = self._client.post(
|
|
85
|
+
f"{self.base_url}/step",
|
|
86
|
+
json={"action": action_dict},
|
|
87
|
+
)
|
|
88
|
+
response.raise_for_status()
|
|
89
|
+
data = response.json()
|
|
90
|
+
return (
|
|
91
|
+
data.get("observation", {}),
|
|
92
|
+
data.get("reward", 0.0),
|
|
93
|
+
data.get("done", False),
|
|
94
|
+
data.get("info", {}),
|
|
95
|
+
)
|
|
96
|
+
except httpx.HTTPError as e:
|
|
97
|
+
logger.error(f"Failed to step environment: {e}")
|
|
98
|
+
raise
|
|
99
|
+
|
|
100
|
+
def state(self) -> dict:
|
|
101
|
+
"""
|
|
102
|
+
Get the current state of the environment without taking an action.
|
|
103
|
+
|
|
104
|
+
Returns:
|
|
105
|
+
dict: Current environment state
|
|
106
|
+
"""
|
|
107
|
+
try:
|
|
108
|
+
response = self._client.get(f"{self.base_url}/state")
|
|
109
|
+
response.raise_for_status()
|
|
110
|
+
return response.json()
|
|
111
|
+
except httpx.HTTPError as e:
|
|
112
|
+
logger.error(f"Failed to get state: {e}")
|
|
113
|
+
raise
|
|
114
|
+
|
|
115
|
+
def health(self) -> bool:
|
|
116
|
+
"""
|
|
117
|
+
Check if the environment server is healthy.
|
|
118
|
+
|
|
119
|
+
Returns:
|
|
120
|
+
bool: True if server is healthy, False otherwise
|
|
121
|
+
"""
|
|
122
|
+
try:
|
|
123
|
+
response = self._client.get(f"{self.base_url}/health")
|
|
124
|
+
return response.status_code == 200
|
|
125
|
+
except httpx.HTTPError:
|
|
126
|
+
return False
|
|
127
|
+
|
|
128
|
+
def close(self):
|
|
129
|
+
"""Close the HTTP client."""
|
|
130
|
+
self._client.close()
|
|
131
|
+
|
|
132
|
+
def __enter__(self):
|
|
133
|
+
return self
|
|
134
|
+
|
|
135
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
136
|
+
self.close()
|
|
137
|
+
return False
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
class AsyncOpenEnvClient:
|
|
141
|
+
"""
|
|
142
|
+
Async client for connecting to OpenEnv servers.
|
|
143
|
+
"""
|
|
144
|
+
|
|
145
|
+
def __init__(
|
|
146
|
+
self,
|
|
147
|
+
base_url: str,
|
|
148
|
+
timeout: float = 30.0,
|
|
149
|
+
api_key: Optional[str] = None,
|
|
150
|
+
):
|
|
151
|
+
self.base_url = base_url.rstrip("/")
|
|
152
|
+
self.timeout = timeout
|
|
153
|
+
self._client = httpx.AsyncClient(timeout=timeout)
|
|
154
|
+
if api_key:
|
|
155
|
+
self._client.headers["Authorization"] = f"Bearer {api_key}"
|
|
156
|
+
|
|
157
|
+
async def reset(self) -> dict:
|
|
158
|
+
try:
|
|
159
|
+
response = await self._client.post(f"{self.base_url}/reset")
|
|
160
|
+
response.raise_for_status()
|
|
161
|
+
data = response.json()
|
|
162
|
+
return data.get("observation", data)
|
|
163
|
+
except httpx.HTTPError as e:
|
|
164
|
+
logger.error(f"Failed to reset environment: {e}")
|
|
165
|
+
raise
|
|
166
|
+
|
|
167
|
+
async def step(self, action: Any) -> Tuple[dict, float, bool, dict]:
|
|
168
|
+
if hasattr(action, "as_dict"):
|
|
169
|
+
action_dict = action.as_dict()
|
|
170
|
+
elif isinstance(action, dict):
|
|
171
|
+
action_dict = action
|
|
172
|
+
else:
|
|
173
|
+
raise ValueError(f"Action must be dict or have as_dict() method, got {type(action)}")
|
|
174
|
+
|
|
175
|
+
try:
|
|
176
|
+
response = await self._client.post(
|
|
177
|
+
f"{self.base_url}/step",
|
|
178
|
+
json={"action": action_dict},
|
|
179
|
+
)
|
|
180
|
+
response.raise_for_status()
|
|
181
|
+
data = response.json()
|
|
182
|
+
return (
|
|
183
|
+
data.get("observation", {}),
|
|
184
|
+
data.get("reward", 0.0),
|
|
185
|
+
data.get("done", False),
|
|
186
|
+
data.get("info", {}),
|
|
187
|
+
)
|
|
188
|
+
except httpx.HTTPError as e:
|
|
189
|
+
logger.error(f"Failed to step environment: {e}")
|
|
190
|
+
raise
|
|
191
|
+
|
|
192
|
+
async def state(self) -> dict:
|
|
193
|
+
try:
|
|
194
|
+
response = await self._client.get(f"{self.base_url}/state")
|
|
195
|
+
response.raise_for_status()
|
|
196
|
+
return response.json()
|
|
197
|
+
except httpx.HTTPError as e:
|
|
198
|
+
logger.error(f"Failed to get state: {e}")
|
|
199
|
+
raise
|
|
200
|
+
|
|
201
|
+
async def aclose(self):
|
|
202
|
+
await self._client.aclose()
|
|
203
|
+
|
|
204
|
+
async def __aenter__(self):
|
|
205
|
+
return self
|
|
206
|
+
|
|
207
|
+
async def __aexit__(self, exc_type, exc_val, exc_tb):
|
|
208
|
+
await self.aclose()
|
|
209
|
+
return False
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
"""Utility for loading OpenEnv environments from YAML config."""
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Any, Dict, Optional
|
|
6
|
+
|
|
7
|
+
import yaml
|
|
8
|
+
|
|
9
|
+
from openenv_agent.client import OpenEnvClient
|
|
10
|
+
|
|
11
|
+
logger = logging.getLogger(__name__)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def load_env_from_yaml(
|
|
15
|
+
yaml_path: str | Path,
|
|
16
|
+
**client_kwargs
|
|
17
|
+
) -> Dict[str, Any]:
|
|
18
|
+
"""
|
|
19
|
+
Load an OpenEnv environment configuration from a YAML file.
|
|
20
|
+
|
|
21
|
+
Args:
|
|
22
|
+
yaml_path: Path to openenv.yaml
|
|
23
|
+
**client_kwargs: Additional arguments for OpenEnvClient
|
|
24
|
+
|
|
25
|
+
Returns:
|
|
26
|
+
Dict with keys:
|
|
27
|
+
- client: OpenEnvClient instance
|
|
28
|
+
- config: Parsed environment config
|
|
29
|
+
- schemas: Observation and action schemas
|
|
30
|
+
"""
|
|
31
|
+
yaml_path = Path(yaml_path)
|
|
32
|
+
if not yaml_path.exists():
|
|
33
|
+
raise FileNotFoundError(f"YAML file not found: {yaml_path}")
|
|
34
|
+
|
|
35
|
+
with open(yaml_path) as f:
|
|
36
|
+
config = yaml.safe_load(f)
|
|
37
|
+
|
|
38
|
+
env_config = config.get("environment", {})
|
|
39
|
+
api_config = config.get("api", {})
|
|
40
|
+
environment_config = config.get("environment_config", {})
|
|
41
|
+
schemas = config.get("schemas", {})
|
|
42
|
+
|
|
43
|
+
# Build base URL
|
|
44
|
+
port = api_config.get("port", 8000)
|
|
45
|
+
base_url = f"http://localhost:{port}"
|
|
46
|
+
|
|
47
|
+
client = OpenEnvClient(base_url=base_url, **client_kwargs)
|
|
48
|
+
|
|
49
|
+
return {
|
|
50
|
+
"client": client,
|
|
51
|
+
"config": {
|
|
52
|
+
"name": env_config.get("name", "unknown"),
|
|
53
|
+
"version": env_config.get("version", "unknown"),
|
|
54
|
+
"description": env_config.get("description", ""),
|
|
55
|
+
},
|
|
56
|
+
"environment_config": environment_config,
|
|
57
|
+
"schemas": schemas,
|
|
58
|
+
"metadata": config.get("metadata", {}),
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def load_env_from_url(
|
|
63
|
+
base_url: str,
|
|
64
|
+
api_key: Optional[str] = None
|
|
65
|
+
) -> OpenEnvClient:
|
|
66
|
+
"""
|
|
67
|
+
Load an OpenEnv environment from a URL.
|
|
68
|
+
|
|
69
|
+
Args:
|
|
70
|
+
base_url: Base URL of the OpenEnv server
|
|
71
|
+
api_key: Optional API key
|
|
72
|
+
|
|
73
|
+
Returns:
|
|
74
|
+
OpenEnvClient instance
|
|
75
|
+
"""
|
|
76
|
+
return OpenEnvClient(base_url=base_url, api_key=api_key)
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
"""Content moderation agent using OpenAI API."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import logging
|
|
5
|
+
from typing import Any, Dict, Optional
|
|
6
|
+
|
|
7
|
+
from openai import OpenAI, APIError, RateLimitError
|
|
8
|
+
|
|
9
|
+
from openenv_agent.agent import BaseAgent, AgentConfig
|
|
10
|
+
|
|
11
|
+
logger = logging.getLogger(__name__)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
# Valid decisions and categories from OpenEnv schema
|
|
15
|
+
VALID_DECISIONS = ["ALLOW", "FLAG", "REMOVE", "ESCALATE"]
|
|
16
|
+
VALID_CATEGORIES = ["SPAM", "HATE_SPEECH", "MISINFORMATION", "HARASSMENT", "SAFE"]
|
|
17
|
+
|
|
18
|
+
DECISION_MAP = {
|
|
19
|
+
"allow": "ALLOW",
|
|
20
|
+
"flag": "FLAG",
|
|
21
|
+
"remove": "REMOVE",
|
|
22
|
+
"escalate": "ESCALATE",
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
CATEGORY_MAP = {
|
|
26
|
+
"allow": "SAFE",
|
|
27
|
+
"flag": "SAFE",
|
|
28
|
+
"remove": "HARASSMENT",
|
|
29
|
+
"escalate": "SAFE",
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class ModerationAgent(BaseAgent):
|
|
34
|
+
"""
|
|
35
|
+
Content moderation agent using OpenAI API via OpenRouter.
|
|
36
|
+
|
|
37
|
+
Connects to OpenEnv content_moderation_env servers and makes
|
|
38
|
+
moderation decisions (ALLOW, FLAG, REMOVE, ESCALATE).
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
def __init__(self, config: Optional[AgentConfig] = None):
|
|
42
|
+
super().__init__(config or AgentConfig(name="ModerationAgent"))
|
|
43
|
+
self.api_key = os.getenv("HF_TOKEN") or os.getenv("OPENROUTER_API_KEY")
|
|
44
|
+
self.use_api = bool(self.api_key)
|
|
45
|
+
|
|
46
|
+
if self.use_api:
|
|
47
|
+
self.client = OpenAI(
|
|
48
|
+
api_key=self.api_key,
|
|
49
|
+
base_url="https://openrouter.ai/api/v1"
|
|
50
|
+
)
|
|
51
|
+
else:
|
|
52
|
+
logger.warning("No API key found. Using fallback mode.")
|
|
53
|
+
self.client = None
|
|
54
|
+
|
|
55
|
+
def predict(self, observation: Dict[str, Any]) -> Dict[str, Any]:
|
|
56
|
+
"""
|
|
57
|
+
Predict a moderation action from an observation.
|
|
58
|
+
|
|
59
|
+
Args:
|
|
60
|
+
observation: Observation dict with post_body, metadata, context
|
|
61
|
+
|
|
62
|
+
Returns:
|
|
63
|
+
Action dict with decision, content_category, reasoning, confidence_score
|
|
64
|
+
"""
|
|
65
|
+
if self.use_api:
|
|
66
|
+
try:
|
|
67
|
+
return self._call_api(observation)
|
|
68
|
+
except (APIError, RateLimitError) as e:
|
|
69
|
+
logger.warning(f"API failed: {e}. Using fallback.")
|
|
70
|
+
return self._fallback_decision(observation)
|
|
71
|
+
else:
|
|
72
|
+
return self._fallback_decision(observation)
|
|
73
|
+
|
|
74
|
+
def _call_api(self, obs: Dict[str, Any]) -> Dict[str, Any]:
|
|
75
|
+
"""Call OpenAI API for moderation decision."""
|
|
76
|
+
post_body = obs.get("post_body", "")
|
|
77
|
+
metadata = obs.get("metadata", {})
|
|
78
|
+
context = obs.get("context", [])
|
|
79
|
+
|
|
80
|
+
user_prompt = self._build_prompt(post_body, metadata, context)
|
|
81
|
+
|
|
82
|
+
response = self.client.chat.completions.create(
|
|
83
|
+
model="openai/gpt-4.1",
|
|
84
|
+
messages=[
|
|
85
|
+
{
|
|
86
|
+
"role": "system",
|
|
87
|
+
"content": self._get_system_prompt()
|
|
88
|
+
},
|
|
89
|
+
{"role": "user", "content": user_prompt}
|
|
90
|
+
],
|
|
91
|
+
temperature=self.config.temperature,
|
|
92
|
+
max_tokens=self.config.max_tokens,
|
|
93
|
+
timeout=self.config.timeout
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
content = response.choices[0].message.content.strip().lower()
|
|
97
|
+
|
|
98
|
+
# Parse response
|
|
99
|
+
decision_found = None
|
|
100
|
+
for decision in ["allow", "flag", "remove", "escalate"]:
|
|
101
|
+
if decision in content:
|
|
102
|
+
decision_found = decision
|
|
103
|
+
break
|
|
104
|
+
|
|
105
|
+
if not decision_found:
|
|
106
|
+
decision_found = "escalate"
|
|
107
|
+
logger.warning(f"Unexpected API response: {content}")
|
|
108
|
+
|
|
109
|
+
decision = DECISION_MAP.get(decision_found, "ESCALATE")
|
|
110
|
+
category = CATEGORY_MAP.get(decision_found, "SAFE")
|
|
111
|
+
|
|
112
|
+
return {
|
|
113
|
+
"decision": decision,
|
|
114
|
+
"content_category": category,
|
|
115
|
+
"reasoning": f"API decision: {decision_found}",
|
|
116
|
+
"confidence_score": 0.8
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
def _get_system_prompt(self) -> str:
|
|
120
|
+
return """You are a content moderation agent. Analyze the post and decide:
|
|
121
|
+
- ALLOW: Safe content
|
|
122
|
+
- FLAG: Suspicious content needing review
|
|
123
|
+
- REMOVE: Harmful content
|
|
124
|
+
- ESCALATE: Complex cases requiring human review
|
|
125
|
+
|
|
126
|
+
Respond with only ONE decision word."""
|
|
127
|
+
|
|
128
|
+
def _build_prompt(
|
|
129
|
+
self,
|
|
130
|
+
post_body: str,
|
|
131
|
+
metadata: Dict[str, Any],
|
|
132
|
+
context: list
|
|
133
|
+
) -> str:
|
|
134
|
+
lines = [
|
|
135
|
+
f"Post: {post_body}",
|
|
136
|
+
f"Author Trust: {metadata.get('author_trust_score', 'N/A')}",
|
|
137
|
+
f"Account Age: {metadata.get('account_age_days', 0)} days",
|
|
138
|
+
f"Reports: {metadata.get('reports_count', 0)}",
|
|
139
|
+
f"Virality: {metadata.get('virality_score', 0)}",
|
|
140
|
+
f"Context: {', '.join(context) if context else 'None'}",
|
|
141
|
+
"",
|
|
142
|
+
"Decide: allow, flag, remove, or escalate?"
|
|
143
|
+
]
|
|
144
|
+
return "\n".join(lines)
|
|
145
|
+
|
|
146
|
+
def _fallback_decision(self, obs: Dict[str, Any]) -> Dict[str, Any]:
|
|
147
|
+
"""
|
|
148
|
+
Keyword-based fallback when API is unavailable.
|
|
149
|
+
"""
|
|
150
|
+
post_body = obs.get("post_body", "").lower()
|
|
151
|
+
metadata = obs.get("metadata", {})
|
|
152
|
+
harmful_keywords = {
|
|
153
|
+
"remove": ["kill", "bomb", "explode", "shoot", "murder"],
|
|
154
|
+
"flag": ["hate", "stupid", "dumb", "idiotic", "scum"],
|
|
155
|
+
"spam": ["click here", "earn money", "buy now", "http://", "https://"]
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
for keyword in harmful_keywords["remove"]:
|
|
159
|
+
if keyword in post_body:
|
|
160
|
+
return {
|
|
161
|
+
"decision": "REMOVE",
|
|
162
|
+
"content_category": "HARASSMENT",
|
|
163
|
+
"reasoning": f"Fallback: detected '{keyword}'",
|
|
164
|
+
"confidence_score": 0.6
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
for keyword in harmful_keywords["flag"]:
|
|
168
|
+
if keyword in post_body:
|
|
169
|
+
return {
|
|
170
|
+
"decision": "FLAG",
|
|
171
|
+
"content_category": "HATE_SPEECH",
|
|
172
|
+
"reasoning": f"Fallback: detected '{keyword}'",
|
|
173
|
+
"confidence_score": 0.5
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
# Check trust and reports
|
|
177
|
+
trust = metadata.get("author_trust_score", 0.5)
|
|
178
|
+
reports = metadata.get("reports_count", 0)
|
|
179
|
+
if trust < 0.3 and reports > 5:
|
|
180
|
+
return {
|
|
181
|
+
"decision": "FLAG",
|
|
182
|
+
"content_category": "SAFE",
|
|
183
|
+
"reasoning": "Fallback: low-trust user with many reports",
|
|
184
|
+
"confidence_score": 0.4
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
return {
|
|
188
|
+
"decision": "ALLOW",
|
|
189
|
+
"content_category": "SAFE",
|
|
190
|
+
"reasoning": "Fallback: no harmful patterns",
|
|
191
|
+
"confidence_score": 0.8
|
|
192
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: openenv-agent
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Agent client for OpenEnv environments - connects RL agents to OpenEnv servers for inference and training
|
|
5
|
+
Author: Croma
|
|
6
|
+
License: MIT
|
|
7
|
+
Keywords: openenv,reinforcement-learning,agent,rl,content-moderation
|
|
8
|
+
Classifier: Development Status :: 3 - Alpha
|
|
9
|
+
Classifier: Intended Audience :: Developers
|
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Requires-Python: >=3.10
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
License-File: LICENSE
|
|
18
|
+
Requires-Dist: openai>=1.3.0
|
|
19
|
+
Requires-Dist: httpx>=0.25.0
|
|
20
|
+
Requires-Dist: pyyaml>=6.0
|
|
21
|
+
Requires-Dist: click>=8.0.0
|
|
22
|
+
Provides-Extra: dev
|
|
23
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
24
|
+
Requires-Dist: pytest-asyncio>=0.21.0; extra == "dev"
|
|
25
|
+
Requires-Dist: ruff>=0.1.0; extra == "dev"
|
|
26
|
+
Dynamic: license-file
|
|
27
|
+
|
|
28
|
+
# OpenEnv Agent
|
|
29
|
+
|
|
30
|
+
Python client library for connecting RL agents to OpenEnv servers.
|
|
31
|
+
|
|
32
|
+
## Installation
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
pip install openenv-agent
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Quick Start
|
|
39
|
+
|
|
40
|
+
```python
|
|
41
|
+
from openenv_agent import OpenEnvClient, ModerationAgent
|
|
42
|
+
|
|
43
|
+
# Connect to an OpenEnv server
|
|
44
|
+
client = OpenEnvClient(base_url="http://localhost:8000")
|
|
45
|
+
|
|
46
|
+
# Reset environment
|
|
47
|
+
obs = client.reset()
|
|
48
|
+
|
|
49
|
+
# Use the moderation agent
|
|
50
|
+
agent = ModerationAgent()
|
|
51
|
+
action = agent.predict(obs)
|
|
52
|
+
|
|
53
|
+
# Step the environment
|
|
54
|
+
next_obs, reward, done, info = client.step(action)
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## CLI Usage
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
# Run agent against a server
|
|
61
|
+
openenv-agent run http://localhost:8000
|
|
62
|
+
|
|
63
|
+
# Interactive mode
|
|
64
|
+
openenv-agent interactive http://localhost:8000
|
|
65
|
+
|
|
66
|
+
# Evaluate on a dataset
|
|
67
|
+
openenv-agent eval http://localhost:8000 --dataset ./data.json
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Features
|
|
71
|
+
|
|
72
|
+
- Async/sync OpenEnv client with Gymnasium-style API
|
|
73
|
+
- Built-in ModerationAgent for content moderation environments
|
|
74
|
+
- Environment loader from openenv.yaml configs
|
|
75
|
+
- CLI tool for easy server interaction
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
README.md
|
|
3
|
+
pyproject.toml
|
|
4
|
+
src/openenv_agent/__init__.py
|
|
5
|
+
src/openenv_agent/agent.py
|
|
6
|
+
src/openenv_agent/cli.py
|
|
7
|
+
src/openenv_agent/client.py
|
|
8
|
+
src/openenv_agent/env_loader.py
|
|
9
|
+
src/openenv_agent/moderation_agent.py
|
|
10
|
+
src/openenv_agent.egg-info/PKG-INFO
|
|
11
|
+
src/openenv_agent.egg-info/SOURCES.txt
|
|
12
|
+
src/openenv_agent.egg-info/dependency_links.txt
|
|
13
|
+
src/openenv_agent.egg-info/entry_points.txt
|
|
14
|
+
src/openenv_agent.egg-info/requires.txt
|
|
15
|
+
src/openenv_agent.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
openenv_agent
|