Skip to content
Open
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
48 changes: 48 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,54 @@ pytest
python -m opensymphony.gateway.http
```

The server listens on `0.0.0.0:8000` by default. To customize:

```bash
# Custom host/port
GATEWAY_HOST=127.0.0.1 GATEWAY_PORT=18792 python -m opensymphony.gateway.http

# Run with uvicorn directly (more control)
pip install "uvicorn[standard]"
uvicorn opensymphony.gateway.http:create_app --factory --host 127.0.0.1 --port 18792
```

### Gateway Endpoints (excerpt)

| Method | Path | Purpose |
|---|---|---|
| `GET` | `/health` | liveness, provider list, soul/tool counts |
| `GET` | `/souls` | list loaded souls |
| `POST` | `/chat` | send a message to a soul (`{"message": "...", "soul_id": "default"}`) |
| `POST` | `/human/chat` | human-facing chat via IntentBridge (`{"message": "...", "user_id": "..."}`) |
| `POST` | `/pipeline/run` | run a declarative pipeline |
| `GET` | `/governance/health` | governance subsystem status |
| `GET` | `/telemetry/summary` | today's LLM/tool/error counters |
| `GET` | `/skills` | skills registry |

For the full endpoint list, see `gateway/http.py` — every handler is decorated
with `@app.get` or `@app.post` near the top of the file.

### Provider configuration

The gateway picks up LLM credentials from environment variables:

```bash
export MIMO_API_KEY=... # or any other provider
export DEEPSEEK_API_KEY=...
export MOONSHOT_API_KEY=...
export ZHIPU_API_KEY=...
export MINIMAX_API_KEY=...
export OPENAI_API_KEY=...

# Optional: override the default model per provider
export MIMO_MODEL=mimo-v2.5
export DEEPSEEK_MODEL=deepseek-chat
export MINIMAX_MODEL=MiniMax-M3
```

See `opensymphony/llm/router.py` `create_router_from_env()` for the full
provider list.

### Example: Define a Soul

```yaml
Expand Down
32 changes: 32 additions & 0 deletions tests/test_tool_use.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,38 @@ def test_parse_tool_call(self):
def test_parse_tool_call_none(self):
assert Agent._parse_tool_call("no tool call here") is None

def test_parse_tool_call_windows_path(self):
"""Regression for issue #3: raw Windows path with single backslashes
(e.g. \\Users\\foo) should be repaired, not silently dropped.
"""
text = r'TOOL_CALL: {"name": "file_read", "params": {"path": "C:\\Users\\foo\\bar.txt"}}'
result = Agent._parse_tool_call(text)
assert result == {"name": "file_read",
"params": {"path": "C:\\Users\\foo\\bar.txt"}}

def test_parse_tool_call_preserves_valid_escapes(self):
"""Regression for issue #3: valid JSON escapes (\\n, \\t, \\\\) must
round-trip through the repair step unchanged.
"""
text = r'TOOL_CALL: {"name": "echo", "params": {"msg": "line1\nline2\ttab"}}'
result = Agent._parse_tool_call(text)
assert result == {"name": "echo", "params": {"msg": "line1\nline2\ttab"}}

def test_parse_tool_call_unicode_escape_preserved(self):
"""Regression for issue #3: \\uXXXX escapes are valid and must not
be double-escaped by the repair regex.
"""
text = r'TOOL_CALL: {"name": "echo", "params": {"q": "caf\u00e9"}}'
result = Agent._parse_tool_call(text)
assert result == {"name": "echo", "params": {"q": "café"}}

def test_parse_tool_call_rejects_total_garbage(self):
"""Regression for issue #3: after repair, truly invalid JSON still
returns None (no crash, no silent success).
"""
result = Agent._parse_tool_call("TOOL_CALL: {not even close")
assert result is None

def test_react_loop_with_tool(self, tmp_path):
"""Agent reads a file then gives final answer."""
# Setup: write a file
Expand Down