mirror of
https://github.com/langchain-ai/example-tool-server.git
synced 2026-06-30 22:17:55 -04:00
update readme, add option to run without auth (#2)
update readme add option to run without auth
This commit is contained in:
@@ -13,33 +13,117 @@ This server implements the following example tools:
|
||||
|
||||
## Usage
|
||||
|
||||
### Local
|
||||
|
||||
#### No Authentication
|
||||
|
||||
If you want to spin the server up locally to test it out without authentication, you can run the following command:
|
||||
|
||||
```shell
|
||||
DISABLE_AUTH=true uv run uvicorn app.server:app
|
||||
```
|
||||
|
||||
You'll need to have `uv` installed: https://docs.astral.sh/uv/
|
||||
|
||||
Once the server is running, if auth is disabled, you'll be able to access the API documentation at: http://localhost:8000/docs
|
||||
|
||||
#### With Authentication
|
||||
|
||||
The server implements a very basic form of authentication that supports a single user. To use it, you'll need to set an `APP_SECRET` environment variable.
|
||||
|
||||
|
||||
1. Generate a secret using your favorite random number generator
|
||||
|
||||
```shell
|
||||
export APP_SECRET=$(openssl rand -base64 32 )
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
```shell
|
||||
export APP_SECRET=$(head -c 32 /dev/urandom | base64)
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
```shell
|
||||
export APP_SECRET="some_super_secure_password"
|
||||
```
|
||||
|
||||
2. Run with `uv`
|
||||
|
||||
```shell
|
||||
APP_SECRET=$APP_SECRET uv run uvicorn app.server:app
|
||||
````
|
||||
|
||||
### Docker
|
||||
|
||||
1. Build with docker
|
||||
|
||||
```shell
|
||||
docker build -t example-tool-server .
|
||||
```
|
||||
|
||||
2. Generate a secret using your favorite random number generator
|
||||
|
||||
|
||||
```shell
|
||||
export APP_SECRET=$(openssl rand -base64 32 )
|
||||
```
|
||||
|
||||
```shell
|
||||
export APP_SECRET=$(head -c 32 /dev/urandom | base64)
|
||||
```
|
||||
|
||||
```shell
|
||||
export APP_SECRET=$( let your cat walk across your keyboard)
|
||||
```
|
||||
|
||||
|
||||
|
||||
```shell
|
||||
docker build -t example-tool-server .
|
||||
```
|
||||
2. Generate a secret key
|
||||
3. Run the image locally
|
||||
```shell
|
||||
docker run -e APP_SECRET=$APP_SECRET -p 8080:8080 example-tool-server
|
||||
```
|
||||
|
||||
Alternatively, deploy to your favorite cloud provider.
|
||||
|
||||
|
||||
## Client
|
||||
|
||||
Once the server is running you can use the universal-tool-client to interact with it.
|
||||
|
||||
```shell
|
||||
docker run -e APP_SECRET=$APP_SECRET -p 8080:8080 example-tool-server
|
||||
pip install universal-tool-client
|
||||
```
|
||||
|
||||
### Client
|
||||
|
||||
or deploy to your favorite cloud provider
|
||||
```python
|
||||
from universal_tool_client import get_sync_client
|
||||
|
||||
url = "http://localhost:8000"
|
||||
headers = {
|
||||
"Authorization": "YOUR SECRET GOES HERE",
|
||||
}
|
||||
|
||||
client = get_sync_client(url=url, headers=headers)
|
||||
|
||||
print(client.health()) # Health check
|
||||
print(client.info()) # Server version and other information
|
||||
|
||||
# List tools
|
||||
print(client.tools.list()) # List of tools
|
||||
# Call a tool
|
||||
print(client.tools.call("echo", {"msg": "hello"})) # hello!
|
||||
|
||||
# Get the tools as a LangchainTools object
|
||||
tools = client.tools.as_langchain_tools()
|
||||
```
|
||||
|
||||
#### Curl
|
||||
|
||||
No authentication:
|
||||
|
||||
```shell
|
||||
curl -X 'POST' \
|
||||
'http://127.0.0.1:8000/tools/call' \
|
||||
-H 'accept: application/json' \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{
|
||||
"$schema": "otc://1.0",
|
||||
"request": {
|
||||
"tool_id": "get_weather@1.0.0",
|
||||
"input": { "city": "san francisco"}
|
||||
}
|
||||
}'
|
||||
```
|
||||
|
||||
With authentication:
|
||||
|
||||
Add the `Authorization` header with the secret you generated earlier.
|
||||
|
||||
e.g., `-H 'Authorization: YOUR SECRET`
|
||||
+13
-1
@@ -11,6 +11,8 @@ from app.tools.github import get_github_issues
|
||||
from app.tools.hackernews import search_hackernews
|
||||
from app.tools.reddit import search_reddit_news
|
||||
|
||||
DISABLE_AUTH = os.environ.get("DISABLE_AUTH", "").lower() in ("true", "1")
|
||||
|
||||
|
||||
def _get_app_secret() -> str:
|
||||
"""Get the app secret from the environment.
|
||||
@@ -19,6 +21,12 @@ def _get_app_secret() -> str:
|
||||
a single "user" with a single secret key.
|
||||
"""
|
||||
secret = os.environ.get("APP_SECRET")
|
||||
|
||||
if DISABLE_AUTH:
|
||||
if secret:
|
||||
raise ValueError("APP_SECRET is not needed when DISABLE_AUTH is enabled.")
|
||||
return ""
|
||||
|
||||
if not secret:
|
||||
raise ValueError("APP_SECRET environment variable is required.")
|
||||
if secret != secret.strip():
|
||||
@@ -32,7 +40,7 @@ APP_SECRET = _get_app_secret()
|
||||
app = Server()
|
||||
|
||||
|
||||
@app.tool()
|
||||
@app.add_tool()
|
||||
async def echo(msg: str) -> str:
|
||||
"""Echo a message appended with an exclamation mark."""
|
||||
return msg + "!"
|
||||
@@ -66,6 +74,10 @@ app.add_auth(auth)
|
||||
@auth.authenticate
|
||||
async def authenticate(authorization: str) -> dict:
|
||||
"""Authenticate the user based on the Authorization header."""
|
||||
if DISABLE_AUTH:
|
||||
return {
|
||||
"identity": "unauthenticated-user",
|
||||
}
|
||||
if not authorization or not hmac.compare_digest(authorization, APP_SECRET):
|
||||
raise Auth.exceptions.HTTPException(status_code=401, detail="Unauthorized")
|
||||
|
||||
|
||||
@@ -11,4 +11,5 @@ dependencies = [
|
||||
[dependency-groups]
|
||||
test = [
|
||||
"ruff>=0.9.9",
|
||||
"universal-tool-client>=0.0.2",
|
||||
]
|
||||
|
||||
@@ -137,13 +137,17 @@ dependencies = [
|
||||
[package.dev-dependencies]
|
||||
test = [
|
||||
{ name = "ruff" },
|
||||
{ name = "universal-tool-client" },
|
||||
]
|
||||
|
||||
[package.metadata]
|
||||
requires-dist = [{ name = "universal-tool-server", specifier = ">=0.0.3" }]
|
||||
|
||||
[package.metadata.requires-dev]
|
||||
test = [{ name = "ruff", specifier = ">=0.9.9" }]
|
||||
test = [
|
||||
{ name = "ruff", specifier = ">=0.9.9" },
|
||||
{ name = "universal-tool-client", specifier = ">=0.0.2" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fastapi"
|
||||
@@ -578,6 +582,20 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/26/9f/ad63fc0248c5379346306f8668cda6e2e2e9c95e01216d2b8ffd9ff037d0/typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d", size = 37438 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "universal-tool-client"
|
||||
version = "0.0.2"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "httpx" },
|
||||
{ name = "langchain-core" },
|
||||
{ name = "orjson" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/8d/8a/b4a1e9db2a958b40498a13547a32b878eef3a54981292d185e97d1d1fe05/universal_tool_client-0.0.2.tar.gz", hash = "sha256:8b78ae67b439b824430b61b6974fa1ec488fe346cfc1dbe3909149894579991b", size = 56609 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/66/ad/cd0bbdb5cb9dc329ab187d35494170cddb84c76e3da861f37910a1c33db8/universal_tool_client-0.0.2-py3-none-any.whl", hash = "sha256:b7cd9763634ac4064b2e8b5dd44292e868406d7102cabb486e53fc4236d31643", size = 8348 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "universal-tool-server"
|
||||
version = "0.0.3"
|
||||
|
||||
Reference in New Issue
Block a user