episteme / client.py
pvs333's picture
clinet fix
f060564
Raw
History Blame Contribute Delete
5.04 kB
# Copyright (c) Meta Platforms, Inc. and affiliates.
# All rights reserved.
#
# This source code is licensed under the BSD-style license found in the
# LICENSE file in the root directory of this source tree.
"""Episteme Environment Client.
Provides a typed client for the Episteme environment server. Extends
:class:`EnvClient` parameterised with :class:`EpistemeAction` and
:class:`EpistemeObservation`.
Example (async)::
async with EpistemeEnv(base_url="http://localhost:8000") as client:
result = await client.reset(task_type="fact_check", difficulty=2)
print(result.observation.result)
result = await client.step(
EpistemeAction(tool_name="search_corpus",
arguments={"query": "eiffel tower"})
)
Example (sync wrapper)::
env = EpistemeEnv(base_url="http://localhost:8000").sync()
with env:
result = env.reset(task_type="research_brief", difficulty=1)
result = env.step(
EpistemeAction(tool_name="read_notes", arguments={})
)
"""
from __future__ import annotations
from typing import Any, Dict, Optional
from openenv.core.env_client import EnvClient
from openenv.core.client_types import StepResult
from openenv.core.env_server.types import State
from .models import EpistemeAction, EpistemeObservation
class EpistemeEnv(
EnvClient[EpistemeAction, EpistemeObservation, State]
):
"""Typed WebSocket client for the Episteme environment.
Inherits connection management, ``step``, and ``state`` from
:class:`EnvClient`. Adds an explicit ``reset`` method that
exposes ``task_type`` and ``difficulty`` as keyword arguments.
"""
# ------------------------------------------------------------------
# reset — surfaces Episteme-specific kwargs
# ------------------------------------------------------------------
async def reset(
self,
*,
task_type: str = "fact_check",
difficulty: int = 1,
seed: Optional[int] = None,
episode_id: Optional[str] = None,
**kwargs: Any,
) -> StepResult[EpistemeObservation]:
"""Reset the environment and start a new episode.
All keyword arguments are forwarded to the server's ``reset``
endpoint via the WebSocket ``reset`` message.
Args:
task_type: One of ``"fact_check"``, ``"research_brief"``,
``"market_analysis"``, or ``"notes"``.
difficulty: Corpus difficulty level (1-5).
seed: Optional random seed for reproducibility.
episode_id: Optional custom episode identifier.
**kwargs: Additional parameters forwarded to the server.
Returns:
:class:`StepResult` containing the initial
:class:`EpistemeObservation`.
"""
reset_kwargs: Dict[str, Any] = {
"task_type": task_type,
"difficulty": difficulty,
**kwargs,
}
if seed is not None:
reset_kwargs["seed"] = seed
if episode_id is not None:
reset_kwargs["episode_id"] = episode_id
return await super().reset(**reset_kwargs)
# ------------------------------------------------------------------
# Serialisation / deserialisation hooks
# ------------------------------------------------------------------
def _step_payload(self, action: EpistemeAction) -> Dict[str, Any]:
"""Convert an :class:`EpistemeAction` to the JSON payload
expected by the environment server's ``step`` endpoint.
"""
return action.model_dump()
def _parse_result(
self, payload: Dict[str, Any]
) -> StepResult[EpistemeObservation]:
"""Parse a server response into a typed :class:`StepResult`.
The framework's ``serialize_observation`` places ``reward``,
``done``, and ``metadata`` at the top level of ``payload``
(outside the nested ``observation`` dict), so we source them
from there.
"""
obs_data = payload.get("observation", {})
# reward / done / metadata live at the payload root,
# NOT inside obs_data (the framework strips them).
reward = payload.get("reward", 0.0)
done = payload.get("done", False)
observation = EpistemeObservation(
result=obs_data.get("result", ""),
reward=reward if reward is not None else 0.0,
done=done,
step_count=obs_data.get("step_count", 0),
steps_remaining=obs_data.get("steps_remaining", 0),
success=obs_data.get("success", True),
)
return StepResult(
observation=observation,
reward=reward,
done=done,
)
def _parse_state(self, payload: Dict[str, Any]) -> State:
"""Parse a server state response into a :class:`State` object."""
return State(
episode_id=payload.get("episode_id"),
step_count=payload.get("step_count", 0),
)