✅(e2e) add first chat end-to-end test
This is a very simple test, but it paves the way for more of them.
This commit is contained in:
@@ -126,7 +126,7 @@ build-frontend: ## build the frontend container
|
|||||||
build-e2e: cache ?=
|
build-e2e: cache ?=
|
||||||
build-e2e: ## build the e2e container
|
build-e2e: ## build the e2e container
|
||||||
@$(MAKE) build-backend cache=$(cache)
|
@$(MAKE) build-backend cache=$(cache)
|
||||||
@$(COMPOSE_E2E) build frontend $(cache)
|
@$(COMPOSE_E2E) build frontend openmockllm-mistral $(cache)
|
||||||
.PHONY: build-e2e
|
.PHONY: build-e2e
|
||||||
|
|
||||||
down: ## stop and remove containers, networks, images, and volumes
|
down: ## stop and remove containers, networks, images, and volumes
|
||||||
@@ -158,7 +158,7 @@ create-compose-with-models: ## override the docker-compose file with models
|
|||||||
run-e2e: ## start the e2e server
|
run-e2e: ## start the e2e server
|
||||||
run-e2e:
|
run-e2e:
|
||||||
@$(MAKE) run-backend
|
@$(MAKE) run-backend
|
||||||
@$(COMPOSE_E2E) up --force-recreate -d frontend
|
@$(COMPOSE_E2E) up --force-recreate -d frontend openmockllm-mistral
|
||||||
.PHONY: run-e2e
|
.PHONY: run-e2e
|
||||||
|
|
||||||
status: ## an alias for "docker compose ps"
|
status: ## an alias for "docker compose ps"
|
||||||
|
|||||||
@@ -11,3 +11,22 @@ services:
|
|||||||
image: conversations:frontend-production
|
image: conversations:frontend-production
|
||||||
ports:
|
ports:
|
||||||
- "3000:3000"
|
- "3000:3000"
|
||||||
|
|
||||||
|
openmockllm-mistral:
|
||||||
|
user: "${DOCKER_USER:-1000}"
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: ./src/OpenMockLLM/Dockerfile
|
||||||
|
image: conversations:openmockllm-mistral
|
||||||
|
command:
|
||||||
|
- openmockllm
|
||||||
|
- --host
|
||||||
|
- "0.0.0.0"
|
||||||
|
- --port
|
||||||
|
- "8000"
|
||||||
|
- --backend
|
||||||
|
- mistral
|
||||||
|
- --model-name
|
||||||
|
- mistral-mock
|
||||||
|
ports:
|
||||||
|
- "8900:8000"
|
||||||
|
|||||||
@@ -2,6 +2,9 @@
|
|||||||
BURST_THROTTLE_RATES="200/minute"
|
BURST_THROTTLE_RATES="200/minute"
|
||||||
SUSTAINED_THROTTLE_RATES="200/hour"
|
SUSTAINED_THROTTLE_RATES="200/hour"
|
||||||
|
|
||||||
|
# LLM
|
||||||
|
LLM_CONFIGURATION_FILE_PATH = /app/conversations/configuration/llm/default.e2e.json
|
||||||
|
|
||||||
# Features
|
# Features
|
||||||
FEATURE_FLAG_WEB_SEARCH=ENABLED
|
FEATURE_FLAG_WEB_SEARCH=ENABLED
|
||||||
FEATURE_FLAG_DOCUMENT_UPLOAD=ENABLED
|
FEATURE_FLAG_DOCUMENT_UPLOAD=ENABLED
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
FROM python:3.13.3-alpine
|
||||||
|
|
||||||
|
# Upgrade pip to its latest release to speed up dependencies installation
|
||||||
|
RUN python -m pip install --upgrade pip setuptools lorem-text
|
||||||
|
|
||||||
|
# Upgrade system packages to install security updates
|
||||||
|
RUN apk update && \
|
||||||
|
apk upgrade
|
||||||
|
|
||||||
|
RUN apk add --no-cache git
|
||||||
|
|
||||||
|
# Install the package
|
||||||
|
RUN pip install git+https://github.com/etalab-ia/openmockllm.git
|
||||||
|
|
||||||
|
# Expose the default port
|
||||||
|
EXPOSE 8000
|
||||||
|
|
||||||
|
# Set default command
|
||||||
|
CMD ["openmockllm", "--host", "0.0.0.0", "--port", "8000"]
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
[OpenMockLLM](https://github.com/etalab-ia/OpenMockLLM) is a FastAPI-based mock LLM API server that simulates
|
||||||
|
several Large Language Model API providers.
|
||||||
|
|
||||||
|
This is a simple docker image to run the server for testing and development purposes (E2E tests mainly).
|
||||||
|
|
||||||
|
It's a bit overkill to have a dedicated image for that, but it allows simple E2E stack with docker-compose since
|
||||||
|
our code is also run in Docker containers.
|
||||||
|
|
||||||
|
## Build and Run manually
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker build -t openmockllm .
|
||||||
|
docker run -p 8000:8000 openmockllm
|
||||||
|
```
|
||||||
|
|
||||||
|
## Next steps
|
||||||
|
|
||||||
|
- Add more chat completion behaviors (specific text streaming, function calling, etc.)
|
||||||
|
- Pin a specific OpenMockLLM version in the Dockerfile
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
{
|
||||||
|
"models": [
|
||||||
|
{
|
||||||
|
"hrid": "default-model",
|
||||||
|
"model_name": "mistral-mock",
|
||||||
|
"human_readable_name": "Default Model",
|
||||||
|
"provider_name": "default-provider",
|
||||||
|
"profile": null,
|
||||||
|
"settings": {},
|
||||||
|
"is_active": true,
|
||||||
|
"icon": [
|
||||||
|
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABwAAAAcCAMAAABF0y+mAAAAn1BMVEUALosAKoovTZjw8vb////+9/jlPUniAAz",
|
||||||
|
"iABUAGIWbpsTwq7HhAAAAI4dle7DrdX4AJohRaaboXWj7+/zn6On5//9NZaT29vfoWmVHYKDoUl/k5OUAIYddc6vpbHYCM47Y3+v53+LiFCUA",
|
||||||
|
"HIWnsckYPJHi6PL77O7jJjW3wdf1w8jre4QgQ5TZ2txwg7Pr3+I8WZ6OnsTuoamClL7tlZ5xz5y8AAAAzUlEQVR4AZ3RRQKDQBBEUSTu7h5c4",
|
||||||
|
"vc/W6Yp3KG2Dz4ynDdeEBvOmq12xx2E1u0B+4NOEocj4DgNJ1PgLAvni8WyBq5Yc71ubFJx23C2q4P7dRYejg1xzvCUgvz5guz11k7gXYKF/1",
|
||||||
|
"8oyiYuvHAYeVkhXCzolVStHcGDjiQzNmMQxsMI5rEJRdQSPZvbpE2E8aY6gC6Z+2Hg4dFA0Yb4YedNL/v4Fk8WJuwiGhrChJNXI210rnib9Fs",
|
||||||
|
"JlXRUC/HwTscPIXf/iklq/tjb/gHAdxkCUjAg2QAAAABJRU5ErkJggg=="
|
||||||
|
],
|
||||||
|
"system_prompt": "You are a helpful AI assistant.",
|
||||||
|
"tools": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"hrid": "default-summarization-model",
|
||||||
|
"model_name": "mistral-mock",
|
||||||
|
"human_readable_name": "Default Summarization Model",
|
||||||
|
"provider_name": "default-provider",
|
||||||
|
"profile": null,
|
||||||
|
"settings": {},
|
||||||
|
"is_active": true,
|
||||||
|
"icon": null,
|
||||||
|
"system_prompt": "You are a helpful AI assistant specialized in summarization.",
|
||||||
|
"tools": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"providers": [
|
||||||
|
{
|
||||||
|
"hrid": "default-provider",
|
||||||
|
"base_url": "http://host.docker.internal:8900",
|
||||||
|
"api_key": "openmockllm-api-key",
|
||||||
|
"kind": "mistral"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
import { expect, test } from '@playwright/test';
|
||||||
|
|
||||||
|
test.beforeEach(async ({ page }) => {
|
||||||
|
await page.goto('/home/');
|
||||||
|
});
|
||||||
|
|
||||||
|
test.describe('Chat page', () => {
|
||||||
|
test('it checks the page is displayed properly', async ({ page }) => {
|
||||||
|
await page.goto('/');
|
||||||
|
|
||||||
|
const newChatButton = page.getByRole('button', { name: 'New chat' });
|
||||||
|
await expect(newChatButton).toBeVisible();
|
||||||
|
|
||||||
|
const chatInput = page.getByRole('textbox', {
|
||||||
|
name: 'Enter your message or a',
|
||||||
|
});
|
||||||
|
await expect(chatInput).toBeVisible();
|
||||||
|
|
||||||
|
const attachmentButton = page.getByRole('button', {
|
||||||
|
name: 'Add attach file',
|
||||||
|
});
|
||||||
|
await expect(attachmentButton).toBeVisible();
|
||||||
|
|
||||||
|
const websearchButton = page.getByRole('button', {
|
||||||
|
name: 'Research on the web',
|
||||||
|
});
|
||||||
|
await expect(websearchButton).toBeVisible();
|
||||||
|
|
||||||
|
const sendMessageButton = page.getByRole('button', { name: 'Send' });
|
||||||
|
await expect(sendMessageButton).toBeVisible();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('the user can chat with LLM (simple)', async ({ page }) => {
|
||||||
|
await page.goto('/');
|
||||||
|
|
||||||
|
const newChatButton = page.getByRole('button', { name: 'New chat' });
|
||||||
|
await expect(newChatButton).toBeVisible();
|
||||||
|
|
||||||
|
const chatInput = page.getByRole('textbox', {
|
||||||
|
name: 'Enter your message or a',
|
||||||
|
});
|
||||||
|
await chatInput.click();
|
||||||
|
await chatInput.fill('Hello, how are you?');
|
||||||
|
|
||||||
|
const sendMessageButton = page.getByRole('button', { name: 'Send' });
|
||||||
|
await expect(sendMessageButton).toBeEnabled();
|
||||||
|
|
||||||
|
await page.keyboard.press('Enter');
|
||||||
|
|
||||||
|
// Wait for the response to appear
|
||||||
|
await page
|
||||||
|
.getByRole('button', { name: 'See more' })
|
||||||
|
.waitFor({ timeout: 10000 });
|
||||||
|
|
||||||
|
const copyButton = page.getByRole('button', { name: 'Copy' });
|
||||||
|
await expect(copyButton).toBeVisible();
|
||||||
|
|
||||||
|
const messageContent = page.getByText('Lorem ipsum dolor sit amet');
|
||||||
|
await expect(messageContent).toBeVisible();
|
||||||
|
|
||||||
|
// Check history
|
||||||
|
const chatHistoryLink = page
|
||||||
|
.getByRole('link', { name: 'Simple chat icon Hello, how' })
|
||||||
|
.first();
|
||||||
|
await expect(chatHistoryLink).toBeVisible();
|
||||||
|
|
||||||
|
await newChatButton.click();
|
||||||
|
|
||||||
|
await page
|
||||||
|
.getByRole('heading', { name: 'What is on your mind?' })
|
||||||
|
.isVisible();
|
||||||
|
|
||||||
|
await chatHistoryLink.click();
|
||||||
|
await expect(messageContent).toBeVisible();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -24,9 +24,7 @@ test.describe('Home page', () => {
|
|||||||
|
|
||||||
// Check the titles
|
// Check the titles
|
||||||
const h2 = page.locator('h2');
|
const h2 = page.locator('h2');
|
||||||
await expect(
|
await expect(h2.getByText('Your sovereign AI assistant')).toBeVisible();
|
||||||
h2.getByText('Your sovereign AI assistant'),
|
|
||||||
).toBeVisible();
|
|
||||||
|
|
||||||
await expect(footer).toBeVisible();
|
await expect(footer).toBeVisible();
|
||||||
});
|
});
|
||||||
@@ -74,9 +72,7 @@ test.describe('Home page', () => {
|
|||||||
|
|
||||||
// Check the titles
|
// Check the titles
|
||||||
const h2 = page.locator('h2');
|
const h2 = page.locator('h2');
|
||||||
await expect(
|
await expect(h2.getByText('Your sovereign AI assistant')).toBeVisible();
|
||||||
h2.getByText('Your sovereign AI assistant'),
|
|
||||||
).toBeVisible();
|
|
||||||
|
|
||||||
await expect(footer).toBeVisible();
|
await expect(footer).toBeVisible();
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user