Spaces:
Runtime error
Runtime error
hirann commited on
Commit ·
1f9fc8c
0
Parent(s):
Add Core Identity environment for OpenEnv
Browse files- .gitignore +5 -0
- Dockerfile +43 -0
- README.md +188 -0
- __init__.py +26 -0
- client.py +95 -0
- models.py +87 -0
- openenv.yaml +6 -0
- pyproject.toml +24 -0
- server/Dockerfile +53 -0
- server/__init__.py +16 -0
- server/core_identity_environment.py +237 -0
- server/requirements.txt +4 -0
- tasks/definitions.py +216 -0
.gitignore
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
__pycache__/
|
| 2 |
+
*.pyc
|
| 3 |
+
.venv/
|
| 4 |
+
*.egg-info/
|
| 5 |
+
.pytest_cache/
|
Dockerfile
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Core Identity Environment - Docker configuration for HF Spaces
|
| 2 |
+
|
| 3 |
+
ARG BASE_IMAGE=ghcr.io/meta-pytorch/openenv-base:latest
|
| 4 |
+
FROM ${BASE_IMAGE} AS builder
|
| 5 |
+
|
| 6 |
+
WORKDIR /app
|
| 7 |
+
|
| 8 |
+
COPY . /app/env
|
| 9 |
+
|
| 10 |
+
WORKDIR /app/env
|
| 11 |
+
|
| 12 |
+
RUN if ! command -v uv > /dev/null 2>&1; then \
|
| 13 |
+
curl -LsSf https://astral.sh/uv/install.sh | sh && \
|
| 14 |
+
mv /root/.local/bin/uv /usr/local/bin/uv && \
|
| 15 |
+
mv /root/.local/bin/uvx /usr/local/bin/uvx; \
|
| 16 |
+
fi
|
| 17 |
+
|
| 18 |
+
RUN apt-get update && apt-get install -y --no-install-recommends \
|
| 19 |
+
git \
|
| 20 |
+
&& rm -rf /var/lib/apt/lists/*
|
| 21 |
+
|
| 22 |
+
RUN --mount=type=cache,target=/root/.cache/uv \
|
| 23 |
+
uv sync --no-install-project --no-editable
|
| 24 |
+
|
| 25 |
+
RUN --mount=type=cache,target=/root/.cache/uv \
|
| 26 |
+
uv sync --no-editable
|
| 27 |
+
|
| 28 |
+
FROM ${BASE_IMAGE}
|
| 29 |
+
|
| 30 |
+
WORKDIR /app
|
| 31 |
+
|
| 32 |
+
COPY --from=builder /app/env/.venv /app/.venv
|
| 33 |
+
COPY --from=builder /app/env /app/env
|
| 34 |
+
|
| 35 |
+
ENV PATH="/app/.venv/bin:$PATH"
|
| 36 |
+
ENV PYTHONPATH="/app/env:$PYTHONPATH"
|
| 37 |
+
|
| 38 |
+
HEALTHCHECK --interval=30s --timeout=3s --start-period=10s --retries=3 \
|
| 39 |
+
CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:8000/health')" || exit 1
|
| 40 |
+
|
| 41 |
+
EXPOSE 8000
|
| 42 |
+
|
| 43 |
+
CMD ["sh", "-c", "cd /app/env && uvicorn core_identity_env.server.app:app --host 0.0.0.0 --port 8000"]
|
README.md
ADDED
|
@@ -0,0 +1,188 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Core Identity Environment — OpenEnv
|
| 2 |
+
|
| 3 |
+
[](https://github.com/meta-pytorch/OpenEnv)
|
| 4 |
+
[](https://python.org)
|
| 5 |
+
|
| 6 |
+
An identity verification environment for OpenEnv (`meta-pytorch/OpenEnv`), where AI agents can perform document verification, credential authentication, profile management, and fraud detection tasks.
|
| 7 |
+
|
| 8 |
+
---
|
| 9 |
+
|
| 10 |
+
## Overview
|
| 11 |
+
|
| 12 |
+
This environment simulates real-world identity verification tasks. Agents must:
|
| 13 |
+
|
| 14 |
+
1. Analyze identity documents or credentials
|
| 15 |
+
2. Perform verification checks
|
| 16 |
+
3. Detect fraud or issues
|
| 17 |
+
4. Make accurate verification decisions
|
| 18 |
+
|
| 19 |
+
### Why Core Identity?
|
| 20 |
+
|
| 21 |
+
- **Security-focused** — critical real-world application
|
| 22 |
+
- **Multi-faceted** — document, credentials, and profile tasks
|
| 23 |
+
- **Difficulty progression** — simple auth → hard fraud detection
|
| 24 |
+
- **Measurable** — clear true/false outcomes
|
| 25 |
+
|
| 26 |
+
---
|
| 27 |
+
|
| 28 |
+
## Project Structure
|
| 29 |
+
|
| 30 |
+
```
|
| 31 |
+
core_identity_env/ # Environment package
|
| 32 |
+
├── __init__.py # Exports: CoreIdentityEnv, CoreIdentityAction
|
| 33 |
+
├── client.py # CoreIdentityEnv — async EnvClient (WebSocket)
|
| 34 |
+
├── models.py # Typed models for all task types
|
| 35 |
+
├── openenv.yaml # OpenEnv manifest (spec_version: 1)
|
| 36 |
+
├── pyproject.toml # Per-env package config
|
| 37 |
+
├── tasks/
|
| 38 |
+
│ ├── __init__.py
|
| 39 |
+
│ └── definitions.py # 5 task types + graders
|
| 40 |
+
└── server/
|
| 41 |
+
├── __init__.py # FastAPI app via create_app()
|
| 42 |
+
├── core_identity_environment.py # Server-side Environment
|
| 43 |
+
├── requirements.txt # Server/Docker deps
|
| 44 |
+
└── Dockerfile # Multi-stage build
|
| 45 |
+
```
|
| 46 |
+
|
| 47 |
+
---
|
| 48 |
+
|
| 49 |
+
## Tasks
|
| 50 |
+
|
| 51 |
+
| Task ID | Name | Task Type | Difficulty |
|
| 52 |
+
|---------|------|-----------|------------|
|
| 53 |
+
| `doc_verify_passport` | Passport Verification | document_verification | Medium |
|
| 54 |
+
| `doc_verify_fake_id` | Fake ID Detection | document_verification | Hard |
|
| 55 |
+
| `auth_valid_creds` | Credential Authentication | credential_authentication | Easy |
|
| 56 |
+
| `auth_brute_force` | Brute Force Detection | credential_authentication | Hard |
|
| 57 |
+
| `profile_update` | Profile Update Validation | profile_management | Medium |
|
| 58 |
+
|
| 59 |
+
---
|
| 60 |
+
|
| 61 |
+
## Quick Start
|
| 62 |
+
|
| 63 |
+
### 1. Install the environment
|
| 64 |
+
|
| 65 |
+
```bash
|
| 66 |
+
pip install -e core_identity_env/
|
| 67 |
+
```
|
| 68 |
+
|
| 69 |
+
### 2. Start the server
|
| 70 |
+
|
| 71 |
+
```bash
|
| 72 |
+
uvicorn core_identity_env.server.app:app --host 0.0.0.0 --port 8000
|
| 73 |
+
```
|
| 74 |
+
|
| 75 |
+
### 3. Use the async client
|
| 76 |
+
|
| 77 |
+
```python
|
| 78 |
+
import asyncio
|
| 79 |
+
from core_identity_env import CoreIdentityEnv, CoreIdentityAction, VerificationResult
|
| 80 |
+
|
| 81 |
+
async def main():
|
| 82 |
+
async with CoreIdentityEnv(base_url="http://localhost:8000") as env:
|
| 83 |
+
result = await env.reset()
|
| 84 |
+
obs = result.observation
|
| 85 |
+
print(f"Task: {obs.task_name}")
|
| 86 |
+
|
| 87 |
+
if obs.document:
|
| 88 |
+
print(f"Document type: {obs.document.document_type}")
|
| 89 |
+
|
| 90 |
+
action = CoreIdentityAction(
|
| 91 |
+
verification=VerificationResult(
|
| 92 |
+
verified=True,
|
| 93 |
+
confidence=0.95,
|
| 94 |
+
checks_performed={"expiry_check": True, "format_check": True},
|
| 95 |
+
),
|
| 96 |
+
submit=True,
|
| 97 |
+
)
|
| 98 |
+
result = await env.step(action)
|
| 99 |
+
print(f"Reward: {result.reward:.4f}")
|
| 100 |
+
|
| 101 |
+
asyncio.run(main())
|
| 102 |
+
```
|
| 103 |
+
|
| 104 |
+
### 4. From a Hugging Face Space
|
| 105 |
+
|
| 106 |
+
```python
|
| 107 |
+
import asyncio
|
| 108 |
+
from core_identity_env import CoreIdentityEnv
|
| 109 |
+
|
| 110 |
+
async def main():
|
| 111 |
+
env = await CoreIdentityEnv.from_env("hirann/core-identity-env")
|
| 112 |
+
try:
|
| 113 |
+
result = await env.reset()
|
| 114 |
+
print(result.observation.task_name)
|
| 115 |
+
finally:
|
| 116 |
+
await env.close()
|
| 117 |
+
|
| 118 |
+
asyncio.run(main())
|
| 119 |
+
```
|
| 120 |
+
|
| 121 |
+
---
|
| 122 |
+
|
| 123 |
+
## Docker
|
| 124 |
+
|
| 125 |
+
```bash
|
| 126 |
+
# Build
|
| 127 |
+
docker build -t core-identity-env -f core_identity_env/server/Dockerfile .
|
| 128 |
+
|
| 129 |
+
# Run
|
| 130 |
+
docker run -p 8000:8000 core-identity-env
|
| 131 |
+
```
|
| 132 |
+
|
| 133 |
+
## Hugging Face Spaces Deployment
|
| 134 |
+
|
| 135 |
+
1. Create a new Space at https://huggingface.co/new-space
|
| 136 |
+
2. Select **Docker** as the SDK
|
| 137 |
+
3. Push your repo
|
| 138 |
+
|
| 139 |
+
---
|
| 140 |
+
|
| 141 |
+
## Architecture
|
| 142 |
+
|
| 143 |
+
```
|
| 144 |
+
┌───────────────────────────────────┐
|
| 145 |
+
│ Client Application │
|
| 146 |
+
│ CoreIdentityEnv (EnvClient) │
|
| 147 |
+
│ async with env: ... │
|
| 148 |
+
└───────────────┬───────────────────┘
|
| 149 |
+
│ WebSocket /ws
|
| 150 |
+
│ (reset, step, state)
|
| 151 |
+
┌───────────────▼───────────────────┐
|
| 152 |
+
│ Docker Container / HF Space │
|
| 153 |
+
│ FastAPI — create_app() │
|
| 154 |
+
│ CoreIdentityEnvironment │
|
| 155 |
+
│ reset() / step() / state │
|
| 156 |
+
└───────────────────────────────────┘
|
| 157 |
+
```
|
| 158 |
+
|
| 159 |
+
---
|
| 160 |
+
|
| 161 |
+
## Reward Function
|
| 162 |
+
|
| 163 |
+
| Component | Weight | Notes |
|
| 164 |
+
|-----------|--------|-------|
|
| 165 |
+
| Accuracy | 70% | Correct verification decision |
|
| 166 |
+
| Completeness | 30% | Checks performed / required |
|
| 167 |
+
| Efficiency bonus | +0.1 | Completed in ≤50% of max steps |
|
| 168 |
+
|
| 169 |
+
---
|
| 170 |
+
|
| 171 |
+
## OpenEnv Spec Compliance
|
| 172 |
+
|
| 173 |
+
| Spec requirement | Status |
|
| 174 |
+
|-----------------|--------|
|
| 175 |
+
| `openenv.yaml` with `spec_version: 1` | ✅ |
|
| 176 |
+
| `type: space`, `runtime: fastapi` | ✅ |
|
| 177 |
+
| `Environment` base class (`reset/step/state`) | ✅ |
|
| 178 |
+
| `EnvClient` base class (async WebSocket) | ✅ |
|
| 179 |
+
| `create_app()` factory | ✅ |
|
| 180 |
+
| Per-env `pyproject.toml` | ✅ |
|
| 181 |
+
| `server/Dockerfile` with `openenv-base` | ✅ |
|
| 182 |
+
| Typed `Action` / `Observation` models | ✅ |
|
| 183 |
+
|
| 184 |
+
---
|
| 185 |
+
|
| 186 |
+
## License
|
| 187 |
+
|
| 188 |
+
MIT — see LICENSE file for details.
|
__init__.py
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Core Identity Environment — OpenEnv.
|
| 2 |
+
|
| 3 |
+
An identity verification environment for AI agents that can:
|
| 4 |
+
- Verify user identity documents (ID, passport, etc.)
|
| 5 |
+
- Authenticate users via credentials
|
| 6 |
+
- Manage user profiles and attributes
|
| 7 |
+
- Detect fraudulent documents
|
| 8 |
+
"""
|
| 9 |
+
|
| 10 |
+
from core_identity_env.client import CoreIdentityEnv
|
| 11 |
+
from core_identity_env.models import (
|
| 12 |
+
CoreIdentityAction,
|
| 13 |
+
CoreIdentityObservation,
|
| 14 |
+
TaskType,
|
| 15 |
+
IdentityDocument,
|
| 16 |
+
VerificationResult,
|
| 17 |
+
)
|
| 18 |
+
|
| 19 |
+
__all__ = [
|
| 20 |
+
"CoreIdentityEnv",
|
| 21 |
+
"CoreIdentityAction",
|
| 22 |
+
"CoreIdentityObservation",
|
| 23 |
+
"TaskType",
|
| 24 |
+
"IdentityDocument",
|
| 25 |
+
"VerificationResult",
|
| 26 |
+
]
|
client.py
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Core Identity Environment Client.
|
| 2 |
+
|
| 3 |
+
Provides the async EnvClient for connecting to a CoreIdentityEnvironment server.
|
| 4 |
+
Supports both async (recommended) and synchronous usage via .sync() wrapper.
|
| 5 |
+
|
| 6 |
+
Example (async):
|
| 7 |
+
>>> import asyncio
|
| 8 |
+
>>> from core_identity_env import CoreIdentityEnv, CoreIdentityAction, VerificationResult
|
| 9 |
+
>>>
|
| 10 |
+
>>> async def main():
|
| 11 |
+
... async with CoreIdentityEnv(base_url="http://localhost:8000") as env:
|
| 12 |
+
... result = await env.reset()
|
| 13 |
+
... obs = result.observation
|
| 14 |
+
... print(obs.document)
|
| 15 |
+
...
|
| 16 |
+
... action = CoreIdentityAction(
|
| 17 |
+
... verification=VerificationResult(
|
| 18 |
+
... verified=True,
|
| 19 |
+
... confidence=0.95,
|
| 20 |
+
... issues=[],
|
| 21 |
+
... ),
|
| 22 |
+
... submit=True,
|
| 23 |
+
... )
|
| 24 |
+
... result = await env.step(action)
|
| 25 |
+
... print(result.reward)
|
| 26 |
+
...
|
| 27 |
+
>>> asyncio.run(main())
|
| 28 |
+
|
| 29 |
+
Example (from Hugging Face Space):
|
| 30 |
+
>>> env = await CoreIdentityEnv.from_env("openenv/core-identity-env")
|
| 31 |
+
>>> try:
|
| 32 |
+
... result = await env.reset()
|
| 33 |
+
... finally:
|
| 34 |
+
... await env.close()
|
| 35 |
+
"""
|
| 36 |
+
|
| 37 |
+
from typing import Any, Dict
|
| 38 |
+
|
| 39 |
+
try:
|
| 40 |
+
from openenv.core.env_client import EnvClient
|
| 41 |
+
from openenv.core.client_types import StepResult
|
| 42 |
+
except ImportError:
|
| 43 |
+
from openenv.core.env_client import EnvClient
|
| 44 |
+
from openenv.core.client_types import StepResult
|
| 45 |
+
|
| 46 |
+
from core_identity_env.models import CoreIdentityObservation, CoreIdentityAction
|
| 47 |
+
|
| 48 |
+
|
| 49 |
+
class CoreIdentityEnv(EnvClient[CoreIdentityAction, CoreIdentityObservation, Dict[str, Any]]):
|
| 50 |
+
"""Async client for the Core Identity Environment.
|
| 51 |
+
|
| 52 |
+
Connects to a running CoreIdentityEnvironment server via WebSocket and
|
| 53 |
+
provides step/reset/state methods for interacting with the environment.
|
| 54 |
+
|
| 55 |
+
Inherits from EnvClient:
|
| 56 |
+
- Async by default — use ``async with`` and ``await``
|
| 57 |
+
- Sync wrapper — call ``.sync()`` for synchronous usage
|
| 58 |
+
- ``from_env(repo_id)`` — connect to a Hugging Face Space
|
| 59 |
+
- ``from_docker_image(image)`` — spin up a local Docker container
|
| 60 |
+
"""
|
| 61 |
+
|
| 62 |
+
def _step_payload(self, action: CoreIdentityAction) -> Dict[str, Any]:
|
| 63 |
+
"""Serialise CoreIdentityAction to JSON payload for the server."""
|
| 64 |
+
return action.model_dump()
|
| 65 |
+
|
| 66 |
+
def _parse_result(self, payload: Dict[str, Any]) -> "StepResult[CoreIdentityObservation]":
|
| 67 |
+
"""Deserialise server JSON response to a typed StepResult."""
|
| 68 |
+
metadata = payload.get("metadata", payload)
|
| 69 |
+
obs_data = metadata.get("observation", metadata)
|
| 70 |
+
reward_data = metadata.get("reward", {})
|
| 71 |
+
done = payload.get("done", metadata.get("done", False))
|
| 72 |
+
reward_value = payload.get("reward", reward_data.get("value", 0.0))
|
| 73 |
+
|
| 74 |
+
try:
|
| 75 |
+
observation = CoreIdentityObservation.model_validate(obs_data)
|
| 76 |
+
except Exception:
|
| 77 |
+
observation = CoreIdentityObservation(
|
| 78 |
+
task_id=obs_data.get("task_id", "unknown"),
|
| 79 |
+
task_type=obs_data.get("task_type", "document_verification"),
|
| 80 |
+
task_name=obs_data.get("task_name", "unknown"),
|
| 81 |
+
task_description=obs_data.get("task_description", ""),
|
| 82 |
+
difficulty=obs_data.get("difficulty", "medium"),
|
| 83 |
+
challenge_data=obs_data.get("challenge_data", {}),
|
| 84 |
+
)
|
| 85 |
+
|
| 86 |
+
return StepResult(
|
| 87 |
+
observation=observation,
|
| 88 |
+
reward=reward_value,
|
| 89 |
+
done=done,
|
| 90 |
+
info=metadata.get("info", {}),
|
| 91 |
+
)
|
| 92 |
+
|
| 93 |
+
def _parse_state(self, payload: Dict[str, Any]) -> Dict[str, Any]:
|
| 94 |
+
"""Return raw state dict from server."""
|
| 95 |
+
return payload
|
models.py
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from enum import Enum
|
| 2 |
+
from typing import Any, Optional
|
| 3 |
+
from pydantic import BaseModel, Field
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
class TaskType(str, Enum):
|
| 7 |
+
"""Types of identity verification tasks the environment supports."""
|
| 8 |
+
DOCUMENT_VERIFICATION = "document_verification"
|
| 9 |
+
CREDENTIAL_AUTHENTICATION = "credential_authentication"
|
| 10 |
+
PROFILE_MANAGEMENT = "profile_management"
|
| 11 |
+
FRAUD_DETECTION = "fraud_detection"
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
class DocumentType(str, Enum):
|
| 15 |
+
"""Types of identity documents."""
|
| 16 |
+
PASSPORT = "passport"
|
| 17 |
+
DRIVERS_LICENSE = "drivers_license"
|
| 18 |
+
NATIONAL_ID = "national_id"
|
| 19 |
+
BIRTH_CERTIFICATE = "birth_certificate"
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
class IdentityDocument(BaseModel):
|
| 23 |
+
"""Identity document data for verification."""
|
| 24 |
+
document_type: DocumentType = Field(description="Type of document")
|
| 25 |
+
document_number: str = Field(description="Document identifier number")
|
| 26 |
+
full_name: str = Field(description="Full name as on document")
|
| 27 |
+
date_of_birth: str = Field(description="Date of birth (YYYY-MM-DD)")
|
| 28 |
+
expiry_date: Optional[str] = Field(default=None, description="Document expiry date")
|
| 29 |
+
issuing_country: str = Field(description="Country that issued the document")
|
| 30 |
+
additional_fields: dict[str, Any] = Field(default_factory=dict, description="Additional document data")
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
class UserCredentials(BaseModel):
|
| 34 |
+
"""User credentials for authentication."""
|
| 35 |
+
username: str = Field(description="Username or email")
|
| 36 |
+
password: str = Field(description="Password")
|
| 37 |
+
two_factor_code: Optional[str] = Field(default=None, description="2FA code if enabled")
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
class UserProfile(BaseModel):
|
| 41 |
+
"""User profile information."""
|
| 42 |
+
user_id: str = Field(description="Unique user identifier")
|
| 43 |
+
full_name: str = Field(description="Full name")
|
| 44 |
+
email: str = Field(description="Email address")
|
| 45 |
+
phone: Optional[str] = Field(default=None, description="Phone number")
|
| 46 |
+
date_of_birth: Optional[str] = Field(default=None, description="Date of birth")
|
| 47 |
+
address: Optional[str] = Field(default=None, description="Physical address")
|
| 48 |
+
attributes: dict[str, Any] = Field(default_factory=dict, description="Additional attributes")
|
| 49 |
+
|
| 50 |
+
|
| 51 |
+
class VerificationResult(BaseModel):
|
| 52 |
+
"""Result of identity verification."""
|
| 53 |
+
verified: bool = Field(description="Whether verification passed")
|
| 54 |
+
confidence: float = Field(default=0.0, description="Confidence score 0-1")
|
| 55 |
+
issues: list[str] = Field(default_factory=list, description="List of verification issues")
|
| 56 |
+
checks_performed: dict[str, bool] = Field(default_factory=dict, description="Individual check results")
|
| 57 |
+
|
| 58 |
+
|
| 59 |
+
class CoreIdentityObservation(BaseModel):
|
| 60 |
+
"""Observation returned by the environment after reset or step."""
|
| 61 |
+
task_id: str = Field(description="Unique identifier for the task")
|
| 62 |
+
task_type: TaskType = Field(description="Type of identity task")
|
| 63 |
+
task_name: str = Field(description="Human-readable name of the task")
|
| 64 |
+
task_description: str = Field(description="Description of what needs to be done")
|
| 65 |
+
difficulty: str = Field(default="medium", description="Difficulty level: easy, medium, hard")
|
| 66 |
+
|
| 67 |
+
document: Optional[IdentityDocument] = Field(default=None, description="Document to verify")
|
| 68 |
+
credentials: Optional[UserCredentials] = Field(default=None, description="Credentials to authenticate")
|
| 69 |
+
profile: Optional[UserProfile] = Field(default=None, description="User profile to manage")
|
| 70 |
+
|
| 71 |
+
expected_verification: Optional[bool] = Field(default=None, description="Expected verification result")
|
| 72 |
+
challenge_data: dict[str, Any] = Field(default_factory=dict, description="Additional challenge data")
|
| 73 |
+
max_steps: int = Field(default=10, description="Maximum allowed steps")
|
| 74 |
+
|
| 75 |
+
|
| 76 |
+
class CoreIdentityAction(BaseModel):
|
| 77 |
+
"""Action to perform in the environment."""
|
| 78 |
+
verification: VerificationResult = Field(description="The verification result from the agent")
|
| 79 |
+
submit: bool = Field(default=False, description="Whether to submit the final result")
|
| 80 |
+
|
| 81 |
+
|
| 82 |
+
class CoreIdentityReward(BaseModel):
|
| 83 |
+
"""Reward components for the environment."""
|
| 84 |
+
accuracy: float = Field(default=0.0, description="Accuracy of verification")
|
| 85 |
+
completeness: float = Field(default=0.0, description="Completeness of checks performed")
|
| 86 |
+
efficiency: float = Field(default=0.0, description="Efficiency bonus for fewer steps")
|
| 87 |
+
total: float = Field(default=0.0, description="Total reward value")
|
openenv.yaml
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
spec_version: 1
|
| 2 |
+
name: core_identity_env
|
| 3 |
+
type: space
|
| 4 |
+
runtime: fastapi
|
| 5 |
+
app: server.app:app
|
| 6 |
+
port: 8000
|
pyproject.toml
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[project]
|
| 2 |
+
name = "core_identity_env"
|
| 3 |
+
version = "0.1.0"
|
| 4 |
+
description = "Core Identity environment for verification, authentication, and profile management"
|
| 5 |
+
readme = "README.md"
|
| 6 |
+
requires-python = ">=3.10"
|
| 7 |
+
dependencies = [
|
| 8 |
+
"openenv-core>=0.1.0",
|
| 9 |
+
"fastapi>=0.109.0",
|
| 10 |
+
"uvicorn>=0.27.0",
|
| 11 |
+
"pydantic>=2.5.0",
|
| 12 |
+
]
|
| 13 |
+
|
| 14 |
+
[project.scripts]
|
| 15 |
+
server = "uvicorn:main"
|
| 16 |
+
core-identity = "core_identity_env.server.app:app"
|
| 17 |
+
|
| 18 |
+
[build-system]
|
| 19 |
+
requires = ["setuptools>=61.0"]
|
| 20 |
+
build-backend = "setuptools.build_meta"
|
| 21 |
+
|
| 22 |
+
[tool.setuptools.packages.find]
|
| 23 |
+
where = ["."]
|
| 24 |
+
include = ["core_identity_env*"]
|
server/Dockerfile
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Core Identity Environment - Docker configuration for HF Spaces
|
| 2 |
+
|
| 3 |
+
ARG BASE_IMAGE=ghcr.io/meta-pytorch/openenv-base:latest
|
| 4 |
+
FROM ${BASE_IMAGE} AS builder
|
| 5 |
+
|
| 6 |
+
WORKDIR /app
|
| 7 |
+
|
| 8 |
+
ARG BUILD_MODE=standalone
|
| 9 |
+
|
| 10 |
+
COPY . /app/env
|
| 11 |
+
|
| 12 |
+
WORKDIR /app/env
|
| 13 |
+
|
| 14 |
+
RUN if ! command -v uv > /dev/null 2>&1; then \
|
| 15 |
+
curl -LsSf https://astral.sh/uv/install.sh | sh && \
|
| 16 |
+
mv /root/.local/bin/uv /usr/local/bin/uv && \
|
| 17 |
+
mv /root/.local/bin/uvx /usr/local/bin/uvx; \
|
| 18 |
+
fi
|
| 19 |
+
|
| 20 |
+
RUN apt-get update && apt-get install -y --no-install-recommends \
|
| 21 |
+
git \
|
| 22 |
+
&& rm -rf /var/lib/apt/lists/*
|
| 23 |
+
|
| 24 |
+
RUN --mount=type=cache,target=/root/.cache/uv \
|
| 25 |
+
if [ -f uv.lock ]; then \
|
| 26 |
+
uv sync --frozen --no-install-project --no-editable; \
|
| 27 |
+
else \
|
| 28 |
+
uv sync --no-install-project --no-editable; \
|
| 29 |
+
fi
|
| 30 |
+
|
| 31 |
+
RUN --mount=type=cache,target=/root/.cache/uv \
|
| 32 |
+
if [ -f uv.lock ]; then \
|
| 33 |
+
uv sync --frozen --no-editable; \
|
| 34 |
+
else \
|
| 35 |
+
uv sync --no-editable; \
|
| 36 |
+
fi
|
| 37 |
+
|
| 38 |
+
FROM ${BASE_IMAGE}
|
| 39 |
+
|
| 40 |
+
WORKDIR /app
|
| 41 |
+
|
| 42 |
+
COPY --from=builder /app/env/.venv /app/.venv
|
| 43 |
+
COPY --from=builder /app/env /app/env
|
| 44 |
+
|
| 45 |
+
ENV PATH="/app/.venv/bin:$PATH"
|
| 46 |
+
ENV PYTHONPATH="/app/env:$PYTHONPATH"
|
| 47 |
+
|
| 48 |
+
HEALTHCHECK --interval=30s --timeout=3s --start-period=10s --retries=3 \
|
| 49 |
+
CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:8000/health')" || exit 1
|
| 50 |
+
|
| 51 |
+
EXPOSE 8000
|
| 52 |
+
|
| 53 |
+
CMD ["sh", "-c", "cd /app/env && uvicorn core_identity_env.server.app:app --host 0.0.0.0 --port 8000"]
|
server/__init__.py
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from core_identity_env.server.core_identity_environment import CoreIdentityEnvironment
|
| 2 |
+
from core_identity_env.models import CoreIdentityAction, CoreIdentityObservation
|
| 3 |
+
|
| 4 |
+
|
| 5 |
+
def create_app():
|
| 6 |
+
"""Create and configure the FastAPI application for the Core Identity Environment."""
|
| 7 |
+
from openenv.core.env_server.http_server import create_app as create_openenv_app
|
| 8 |
+
return create_openenv_app(
|
| 9 |
+
CoreIdentityEnvironment,
|
| 10 |
+
CoreIdentityAction,
|
| 11 |
+
CoreIdentityObservation,
|
| 12 |
+
env_name="core_identity_env",
|
| 13 |
+
)
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
app = create_app()
|
server/core_identity_environment.py
ADDED
|
@@ -0,0 +1,237 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Core Identity Environment - server-side implementation."""
|
| 2 |
+
|
| 3 |
+
import random
|
| 4 |
+
from typing import Any, Dict, Optional
|
| 5 |
+
from dataclasses import dataclass, field
|
| 6 |
+
from uuid import uuid4
|
| 7 |
+
|
| 8 |
+
try:
|
| 9 |
+
from openenv.core.env_server.interfaces import Environment
|
| 10 |
+
from openenv.core.env_server.types import Action, Observation, State
|
| 11 |
+
except ImportError:
|
| 12 |
+
from openenv.core.env_server.interfaces import Environment
|
| 13 |
+
from openenv.core.env_server.types import Action, Observation, State
|
| 14 |
+
|
| 15 |
+
from core_identity_env.models import (
|
| 16 |
+
CoreIdentityObservation,
|
| 17 |
+
CoreIdentityAction,
|
| 18 |
+
VerificationResult,
|
| 19 |
+
TaskType,
|
| 20 |
+
IdentityDocument,
|
| 21 |
+
UserCredentials,
|
| 22 |
+
UserProfile,
|
| 23 |
+
)
|
| 24 |
+
from core_identity_env.tasks.definitions import (
|
| 25 |
+
get_all_tasks,
|
| 26 |
+
get_task_by_id,
|
| 27 |
+
CoreIdentityTask,
|
| 28 |
+
CoreIdentityTaskEvaluator,
|
| 29 |
+
GradingResult,
|
| 30 |
+
)
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
@dataclass
|
| 34 |
+
class _EpisodeState:
|
| 35 |
+
task: CoreIdentityTask
|
| 36 |
+
episode_id: str
|
| 37 |
+
current_step: int = 0
|
| 38 |
+
cumulative_reward: float = 0.0
|
| 39 |
+
submitted_verification: Dict[str, Any] = field(default_factory=dict)
|
| 40 |
+
episode_complete: bool = False
|
| 41 |
+
|
| 42 |
+
|
| 43 |
+
DIFFICULTY_WEIGHTS = {
|
| 44 |
+
"easy": 0.15,
|
| 45 |
+
"medium": 0.12,
|
| 46 |
+
"hard": 0.08,
|
| 47 |
+
}
|
| 48 |
+
|
| 49 |
+
|
| 50 |
+
class CoreIdentityEnvironment(Environment):
|
| 51 |
+
"""Server-side Core Identity Environment compliant with OpenEnv spec."""
|
| 52 |
+
|
| 53 |
+
def __init__(
|
| 54 |
+
self,
|
| 55 |
+
task_id: Optional[str] = None,
|
| 56 |
+
seed: Optional[int] = None,
|
| 57 |
+
max_steps: int = 10,
|
| 58 |
+
):
|
| 59 |
+
self._task_id = task_id
|
| 60 |
+
self._seed = seed
|
| 61 |
+
self._max_steps = max_steps
|
| 62 |
+
self._ep: Optional[_EpisodeState] = None
|
| 63 |
+
self._state = State(episode_id=str(uuid4()), step_count=0)
|
| 64 |
+
|
| 65 |
+
if seed is not None:
|
| 66 |
+
random.seed(seed)
|
| 67 |
+
|
| 68 |
+
def reset(
|
| 69 |
+
self,
|
| 70 |
+
seed: Optional[int] = None,
|
| 71 |
+
episode_id: Optional[str] = None,
|
| 72 |
+
task_id: Optional[str] = None,
|
| 73 |
+
**kwargs: Any,
|
| 74 |
+
) -> Observation:
|
| 75 |
+
if seed is not None:
|
| 76 |
+
random.seed(seed)
|
| 77 |
+
|
| 78 |
+
target_task_id = task_id or self._task_id
|
| 79 |
+
if target_task_id:
|
| 80 |
+
task = get_task_by_id(target_task_id)
|
| 81 |
+
else:
|
| 82 |
+
task = random.choice(get_all_tasks())
|
| 83 |
+
|
| 84 |
+
eid = episode_id or str(uuid4())
|
| 85 |
+
|
| 86 |
+
document = None
|
| 87 |
+
credentials = None
|
| 88 |
+
profile = None
|
| 89 |
+
|
| 90 |
+
if task.document:
|
| 91 |
+
document = IdentityDocument(**task.document)
|
| 92 |
+
if task.credentials:
|
| 93 |
+
credentials = UserCredentials(**task.credentials)
|
| 94 |
+
if task.profile:
|
| 95 |
+
profile = UserProfile(**task.profile)
|
| 96 |
+
|
| 97 |
+
self._ep = _EpisodeState(
|
| 98 |
+
task=task,
|
| 99 |
+
episode_id=eid,
|
| 100 |
+
current_step=0,
|
| 101 |
+
cumulative_reward=0.0,
|
| 102 |
+
submitted_verification={},
|
| 103 |
+
episode_complete=False,
|
| 104 |
+
)
|
| 105 |
+
self._state = State(episode_id=eid, step_count=0)
|
| 106 |
+
|
| 107 |
+
obs = self._build_observation(document, credentials, profile)
|
| 108 |
+
return Observation(
|
| 109 |
+
done=False,
|
| 110 |
+
reward=0.0,
|
| 111 |
+
metadata=obs.model_dump(),
|
| 112 |
+
)
|
| 113 |
+
|
| 114 |
+
def step(
|
| 115 |
+
self,
|
| 116 |
+
action: Action,
|
| 117 |
+
timeout_s: Optional[float] = None,
|
| 118 |
+
**kwargs: Any,
|
| 119 |
+
) -> Observation:
|
| 120 |
+
if self._ep is None:
|
| 121 |
+
return Observation(
|
| 122 |
+
done=True,
|
| 123 |
+
reward=0.0,
|
| 124 |
+
metadata={"error": "Environment not reset. Call reset() first."},
|
| 125 |
+
)
|
| 126 |
+
|
| 127 |
+
if self._ep.episode_complete:
|
| 128 |
+
return Observation(
|
| 129 |
+
done=True,
|
| 130 |
+
reward=0.0,
|
| 131 |
+
metadata={"error": "Episode already finished."},
|
| 132 |
+
)
|
| 133 |
+
|
| 134 |
+
action_data: Dict[str, Any] = {}
|
| 135 |
+
if hasattr(action, "data") and isinstance(action.data, dict):
|
| 136 |
+
action_data = action.data
|
| 137 |
+
elif isinstance(action, dict):
|
| 138 |
+
action_data = action
|
| 139 |
+
elif hasattr(action, "__dict__"):
|
| 140 |
+
action_data = vars(action)
|
| 141 |
+
|
| 142 |
+
try:
|
| 143 |
+
env_action = CoreIdentityAction.model_validate(action_data)
|
| 144 |
+
except Exception:
|
| 145 |
+
env_action = CoreIdentityAction(verification=VerificationResult())
|
| 146 |
+
|
| 147 |
+
self._ep.current_step += 1
|
| 148 |
+
self._state.step_count = self._ep.current_step
|
| 149 |
+
|
| 150 |
+
verification_dict = env_action.verification.model_dump()
|
| 151 |
+
self._ep.submitted_verification = verification_dict
|
| 152 |
+
|
| 153 |
+
evaluator = CoreIdentityTaskEvaluator(self._ep.task)
|
| 154 |
+
result = evaluator.grade(verification_dict)
|
| 155 |
+
|
| 156 |
+
reward_value = result.score * DIFFICULTY_WEIGHTS.get(self._ep.task.difficulty, 0.1)
|
| 157 |
+
|
| 158 |
+
terminal = env_action.submit or self._ep.current_step >= self._ep.task.max_steps
|
| 159 |
+
|
| 160 |
+
if terminal:
|
| 161 |
+
final_score = result.score
|
| 162 |
+
if self._ep.current_step <= self._ep.task.max_steps * 0.5:
|
| 163 |
+
final_score += 0.1
|
| 164 |
+
reward_value = min(1.0, final_score)
|
| 165 |
+
self._ep.episode_complete = True
|
| 166 |
+
|
| 167 |
+
self._ep.cumulative_reward += reward_value
|
| 168 |
+
|
| 169 |
+
document = None
|
| 170 |
+
credentials = None
|
| 171 |
+
profile = None
|
| 172 |
+
if self._ep.task.document:
|
| 173 |
+
document = IdentityDocument(**self._ep.task.document)
|
| 174 |
+
if self._ep.task.credentials:
|
| 175 |
+
credentials = UserCredentials(**self._ep.task.credentials)
|
| 176 |
+
if self._ep.task.profile:
|
| 177 |
+
profile = UserProfile(**self._ep.task.profile)
|
| 178 |
+
|
| 179 |
+
obs = self._build_observation(document, credentials, profile)
|
| 180 |
+
step_result = {
|
| 181 |
+
"observation": obs.model_dump(),
|
| 182 |
+
"reward": {
|
| 183 |
+
"value": reward_value,
|
| 184 |
+
"accuracy": result.accuracy,
|
| 185 |
+
"completeness": result.completeness,
|
| 186 |
+
"total": result.score,
|
| 187 |
+
"feedback": result.feedback,
|
| 188 |
+
},
|
| 189 |
+
"done": self._ep.episode_complete,
|
| 190 |
+
"info": {
|
| 191 |
+
"step": self._ep.current_step,
|
| 192 |
+
"is_final": self._ep.episode_complete,
|
| 193 |
+
},
|
| 194 |
+
}
|
| 195 |
+
|
| 196 |
+
return Observation(
|
| 197 |
+
done=self._ep.episode_complete,
|
| 198 |
+
reward=reward_value,
|
| 199 |
+
metadata=step_result,
|
| 200 |
+
)
|
| 201 |
+
|
| 202 |
+
@property
|
| 203 |
+
def state(self) -> State:
|
| 204 |
+
return self._state
|
| 205 |
+
|
| 206 |
+
def _build_observation(
|
| 207 |
+
self,
|
| 208 |
+
document: Optional[IdentityDocument],
|
| 209 |
+
credentials: Optional[UserCredentials],
|
| 210 |
+
profile: Optional[UserProfile],
|
| 211 |
+
) -> CoreIdentityObservation:
|
| 212 |
+
ep = self._ep
|
| 213 |
+
return CoreIdentityObservation(
|
| 214 |
+
task_id=ep.task.task_id,
|
| 215 |
+
task_type=TaskType(ep.task.task_type),
|
| 216 |
+
task_name=ep.task.name,
|
| 217 |
+
task_description=ep.task.description,
|
| 218 |
+
difficulty=ep.task.difficulty,
|
| 219 |
+
document=document,
|
| 220 |
+
credentials=credentials,
|
| 221 |
+
profile=profile,
|
| 222 |
+
expected_verification=ep.task.expected_verification,
|
| 223 |
+
challenge_data=ep.task.challenge_data,
|
| 224 |
+
max_steps=ep.task.max_steps,
|
| 225 |
+
)
|
| 226 |
+
|
| 227 |
+
def get_task_list(self) -> Dict[str, Any]:
|
| 228 |
+
return [
|
| 229 |
+
{
|
| 230 |
+
"task_id": t.task_id,
|
| 231 |
+
"name": t.name,
|
| 232 |
+
"task_type": t.task_type,
|
| 233 |
+
"difficulty": t.difficulty,
|
| 234 |
+
"description": t.description,
|
| 235 |
+
}
|
| 236 |
+
for t in get_all_tasks()
|
| 237 |
+
]
|
server/requirements.txt
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
fastapi>=0.109.0
|
| 2 |
+
uvicorn>=0.27.0
|
| 3 |
+
pydantic>=2.5.0
|
| 4 |
+
openenv-core
|
tasks/definitions.py
ADDED
|
@@ -0,0 +1,216 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Task definitions and graders for the Core Identity environment."""
|
| 2 |
+
|
| 3 |
+
from typing import List, Dict, Any
|
| 4 |
+
from dataclasses import dataclass
|
| 5 |
+
import re
|
| 6 |
+
from datetime import datetime, timedelta
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
@dataclass
|
| 10 |
+
class CoreIdentityTask:
|
| 11 |
+
task_id: str
|
| 12 |
+
name: str
|
| 13 |
+
description: str
|
| 14 |
+
task_type: str
|
| 15 |
+
difficulty: str
|
| 16 |
+
document: Dict[str, Any] | None
|
| 17 |
+
credentials: Dict[str, Any] | None
|
| 18 |
+
profile: Dict[str, Any] | None
|
| 19 |
+
expected_verification: bool
|
| 20 |
+
required_checks: List[str]
|
| 21 |
+
challenge_data: Dict[str, Any]
|
| 22 |
+
max_steps: int
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
@dataclass
|
| 26 |
+
class GradingResult:
|
| 27 |
+
accuracy: float
|
| 28 |
+
completeness: float
|
| 29 |
+
score: float
|
| 30 |
+
feedback: str
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
class CoreIdentityTaskEvaluator:
|
| 34 |
+
"""Evaluate responses for Core Identity tasks."""
|
| 35 |
+
|
| 36 |
+
def __init__(self, task: CoreIdentityTask):
|
| 37 |
+
self.task = task
|
| 38 |
+
|
| 39 |
+
def grade(self, response: Dict[str, Any]) -> GradingResult:
|
| 40 |
+
verification = response.get("verification", {})
|
| 41 |
+
verified = verification.get("verified", False)
|
| 42 |
+
issues = verification.get("issues", [])
|
| 43 |
+
checks_performed = verification.get("checks_performed", {})
|
| 44 |
+
|
| 45 |
+
expected = self.task.expected_verification
|
| 46 |
+
|
| 47 |
+
accuracy = 1.0 if verified == expected else 0.0
|
| 48 |
+
|
| 49 |
+
performed_count = sum(1 for v in checks_performed.values() if v)
|
| 50 |
+
required_count = len(self.task.required_checks)
|
| 51 |
+
completeness = performed_count / required_count if required_count > 0 else 0.5
|
| 52 |
+
|
| 53 |
+
if accuracy == 1.0:
|
| 54 |
+
base_score = 0.7
|
| 55 |
+
completeness_bonus = completeness * 0.3
|
| 56 |
+
else:
|
| 57 |
+
base_score = 0.0
|
| 58 |
+
completeness_bonus = 0.0
|
| 59 |
+
|
| 60 |
+
score = base_score + completeness_bonus
|
| 61 |
+
|
| 62 |
+
feedback = f"Verification: {verified}, Expected: {expected}, Checks: {performed_count}/{required_count}"
|
| 63 |
+
|
| 64 |
+
return GradingResult(
|
| 65 |
+
accuracy=accuracy,
|
| 66 |
+
completeness=completeness,
|
| 67 |
+
score=score,
|
| 68 |
+
feedback=feedback,
|
| 69 |
+
)
|
| 70 |
+
|
| 71 |
+
|
| 72 |
+
def create_document_verification_task() -> CoreIdentityTask:
|
| 73 |
+
return CoreIdentityTask(
|
| 74 |
+
task_id="doc_verify_passport",
|
| 75 |
+
name="Passport Verification",
|
| 76 |
+
description="Verify the authenticity of a passport document",
|
| 77 |
+
task_type="document_verification",
|
| 78 |
+
difficulty="medium",
|
| 79 |
+
document={
|
| 80 |
+
"document_type": "passport",
|
| 81 |
+
"document_number": "P12345678",
|
| 82 |
+
"full_name": "John Smith",
|
| 83 |
+
"date_of_birth": "1990-01-15",
|
| 84 |
+
"expiry_date": "2030-01-15",
|
| 85 |
+
"issuing_country": "United States",
|
| 86 |
+
},
|
| 87 |
+
credentials=None,
|
| 88 |
+
profile=None,
|
| 89 |
+
expected_verification=True,
|
| 90 |
+
required_checks=["expiry_check", "format_check", "country_validation"],
|
| 91 |
+
challenge_data={
|
| 92 |
+
"valid_passport": True,
|
| 93 |
+
"expired_document": False,
|
| 94 |
+
},
|
| 95 |
+
max_steps=5
|
| 96 |
+
)
|
| 97 |
+
|
| 98 |
+
|
| 99 |
+
def create_document_fraud_task() -> CoreIdentityTask:
|
| 100 |
+
return CoreIdentityTask(
|
| 101 |
+
task_id="doc_verify_fake_id",
|
| 102 |
+
name="Fake ID Detection",
|
| 103 |
+
description="Detect a potentially fraudulent ID document",
|
| 104 |
+
task_type="document_verification",
|
| 105 |
+
difficulty="hard",
|
| 106 |
+
document={
|
| 107 |
+
"document_type": "drivers_license",
|
| 108 |
+
"document_number": "DL999999999",
|
| 109 |
+
"full_name": "Jane Doe",
|
| 110 |
+
"date_of_birth": "1985-06-20",
|
| 111 |
+
"expiry_date": "2025-06-20",
|
| 112 |
+
"issuing_country": "California",
|
| 113 |
+
},
|
| 114 |
+
credentials=None,
|
| 115 |
+
profile=None,
|
| 116 |
+
expected_verification=False,
|
| 117 |
+
required_checks=["expiry_check", "format_check", "country_validation", "fraud_indicators"],
|
| 118 |
+
challenge_data={
|
| 119 |
+
"valid_passport": False,
|
| 120 |
+
"expired_document": True,
|
| 121 |
+
"suspicious_number": True,
|
| 122 |
+
"fraud_indicators": ["expired", "invalid_format"],
|
| 123 |
+
},
|
| 124 |
+
max_steps=8
|
| 125 |
+
)
|
| 126 |
+
|
| 127 |
+
|
| 128 |
+
def create_authentication_task() -> CoreIdentityTask:
|
| 129 |
+
return CoreIdentityTask(
|
| 130 |
+
task_id="auth_valid_creds",
|
| 131 |
+
name="Credential Authentication",
|
| 132 |
+
description="Verify user credentials for authentication",
|
| 133 |
+
task_type="credential_authentication",
|
| 134 |
+
difficulty="easy",
|
| 135 |
+
document=None,
|
| 136 |
+
credentials={
|
| 137 |
+
"username": "john.smith@email.com",
|
| 138 |
+
"password": "SecureP@ss123",
|
| 139 |
+
},
|
| 140 |
+
profile=None,
|
| 141 |
+
expected_verification=True,
|
| 142 |
+
required_checks=["username_format", "password_strength"],
|
| 143 |
+
challenge_data={
|
| 144 |
+
"valid_credentials": True,
|
| 145 |
+
"account_locked": False,
|
| 146 |
+
},
|
| 147 |
+
max_steps=3
|
| 148 |
+
)
|
| 149 |
+
|
| 150 |
+
|
| 151 |
+
def create_authentication_fraud_task() -> CoreIdentityTask:
|
| 152 |
+
return CoreIdentityTask(
|
| 153 |
+
task_id="auth_brute_force",
|
| 154 |
+
name="Brute Force Detection",
|
| 155 |
+
description="Detect and block brute force authentication attempts",
|
| 156 |
+
task_type="credential_authentication",
|
| 157 |
+
difficulty="hard",
|
| 158 |
+
document=None,
|
| 159 |
+
credentials={
|
| 160 |
+
"username": "admin@company.com",
|
| 161 |
+
"password": "wrong_password",
|
| 162 |
+
},
|
| 163 |
+
profile=None,
|
| 164 |
+
expected_verification=False,
|
| 165 |
+
required_checks=["username_format", "password_strength", "attempt_tracking", "ip_validation"],
|
| 166 |
+
challenge_data={
|
| 167 |
+
"valid_credentials": False,
|
| 168 |
+
"failed_attempts": 5,
|
| 169 |
+
"suspicious_ip": True,
|
| 170 |
+
"time_window": "5 minutes",
|
| 171 |
+
},
|
| 172 |
+
max_steps=6
|
| 173 |
+
)
|
| 174 |
+
|
| 175 |
+
|
| 176 |
+
def create_profile_task() -> CoreIdentityTask:
|
| 177 |
+
return CoreIdentityTask(
|
| 178 |
+
task_id="profile_update",
|
| 179 |
+
name="Profile Update Validation",
|
| 180 |
+
description="Validate a user profile update request",
|
| 181 |
+
task_type="profile_management",
|
| 182 |
+
difficulty="medium",
|
| 183 |
+
document=None,
|
| 184 |
+
credentials=None,
|
| 185 |
+
profile={
|
| 186 |
+
"user_id": "user_12345",
|
| 187 |
+
"full_name": "Alice Johnson",
|
| 188 |
+
"email": "alice.j@newdomain.com",
|
| 189 |
+
"phone": "+1-555-987-6543",
|
| 190 |
+
"date_of_birth": "1992-03-10",
|
| 191 |
+
},
|
| 192 |
+
expected_verification=True,
|
| 193 |
+
required_checks=["email_format", "phone_format", "age_verification"],
|
| 194 |
+
challenge_data={
|
| 195 |
+
"valid_request": True,
|
| 196 |
+
"requested_changes": ["email", "phone"],
|
| 197 |
+
},
|
| 198 |
+
max_steps=5
|
| 199 |
+
)
|
| 200 |
+
|
| 201 |
+
|
| 202 |
+
def get_all_tasks() -> List[CoreIdentityTask]:
|
| 203 |
+
return [
|
| 204 |
+
create_document_verification_task(),
|
| 205 |
+
create_document_fraud_task(),
|
| 206 |
+
create_authentication_task(),
|
| 207 |
+
create_authentication_fraud_task(),
|
| 208 |
+
create_profile_task(),
|
| 209 |
+
]
|
| 210 |
+
|
| 211 |
+
|
| 212 |
+
def get_task_by_id(task_id: str) -> CoreIdentityTask:
|
| 213 |
+
for task in get_all_tasks():
|
| 214 |
+
if task.task_id == task_id:
|
| 215 |
+
return task
|
| 216 |
+
raise ValueError(f"Task not found: {task_id}")
|