mirror of
https://github.com/rowboatlabs/rowboat.git
synced 2026-06-06 19:35:44 +02:00
add auth for copilot and agents
This commit is contained in:
parent
cdc96e7ce3
commit
ad4ea23d79
14 changed files with 173 additions and 43 deletions
|
|
@ -5,3 +5,5 @@ AUTH0_BASE_URL=http://localhost:3000
|
||||||
AUTH0_ISSUER_BASE_URL=<AUTH0_ISSUER_BASE_URL>
|
AUTH0_ISSUER_BASE_URL=<AUTH0_ISSUER_BASE_URL>
|
||||||
AUTH0_CLIENT_ID=<AUTH0_CLIENT_ID>
|
AUTH0_CLIENT_ID=<AUTH0_CLIENT_ID>
|
||||||
AUTH0_CLIENT_SECRET=<AUTH0_CLIENT_SECRET>
|
AUTH0_CLIENT_SECRET=<AUTH0_CLIENT_SECRET>
|
||||||
|
COPILOT_API_KEY=test
|
||||||
|
AGENTS_API_KEY=test
|
||||||
|
|
@ -1 +1,2 @@
|
||||||
OPENAI_API_KEY=<your_openai_api_key>
|
OPENAI_API_KEY=<your_openai_api_key>
|
||||||
|
API_KEY=test
|
||||||
|
|
@ -74,8 +74,9 @@ Copy `.env.example` to `.env` and add your API keys
|
||||||
- To set up the server on a remote machine: `gunicorn -b 0.0.0.0:4040 src.app.main:app`
|
- To set up the server on a remote machine: `gunicorn -b 0.0.0.0:4040 src.app.main:app`
|
||||||
|
|
||||||
### 🖥️ Run test client
|
### 🖥️ Run test client
|
||||||
`python -m tests.app_client --sample_request default_example.json`
|
`python -m tests.app_client --sample_request default_example.json --api_key test`
|
||||||
- `--sample_request`: Path to the sample request file, under `tests/sample_requests` folder
|
- `--sample_request`: Path to the sample request file, under `tests/sample_requests` folder
|
||||||
|
- `--api_key`: API key to use for authentication. This is the same key as the one in the `.env` file.
|
||||||
|
|
||||||
## 📖 More details
|
## 📖 More details
|
||||||
|
|
||||||
|
|
|
||||||
38
apps/agents/poetry.lock
generated
38
apps/agents/poetry.lock
generated
|
|
@ -342,6 +342,29 @@ Werkzeug = ">=3.1"
|
||||||
async = ["asgiref (>=3.2)"]
|
async = ["asgiref (>=3.2)"]
|
||||||
dotenv = ["python-dotenv"]
|
dotenv = ["python-dotenv"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "gunicorn"
|
||||||
|
version = "23.0.0"
|
||||||
|
description = "WSGI HTTP Server for UNIX"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.7"
|
||||||
|
groups = ["main"]
|
||||||
|
markers = "python_version <= \"3.11\" or python_version >= \"3.12\""
|
||||||
|
files = [
|
||||||
|
{file = "gunicorn-23.0.0-py3-none-any.whl", hash = "sha256:ec400d38950de4dfd418cff8328b2c8faed0edb0d517d3394e457c317908ca4d"},
|
||||||
|
{file = "gunicorn-23.0.0.tar.gz", hash = "sha256:f014447a0101dc57e294f6c18ca6b40227a4c90e9bdb586042628030cba004ec"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
packaging = "*"
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
eventlet = ["eventlet (>=0.24.1,!=0.36.0)"]
|
||||||
|
gevent = ["gevent (>=1.4.0)"]
|
||||||
|
setproctitle = ["setproctitle"]
|
||||||
|
testing = ["coverage", "eventlet", "gevent", "pytest", "pytest-cov"]
|
||||||
|
tornado = ["tornado (>=0.2)"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "h11"
|
name = "h11"
|
||||||
version = "0.14.0"
|
version = "0.14.0"
|
||||||
|
|
@ -930,6 +953,19 @@ files = [
|
||||||
[package.dependencies]
|
[package.dependencies]
|
||||||
et-xmlfile = "*"
|
et-xmlfile = "*"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "packaging"
|
||||||
|
version = "24.2"
|
||||||
|
description = "Core utilities for Python packages"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.8"
|
||||||
|
groups = ["main"]
|
||||||
|
markers = "python_version <= \"3.11\" or python_version >= \"3.12\""
|
||||||
|
files = [
|
||||||
|
{file = "packaging-24.2-py3-none-any.whl", hash = "sha256:09abb1bccd265c01f4a3aa3f7a7db064b36514d2cba19a2f694fe6150451a759"},
|
||||||
|
{file = "packaging-24.2.tar.gz", hash = "sha256:c228a6dc5e932d346bc5739379109d49e8853dd8223571c7c5b55260edc0b97f"},
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pandas"
|
name = "pandas"
|
||||||
version = "2.2.3"
|
version = "2.2.3"
|
||||||
|
|
@ -1619,4 +1655,4 @@ test = ["pytest (>=6.0.0)", "setuptools (>=65)"]
|
||||||
[metadata]
|
[metadata]
|
||||||
lock-version = "2.1"
|
lock-version = "2.1"
|
||||||
python-versions = ">=3.10,<4.0"
|
python-versions = ">=3.10,<4.0"
|
||||||
content-hash = "789b01787d1fa737ae3ad38f642c5c1bcbb142fd38bbbd7a3906a9300b232eb3"
|
content-hash = "ef220af6b184e760ccc9b45e26ec9f58a54cb9327c562a612798433a7f9c08e4"
|
||||||
|
|
|
||||||
|
|
@ -64,3 +64,4 @@ urllib3 = "^2.2.3"
|
||||||
websockets = "^13.1"
|
websockets = "^13.1"
|
||||||
Werkzeug = "^3.0.5"
|
Werkzeug = "^3.0.5"
|
||||||
wheel = "^0.44.0"
|
wheel = "^0.44.0"
|
||||||
|
gunicorn = "^23.0.0"
|
||||||
|
|
|
||||||
|
|
@ -1,51 +1,82 @@
|
||||||
annotated-types==0.7.0
|
annotated-types==0.7.0
|
||||||
anyio==4.6.2
|
anyio==4.8.0
|
||||||
beautifulsoup4==4.12.3
|
beautifulsoup4==4.12.3
|
||||||
blinker==1.8.2
|
blinker==1.9.0
|
||||||
certifi==2024.8.30
|
build==1.2.2.post1
|
||||||
charset-normalizer==3.4.0
|
CacheControl==0.14.2
|
||||||
click==8.1.7
|
certifi==2024.12.14
|
||||||
|
cffi==1.17.1
|
||||||
|
charset-normalizer==3.4.1
|
||||||
|
cleo==2.1.0
|
||||||
|
click==8.1.8
|
||||||
|
crashtest==0.4.1
|
||||||
|
distlib==0.3.9
|
||||||
distro==1.9.0
|
distro==1.9.0
|
||||||
dnspython==2.7.0
|
dnspython==2.7.0
|
||||||
|
dulwich==0.22.7
|
||||||
et_xmlfile==2.0.0
|
et_xmlfile==2.0.0
|
||||||
eval_type_backport==0.2.0
|
eval_type_backport==0.2.2
|
||||||
firecrawl==1.4.0
|
fastjsonschema==2.21.1
|
||||||
Flask==3.0.3
|
filelock==3.17.0
|
||||||
|
firecrawl==1.9.0
|
||||||
|
Flask==3.1.0
|
||||||
|
gunicorn==23.0.0
|
||||||
h11==0.14.0
|
h11==0.14.0
|
||||||
httpcore==1.0.6
|
httpcore==1.0.7
|
||||||
httpx==0.27.2
|
httpx==0.27.2
|
||||||
idna==3.10
|
idna==3.10
|
||||||
|
installer==0.7.0
|
||||||
itsdangerous==2.2.0
|
itsdangerous==2.2.0
|
||||||
Jinja2==3.1.4
|
jaraco.classes==3.4.0
|
||||||
|
jaraco.context==6.0.1
|
||||||
|
jaraco.functools==4.1.0
|
||||||
|
Jinja2==3.1.5
|
||||||
jiter==0.6.1
|
jiter==0.6.1
|
||||||
jsonpath-python==1.0.6
|
jsonpath-python==1.0.6
|
||||||
|
keyring==25.6.0
|
||||||
lxml==5.3.0
|
lxml==5.3.0
|
||||||
markdownify==0.13.1
|
markdownify==0.13.1
|
||||||
MarkupSafe==3.0.2
|
MarkupSafe==3.0.2
|
||||||
|
more-itertools==10.6.0
|
||||||
|
msgpack==1.1.0
|
||||||
mypy-extensions==1.0.0
|
mypy-extensions==1.0.0
|
||||||
nest-asyncio==1.6.0
|
nest-asyncio==1.6.0
|
||||||
numpy==2.1.2
|
numpy==2.2.1
|
||||||
openai==1.52.2
|
openai==1.59.7
|
||||||
openpyxl==3.1.5
|
openpyxl==3.1.5
|
||||||
|
packaging==24.2
|
||||||
pandas==2.2.3
|
pandas==2.2.3
|
||||||
pydantic==2.9.2
|
pkginfo==1.12.0
|
||||||
pydantic_core==2.23.4
|
platformdirs==4.3.6
|
||||||
|
poetry==2.0.1
|
||||||
|
poetry-core==2.0.1
|
||||||
|
pycparser==2.22
|
||||||
|
pydantic==2.10.5
|
||||||
|
pydantic_core==2.27.2
|
||||||
pymongo==4.10.1
|
pymongo==4.10.1
|
||||||
python-dateutil==2.8.2
|
pyproject_hooks==1.2.0
|
||||||
|
python-dateutil==2.9.0.post0
|
||||||
python-docx==1.1.2
|
python-docx==1.1.2
|
||||||
python-dotenv==1.0.1
|
python-dotenv==1.0.1
|
||||||
pytz==2024.2
|
pytz==2024.2
|
||||||
|
RapidFuzz==3.11.0
|
||||||
requests==2.32.3
|
requests==2.32.3
|
||||||
setuptools==75.1.0
|
requests-toolbelt==1.0.0
|
||||||
six==1.16.0
|
setuptools==75.8.0
|
||||||
|
shellingham==1.5.4
|
||||||
|
six==1.17.0
|
||||||
sniffio==1.3.1
|
sniffio==1.3.1
|
||||||
soupsieve==2.6
|
soupsieve==2.6
|
||||||
tabulate==0.9.0
|
tabulate==0.9.0
|
||||||
tqdm==4.66.5
|
tomlkit==0.13.2
|
||||||
|
tqdm==4.67.1
|
||||||
|
trove-classifiers==2025.1.15.22
|
||||||
typing-inspect==0.9.0
|
typing-inspect==0.9.0
|
||||||
typing_extensions==4.12.2
|
typing_extensions==4.12.2
|
||||||
tzdata==2024.2
|
tzdata==2024.2
|
||||||
urllib3==2.2.3
|
urllib3==2.3.0
|
||||||
|
virtualenv==20.29.1
|
||||||
websockets==13.1
|
websockets==13.1
|
||||||
Werkzeug==3.0.5
|
Werkzeug==3.1.3
|
||||||
wheel==0.44.0
|
wheel==0.44.0
|
||||||
|
xattr==1.1.4
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
from flask import Flask, request, jsonify
|
from flask import Flask, request, jsonify
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
from functools import wraps
|
||||||
|
import os
|
||||||
|
|
||||||
from src.graph.core import run_turn
|
from src.graph.core import run_turn
|
||||||
from src.graph.tools import RAG_TOOL, CLOSE_CHAT_TOOL
|
from src.graph.tools import RAG_TOOL, CLOSE_CHAT_TOOL
|
||||||
|
|
@ -9,11 +11,30 @@ logger = common_logger
|
||||||
|
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
|
|
||||||
|
@app.route("/health", methods=["GET"])
|
||||||
|
def health():
|
||||||
|
return jsonify({"status": "ok"})
|
||||||
|
|
||||||
@app.route("/")
|
@app.route("/")
|
||||||
def home():
|
def home():
|
||||||
return "Hello, World!"
|
return "Hello, World!"
|
||||||
|
|
||||||
|
def require_api_key(f):
|
||||||
|
@wraps(f)
|
||||||
|
def decorated(*args, **kwargs):
|
||||||
|
auth_header = request.headers.get('Authorization')
|
||||||
|
if not auth_header or not auth_header.startswith('Bearer '):
|
||||||
|
return jsonify({'error': 'Missing or invalid authorization header'}), 401
|
||||||
|
|
||||||
|
token = auth_header.split('Bearer ')[1]
|
||||||
|
if token != os.environ.get('API_KEY'):
|
||||||
|
return jsonify({'error': 'Invalid API key'}), 403
|
||||||
|
|
||||||
|
return f(*args, **kwargs)
|
||||||
|
return decorated
|
||||||
|
|
||||||
@app.route("/chat", methods=["POST"])
|
@app.route("/chat", methods=["POST"])
|
||||||
|
@require_api_key
|
||||||
def chat():
|
def chat():
|
||||||
print('='*200)
|
print('='*200)
|
||||||
logger.info('='*200)
|
logger.info('='*200)
|
||||||
|
|
|
||||||
|
|
@ -7,13 +7,16 @@ if __name__ == "__main__":
|
||||||
import argparse
|
import argparse
|
||||||
parser = argparse.ArgumentParser()
|
parser = argparse.ArgumentParser()
|
||||||
parser.add_argument('--sample_request', type=str, required=True, help='Sample request JSON file name under tests/sample_requests/')
|
parser.add_argument('--sample_request', type=str, required=True, help='Sample request JSON file name under tests/sample_requests/')
|
||||||
|
parser.add_argument('--api_key', type=str, required=True, help='API key to use for authentication')
|
||||||
|
parser.add_argument('--host', type=str, required=False, help='Host to use for the request', default='http://localhost:4040')
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
request = read_json_from_file(f"./tests/sample_requests/{args.sample_request}").get("lastRequest", {})
|
request = read_json_from_file(f"./tests/sample_requests/{args.sample_request}").get("lastRequest", {})
|
||||||
print("Sending request...")
|
print("Sending request...")
|
||||||
response = requests.post(
|
response = requests.post(
|
||||||
"http://localhost:4040/chat",
|
f"{args.host}/chat",
|
||||||
json=request
|
json=request,
|
||||||
|
headers={'Authorization': f'Bearer {args.api_key}'}
|
||||||
).json()
|
).json()
|
||||||
print("Output: ")
|
print("Output: ")
|
||||||
print(response)
|
print(response)
|
||||||
|
|
@ -24,6 +24,7 @@ pip install -r requirements.txt
|
||||||
4. Set up your OpenAI API key:
|
4. Set up your OpenAI API key:
|
||||||
```bash
|
```bash
|
||||||
export OPENAI_API_KEY='your-api-key-here' # On Windows, use: set OPENAI_API_KEY=your-api-key-here
|
export OPENAI_API_KEY='your-api-key-here' # On Windows, use: set OPENAI_API_KEY=your-api-key-here
|
||||||
|
export API_KEY='test-api-key' # set a shared API key for the application
|
||||||
```
|
```
|
||||||
|
|
||||||
## Running the Application
|
## Running the Application
|
||||||
|
|
@ -33,24 +34,27 @@ export OPENAI_API_KEY='your-api-key-here' # On Windows, use: set OPENAI_API_KEY
|
||||||
python app.py
|
python app.py
|
||||||
```
|
```
|
||||||
|
|
||||||
The server will start on `http://localhost:5000`
|
The server will start on `http://localhost:3002`
|
||||||
|
|
||||||
## API Usage
|
## API Usage
|
||||||
|
|
||||||
The application exposes a single endpoint at `/chat` that accepts POST requests.
|
The application exposes a single endpoint at `/chat` that accepts POST requests.
|
||||||
|
|
||||||
### Example Request:
|
### Example Request:
|
||||||
```json
|
```bash
|
||||||
{
|
curl -X POST http://localhost:3002/chat \
|
||||||
"messages": [
|
-H "Content-Type: application/json" \
|
||||||
{
|
-H "Authorization: Bearer test-api-key" \
|
||||||
"role": "user",
|
-d '{
|
||||||
"content": "Your message here"
|
"messages": [
|
||||||
}
|
{
|
||||||
],
|
"role": "user",
|
||||||
"workflow_schema": "Your workflow schema here",
|
"content": "Your message here"
|
||||||
"current_workflow_config": "Your current workflow configuration here"
|
}
|
||||||
}
|
],
|
||||||
|
"workflow_schema": "Your workflow schema here",
|
||||||
|
"current_workflow_config": "Your current workflow configuration here"
|
||||||
|
}'
|
||||||
```
|
```
|
||||||
|
|
||||||
### Example Response:
|
### Example Response:
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,8 @@ from pydantic import BaseModel, ValidationError
|
||||||
from typing import List
|
from typing import List
|
||||||
from copilot import UserMessage, AssistantMessage, get_response
|
from copilot import UserMessage, AssistantMessage, get_response
|
||||||
from lib import AgentContext, PromptContext, ToolContext, ChatContext
|
from lib import AgentContext, PromptContext, ToolContext, ChatContext
|
||||||
|
import os
|
||||||
|
from functools import wraps
|
||||||
|
|
||||||
class ApiRequest(BaseModel):
|
class ApiRequest(BaseModel):
|
||||||
messages: List[UserMessage | AssistantMessage]
|
messages: List[UserMessage | AssistantMessage]
|
||||||
|
|
@ -24,7 +26,26 @@ def validate_request(request_data: ApiRequest) -> None:
|
||||||
if not isinstance(request_data.messages[-1], UserMessage):
|
if not isinstance(request_data.messages[-1], UserMessage):
|
||||||
raise ValueError('Last message must be a user message')
|
raise ValueError('Last message must be a user message')
|
||||||
|
|
||||||
|
def require_api_key(f):
|
||||||
|
@wraps(f)
|
||||||
|
def decorated(*args, **kwargs):
|
||||||
|
auth_header = request.headers.get('Authorization')
|
||||||
|
if not auth_header or not auth_header.startswith('Bearer '):
|
||||||
|
return jsonify({'error': 'Missing or invalid authorization header'}), 401
|
||||||
|
|
||||||
|
token = auth_header.split('Bearer ')[1]
|
||||||
|
if token != os.environ.get('API_KEY'):
|
||||||
|
return jsonify({'error': 'Invalid API key'}), 403
|
||||||
|
|
||||||
|
return f(*args, **kwargs)
|
||||||
|
return decorated
|
||||||
|
|
||||||
|
@app.route('/health', methods=['GET'])
|
||||||
|
def health():
|
||||||
|
return jsonify({'status': 'ok'})
|
||||||
|
|
||||||
@app.route('/chat', methods=['POST'])
|
@app.route('/chat', methods=['POST'])
|
||||||
|
@require_api_key
|
||||||
def chat():
|
def chat():
|
||||||
try:
|
try:
|
||||||
# Log incoming request
|
# Log incoming request
|
||||||
|
|
@ -74,4 +95,4 @@ def chat():
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
print("Starting Flask server...")
|
print("Starting Flask server...")
|
||||||
app.run(port=5000, debug=True)
|
app.run(port=3002, host='0.0.0.0', debug=True)
|
||||||
|
|
@ -5,6 +5,7 @@ certifi==2024.8.30
|
||||||
click==8.1.7
|
click==8.1.7
|
||||||
distro==1.9.0
|
distro==1.9.0
|
||||||
Flask==3.1.0
|
Flask==3.1.0
|
||||||
|
gunicorn==23.0.0
|
||||||
h11==0.14.0
|
h11==0.14.0
|
||||||
httpcore==1.0.7
|
httpcore==1.0.7
|
||||||
httpx==0.28.0
|
httpx==0.28.0
|
||||||
|
|
@ -14,6 +15,8 @@ Jinja2==3.1.4
|
||||||
jiter==0.8.0
|
jiter==0.8.0
|
||||||
MarkupSafe==3.0.2
|
MarkupSafe==3.0.2
|
||||||
openai==1.57.0
|
openai==1.57.0
|
||||||
|
packaging==24.2
|
||||||
|
pydantic==2.10.3
|
||||||
pydantic_core==2.27.1
|
pydantic_core==2.27.1
|
||||||
sniffio==1.3.1
|
sniffio==1.3.1
|
||||||
tqdm==4.67.1
|
tqdm==4.67.1
|
||||||
|
|
|
||||||
|
|
@ -504,6 +504,7 @@ export async function getCopilotResponse(
|
||||||
body: JSON.stringify(request),
|
body: JSON.stringify(request),
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
|
'Authorization': `Bearer ${process.env.COPILOT_API_KEY}`,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
|
|
|
||||||
|
|
@ -162,11 +162,12 @@ export async function getAgenticApiResponse(
|
||||||
}> {
|
}> {
|
||||||
// call agentic api
|
// call agentic api
|
||||||
console.log(`agentic request`, JSON.stringify(request, null, 2));
|
console.log(`agentic request`, JSON.stringify(request, null, 2));
|
||||||
const response = await fetch(process.env.AGENTIC_API_URL + '/chat', {
|
const response = await fetch(process.env.AGENTS_API_URL + '/chat', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: JSON.stringify(request),
|
body: JSON.stringify(request),
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
|
'Authorization': `Bearer ${process.env.AGENTS_API_KEY}`,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
|
|
|
||||||
|
|
@ -14,8 +14,10 @@ services:
|
||||||
- OXYLABS_USERNAME=${OXYLABS_USERNAME}
|
- OXYLABS_USERNAME=${OXYLABS_USERNAME}
|
||||||
- OXYLABS_PASSWORD=${OXYLABS_PASSWORD}
|
- OXYLABS_PASSWORD=${OXYLABS_PASSWORD}
|
||||||
- CHAT_WIDGET_SESSION_JWT_SECRET=${CHAT_WIDGET_SESSION_JWT_SECRET}
|
- CHAT_WIDGET_SESSION_JWT_SECRET=${CHAT_WIDGET_SESSION_JWT_SECRET}
|
||||||
- AGENTIC_API_URL=http://agents:3001
|
- AGENTS_API_URL=http://agents:3001
|
||||||
|
- AGENTS_API_KEY=${AGENTS_API_KEY}
|
||||||
- COPILOT_API_URL=http://copilot:3002
|
- COPILOT_API_URL=http://copilot:3002
|
||||||
|
- COPILOT_API_KEY=${COPILOT_API_KEY}
|
||||||
- AUTH0_SECRET=${AUTH0_SECRET}
|
- AUTH0_SECRET=${AUTH0_SECRET}
|
||||||
- AUTH0_BASE_URL=${AUTH0_BASE_URL}
|
- AUTH0_BASE_URL=${AUTH0_BASE_URL}
|
||||||
- AUTH0_ISSUER_BASE_URL=${AUTH0_ISSUER_BASE_URL}
|
- AUTH0_ISSUER_BASE_URL=${AUTH0_ISSUER_BASE_URL}
|
||||||
|
|
@ -31,6 +33,7 @@ services:
|
||||||
- "3001:3001"
|
- "3001:3001"
|
||||||
environment:
|
environment:
|
||||||
- OPENAI_API_KEY=${OPENAI_API_KEY}
|
- OPENAI_API_KEY=${OPENAI_API_KEY}
|
||||||
|
- API_KEY=${AGENTS_API_KEY}
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
|
||||||
copilot:
|
copilot:
|
||||||
|
|
@ -41,6 +44,7 @@ services:
|
||||||
- "3002:3002"
|
- "3002:3002"
|
||||||
environment:
|
environment:
|
||||||
- OPENAI_API_KEY=${OPENAI_API_KEY}
|
- OPENAI_API_KEY=${OPENAI_API_KEY}
|
||||||
|
- API_KEY=${COPILOT_API_KEY}
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
|
||||||
docs:
|
docs:
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue