Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 42 additions & 28 deletions docs/source/environments/echo.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,25 +9,27 @@ The simplest way to use the Echo environment is through the `EchoEnv` class. The

```python
import asyncio
from echo_env import EchoAction, EchoEnv
from echo_env import CallToolAction, EchoEnv

async def main():
# Create environment from Docker image
client = await EchoEnv.from_docker_image("echo-env:latest")

async with client:
# Reset
result = await client.reset()
print(f"Reset: {result.observation.echoed_message}")
await client.reset()

# Send multiple messages
messages = ["Hello, World!", "Testing echo", "Final message"]

for msg in messages:
result = await client.step(EchoAction(message=msg))
result = await client.step(
CallToolAction(
tool_name="echo_message",
arguments={"message": msg},
)
)
print(f"Sent: '{msg}'")
print(f" → Echoed: '{result.observation.echoed_message}'")
print(f" → Length: {result.observation.message_length}")
print(f" → Echoed: '{result.observation.result}'")
print(f" → Reward: {result.reward}")

asyncio.run(main())
Expand All @@ -36,12 +38,17 @@ asyncio.run(main())
For **synchronous usage**, use the `.sync()` wrapper:

```python
from echo_env import EchoAction, EchoEnv
from echo_env import CallToolAction, EchoEnv

with EchoEnv(base_url="http://localhost:8000").sync() as client:
result = client.reset()
result = client.step(EchoAction(message="Hello!"))
print(result.observation.echoed_message)
client.reset()
result = client.step(
CallToolAction(
tool_name="echo_message",
arguments={"message": "Hello!"},
)
)
print(result.observation.result)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

result.observation.result is not the echoed string — it is the full FastMCP tool-result envelope:

{'content': [{'type': 'text', 'text': 'Hello!', ...}], 'structured_content': {'result': 'Hello!'}, 'data': 'Hello!', 'is_error': False}

So this prints the whole dict, not Hello!. Use result.observation.result["data"] (verified → 'Hello!') or client.call_tool("echo_message", message="Hello!") (verified → 'Hello!'). Same applies to line 32's → Echoed: '{result.observation.result}'.

```

The `EchoEnv.from_docker_image()` method handles:
Expand All @@ -61,23 +68,20 @@ docker build -t echo-env:latest -f envs/echo_env/server/Dockerfile .

## Environment Details

### Action
**EchoAction**: Contains a single field
- `message` (str) - The message to echo back
### Tools
- `echo_message(message)` - Echo the provided message
- `echo_with_length(message)` - Echo the message and include its length

### Observation
**EchoObservation**: Contains the echo response and metadata
- `echoed_message` (str) - The message echoed back
- `message_length` (int) - Length of the message
- `reward` (float) - Reward based on message length (length × 0.1)
- `done` (bool) - Always False for echo environment
**CallToolObservation**: Contains the tool result and metadata
- `result` - The tool return value
- `reward` (float) - Reward returned by the environment
- `done` (bool) - Always `False` for the echo environment
- `metadata` (dict) - Additional info like step count

### Reward
The reward is calculated as: `message_length × 0.1`
- "Hi" → reward: 0.2
- "Hello, World!" → reward: 1.3
- Empty message → reward: 0.0
The echo environment returns `0.0` reward for tool calls. It is intended as a

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verified at runtime: CallToolAction steps return reward=None, not 0.0 — only reset() returns 0.0. Please correct this sentence, and the reward (float) bullet on line 78 (it is None for tool calls). If 0.0 is actually the intended semantics, the fix belongs in the environment (MCPEnvironment._async_handle_call_tool), not the docs.

minimal MCP integration example rather than a reward-shaping reference.

## Advanced Usage

Expand All @@ -86,17 +90,27 @@ The reward is calculated as: `message_length × 0.1`
If you already have an Echo environment server running, you can connect directly:

```python
from echo_env import EchoAction, EchoEnv
from echo_env import CallToolAction, EchoEnv

# Async usage
async with EchoEnv(base_url="http://localhost:8000") as client:
result = await client.reset()
result = await client.step(EchoAction(message="Hello!"))
await client.reset()
result = await client.step(
CallToolAction(
tool_name="echo_message",
arguments={"message": "Hello!"},
)
)

# Sync usage
with EchoEnv(base_url="http://localhost:8000").sync() as client:
result = client.reset()
result = client.step(EchoAction(message="Hello!"))
client.reset()
result = client.step(
CallToolAction(
tool_name="echo_message",
arguments={"message": "Hello!"},
)
)
```

Note: When connecting to an existing server, closing the client will NOT stop the server.
Expand Down
27 changes: 18 additions & 9 deletions docs/source/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,15 +58,19 @@ parallel environment runs, and integrations with async frameworks.
```python
import asyncio

from echo_env import EchoAction, EchoEnv
from echo_env import CallToolAction, EchoEnv


async def main():
async with EchoEnv(base_url="https://openenv-echo-env.hf.space") as client:
result = await client.reset()
print(result.observation.echoed_message)

