From 85cec03cd0782eee6a486152fbd2568051df2351 Mon Sep 17 00:00:00 2001 From: lawcontinue Date: Sun, 28 Jun 2026 17:58:27 +0800 Subject: [PATCH] docs+test: gateway quickstart + regression tests for issue #3 - README: expand 'Start the API server' with custom host/port, uvicorn example, endpoint table, provider env vars - tests/test_tool_use.py: add 4 regression tests for _parse_tool_call invalid-escape fallback: * test_parse_tool_call_windows_path * test_parse_tool_call_preserves_valid_escapes * test_parse_tool_call_unicode_escape_preserved * test_parse_tool_call_rejects_total_garbage Closes follow-up on issue #3 (which was fixed in 9e7878c without tests). --- README.md | 48 ++++++++++++++++++++++++++++++++++++++++++ tests/test_tool_use.py | 32 ++++++++++++++++++++++++++++ 2 files changed, 80 insertions(+) diff --git a/README.md b/README.md index 6800925..e9824c9 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/tests/test_tool_use.py b/tests/test_tool_use.py index 0f7401a..7984d23 100644 --- a/tests/test_tool_use.py +++ b/tests/test_tool_use.py @@ -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