result = await client.step(EchoAction(message="Hello, World!"))
await client.reset()

result = await client.step(
CallToolAction(
tool_name="echo_message",
arguments={"message": "Hello, World!"},
)
)
print(result.reward)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After a CallToolAction step, result.reward is None (tool calls do not set a reward; only reset() returns 0.0), so this prints None. Consider printing the tool result instead, e.g. print(result.observation.result["data"]), or switch to client.call_tool(...).



Expand All @@ -76,12 +80,17 @@ asyncio.run(main())
For scripts and notebooks, use `.sync()`:

```python
from echo_env import EchoAction, EchoEnv
from echo_env import CallToolAction, EchoEnv

with EchoEnv(base_url="https://openenv-echo-env.hf.space").sync() as client:
result = client.reset()
result = client.step(EchoAction(message="Hello, World!"))
print(result.observation.echoed_message)
client.reset()
result = client.step(
CallToolAction(
tool_name="echo_message",
arguments={"message": "Hello, World!"},
)
)
print(result.observation.result)
```

## Use Containers or Local Servers
Expand Down
70 changes: 42 additions & 28 deletions envs/echo_env/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,25 +21,27 @@ The simplest way to use the Echo environment is through the `EchoEnv` class. The

```python
import asyncio
from echo_env import EchoAction, EchoEnv
from echo_env import CallToolAction, EchoEnv

async def main():
# Create environment from Docker image
client = await EchoEnv.from_docker_image("echo-env:latest")

async with client:
# Reset
result = await client.reset()
print(f"Reset: {result.observation.echoed_message}")
await client.reset()

# Send multiple messages
messages = ["Hello, World!", "Testing echo", "Final message"]

for msg in messages:
result = await client.step(EchoAction(message=msg))
result = await client.step(
CallToolAction(
tool_name="echo_message",
arguments={"message": msg},
)
)
print(f"Sent: '{msg}'")
print(f" → Echoed: '{result.observation.echoed_message}'")
print(f" → Length: {result.observation.message_length}")
print(f" → Echoed: '{result.observation.result}'")
print(f" → Reward: {result.reward}")

asyncio.run(main())
Expand All @@ -48,12 +50,17 @@ asyncio.run(main())
For **synchronous usage**, use the `.sync()` wrapper:

```python
from echo_env import EchoAction, EchoEnv
from echo_env import CallToolAction, EchoEnv

with EchoEnv(base_url="http://localhost:8000").sync() as client:
result = client.reset()
result = client.step(EchoAction(message="Hello!"))
print(result.observation.echoed_message)
client.reset()
result = client.step(
CallToolAction(
tool_name="echo_message",
arguments={"message": "Hello!"},
)
)
print(result.observation.result)
```

The `EchoEnv.from_docker_image()` method handles:
Expand All @@ -73,23 +80,20 @@ docker build -t echo-env:latest -f envs/echo_env/server/Dockerfile .

## Environment Details

### Action
**EchoAction**: Contains a single field
- `message` (str) - The message to echo back
### Tools
- `echo_message(message)` - Echo the provided message
- `echo_with_length(message)` - Echo the message and include its length

### Observation
**EchoObservation**: Contains the echo response and metadata
- `echoed_message` (str) - The message echoed back
- `message_length` (int) - Length of the message
- `reward` (float) - Reward based on message length (length × 0.1)
- `done` (bool) - Always False for echo environment
**CallToolObservation**: Contains the tool result and metadata
- `result` - The tool return value
- `reward` (float) - Reward returned by the environment
- `done` (bool) - Always `False` for the echo environment
- `metadata` (dict) - Additional info like step count

### Reward
The reward is calculated as: `message_length × 0.1`
- "Hi" → reward: 0.2
- "Hello, World!" → reward: 1.3
- Empty message → reward: 0.0
The echo environment returns `0.0` reward for tool calls. It is intended as a

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verified at runtime: tool-call steps return reward=None, not 0.0 (only reset() returns 0.0). Please fix this sentence and the reward (float) bullet on line 90. Mirrors the same issue in docs/source/environments/echo.md.

minimal MCP integration example rather than a reward-shaping reference.

## Advanced Usage

Expand All @@ -98,17 +102,27 @@ The reward is calculated as: `message_length × 0.1`
If you already have an Echo environment server running, you can connect directly:

```python
from echo_env import EchoAction, EchoEnv
from echo_env import CallToolAction, EchoEnv

# Async usage
async with EchoEnv(base_url="http://localhost:8000") as client:
result = await client.reset()
result = await client.step(EchoAction(message="Hello!"))
await client.reset()
result = await client.step(
CallToolAction(
tool_name="echo_message",
arguments={"message": "Hello!"},
)
)

# Sync usage
with EchoEnv(base_url="http://localhost:8000").sync() as client:
result = client.reset()
result = client.step(EchoAction(message="Hello!"))
client.reset()
result = client.step(
CallToolAction(
tool_name="echo_message",
arguments={"message": "Hello!"},
)
)
```

Note: When connecting to an existing server, closing the client will NOT stop the server.
Expand Down
Loading