From 16b89976e3eeccdc843659435cae4dd7233c9e50 Mon Sep 17 00:00:00 2001 From: Brian Newsom Date: Thu, 28 May 2026 09:50:20 -0600 Subject: [PATCH 1/4] chore: remove unused intake surface area Signed-off-by: Brian Newsom --- openapi/ga/individual/platform.openapi.yaml | 2874 +------------- openapi/ga/openapi.yaml | 2874 +------------- openapi/openapi.yaml | 2874 +------------- sdk/stainless.yaml | 89 - services/intake/README.md | 395 +- services/intake/alembic.ini | 120 - services/intake/alembic/README | 1 - services/intake/alembic/env.py | 98 - services/intake/alembic/script.py.mako | 26 - ..._14_1101-a7e3b1e7355b_initial_migration.py | 124 - services/intake/pyproject.toml | 28 +- services/intake/scripts/README.md | 67 - services/intake/scripts/__init__.py | 9 - .../intake/scripts/generate_test_dataset.py | 696 ---- services/intake/scripts/init-multiple-dbs.sh | 6 - services/intake/scripts/load_test_data.py | 419 --- services/intake/scripts/seed-entries.py | 963 ----- .../src/nmp/intake/api/v2/apps/__init__.py | 4 - .../src/nmp/intake/api/v2/apps/endpoints.py | 174 - .../src/nmp/intake/api/v2/apps/schemas.py | 81 - .../src/nmp/intake/api/v2/entries/__init__.py | 4 - .../nmp/intake/api/v2/entries/endpoints.py | 512 --- .../src/nmp/intake/api/v2/entries/schemas.py | 181 - .../src/nmp/intake/api/v2/exports/__init__.py | 4 - .../nmp/intake/api/v2/exports/endpoints.py | 243 -- .../src/nmp/intake/api/v2/exports/schemas.py | 102 - .../src/nmp/intake/api/v2/health/endpoints.py | 19 +- .../src/nmp/intake/api/v2/tasks/__init__.py | 4 - .../src/nmp/intake/api/v2/tasks/endpoints.py | 190 - .../src/nmp/intake/api/v2/tasks/schemas.py | 85 - .../intake/src/nmp/intake/app/__init__.py | 4 - .../intake/src/nmp/intake/app/exporter.py | 285 -- .../src/nmp/intake/app/utils/__init__.py | 4 - .../src/nmp/intake/app/utils/datastore.py | 117 - .../src/nmp/intake/app/utils/exports.py | 113 - services/intake/src/nmp/intake/config.py | 9 - .../src/nmp/intake/entities/__init__.py | 74 - .../src/nmp/intake/entities/entities.py | 153 - .../intake/src/nmp/intake/entities/enums.py | 106 - .../intake/src/nmp/intake/entities/values.py | 614 --- services/intake/src/nmp/intake/service.py | 10 +- .../intake/spans/ingest/chat_completions.py | 72 +- services/intake/tests/data/action_heavy.json | 695 ---- services/intake/tests/data/all_entries.json | 3303 ----------------- services/intake/tests/data/conversations.json | 530 --- .../intake/tests/data/feedback_heavy.json | 1122 ------ services/intake/tests/data/single_turns.json | 962 ----- .../intake/tests/integration/test_intake.py | 485 +-- services/intake/tests/test_apps.py | 107 - services/intake/tests/test_entries.py | 851 ----- services/intake/tests/test_export_utils.py | 21 - services/intake/tests/test_exports.py | 294 -- services/intake/tests/test_sdk_basic.py | 93 - services/intake/tests/test_tasks.py | 95 - services/intake/tests/test_values.py | 52 - uv.lock | 160 - 56 files changed, 694 insertions(+), 22903 deletions(-) delete mode 100644 services/intake/alembic.ini delete mode 100644 services/intake/alembic/README delete mode 100644 services/intake/alembic/env.py delete mode 100644 services/intake/alembic/script.py.mako delete mode 100644 services/intake/alembic/versions/2025_10_14_1101-a7e3b1e7355b_initial_migration.py delete mode 100644 services/intake/scripts/README.md delete mode 100644 services/intake/scripts/__init__.py delete mode 100644 services/intake/scripts/generate_test_dataset.py delete mode 100755 services/intake/scripts/init-multiple-dbs.sh delete mode 100644 services/intake/scripts/load_test_data.py delete mode 100644 services/intake/scripts/seed-entries.py delete mode 100644 services/intake/src/nmp/intake/api/v2/apps/__init__.py delete mode 100644 services/intake/src/nmp/intake/api/v2/apps/endpoints.py delete mode 100644 services/intake/src/nmp/intake/api/v2/apps/schemas.py delete mode 100644 services/intake/src/nmp/intake/api/v2/entries/__init__.py delete mode 100644 services/intake/src/nmp/intake/api/v2/entries/endpoints.py delete mode 100644 services/intake/src/nmp/intake/api/v2/entries/schemas.py delete mode 100644 services/intake/src/nmp/intake/api/v2/exports/__init__.py delete mode 100644 services/intake/src/nmp/intake/api/v2/exports/endpoints.py delete mode 100644 services/intake/src/nmp/intake/api/v2/exports/schemas.py delete mode 100644 services/intake/src/nmp/intake/api/v2/tasks/__init__.py delete mode 100644 services/intake/src/nmp/intake/api/v2/tasks/endpoints.py delete mode 100644 services/intake/src/nmp/intake/api/v2/tasks/schemas.py delete mode 100644 services/intake/src/nmp/intake/app/__init__.py delete mode 100644 services/intake/src/nmp/intake/app/exporter.py delete mode 100644 services/intake/src/nmp/intake/app/utils/__init__.py delete mode 100644 services/intake/src/nmp/intake/app/utils/datastore.py delete mode 100644 services/intake/src/nmp/intake/app/utils/exports.py delete mode 100644 services/intake/src/nmp/intake/entities/__init__.py delete mode 100644 services/intake/src/nmp/intake/entities/entities.py delete mode 100644 services/intake/src/nmp/intake/entities/enums.py delete mode 100644 services/intake/src/nmp/intake/entities/values.py delete mode 100644 services/intake/tests/data/action_heavy.json delete mode 100644 services/intake/tests/data/all_entries.json delete mode 100644 services/intake/tests/data/conversations.json delete mode 100644 services/intake/tests/data/feedback_heavy.json delete mode 100644 services/intake/tests/data/single_turns.json delete mode 100644 services/intake/tests/test_apps.py delete mode 100644 services/intake/tests/test_entries.py delete mode 100644 services/intake/tests/test_export_utils.py delete mode 100644 services/intake/tests/test_exports.py delete mode 100644 services/intake/tests/test_sdk_basic.py delete mode 100644 services/intake/tests/test_tasks.py delete mode 100644 services/intake/tests/test_values.py diff --git a/openapi/ga/individual/platform.openapi.yaml b/openapi/ga/individual/platform.openapi.yaml index 874f04cbd3..f3c6662bf1 100644 --- a/openapi/ga/individual/platform.openapi.yaml +++ b/openapi/ga/individual/platform.openapi.yaml @@ -5755,834 +5755,12 @@ paths: application/json: schema: $ref: '#/components/schemas/HTTPValidationError' - /apis/intake/v2/workspaces/{workspace}/apps: - get: - tags: - - Apps - summary: List Apps - description: List all apps with filtering capabilities. - operationId: list_apps_apis_intake_v2_workspaces__workspace__apps_get - parameters: - - name: workspace - in: path - required: true - schema: - type: string - title: Workspace - - name: page - in: query - required: false - schema: - type: integer - description: Page number. - default: 1 - title: Page - description: Page number. - - name: page_size - in: query - required: false - schema: - type: integer - description: Page size. - default: 10 - title: Page Size - description: Page size. - - name: sort - in: query - required: false - schema: - allOf: - - $ref: '#/components/schemas/AppSortField' - description: The field to sort by. To sort in decreasing order, use `-` - in front of the field name. - default: created_at - description: The field to sort by. To sort in decreasing order, use `-` in - front of the field name. - - in: query - name: filter - style: deepObject - required: false - explode: true - schema: - $ref: '#/components/schemas/AppFilter' - description: Filter apps by name, description, project, created_at, and updated_at. - responses: - '200': - description: Successful Response - content: - application/json: - schema: - $ref: '#/components/schemas/AppsPage' - '422': - description: Validation Error - content: - application/json: - schema: - $ref: '#/components/schemas/HTTPValidationError' - post: - tags: - - Apps - summary: Create App - description: Create a new app. - operationId: create_app_apis_intake_v2_workspaces__workspace__apps_post - parameters: - - name: workspace - in: path - required: true - schema: - type: string - title: Workspace - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/AppInput' - responses: - '201': - description: Successful Response - content: - application/json: - schema: - $ref: '#/components/schemas/App' - '200': - description: Successful Response - '409': - description: App already exists - '422': - description: Validation Error - /apis/intake/v2/workspaces/{workspace}/apps/{app}/tasks/{name}: - get: - tags: - - Tasks - summary: Get Task - description: Get a specific task. - operationId: get_task_apis_intake_v2_workspaces__workspace__apps__app__tasks__name__get - parameters: - - name: workspace - in: path - required: true - schema: - type: string - title: Workspace - - name: app - in: path - required: true - schema: - type: string - title: App - - name: name - in: path - required: true - schema: - type: string - title: Name - responses: - '200': - description: Successful Response - content: - application/json: - schema: - $ref: '#/components/schemas/Task' - '422': - description: Validation Error - content: - application/json: - schema: - $ref: '#/components/schemas/HTTPValidationError' - patch: - tags: - - Tasks - summary: Update Task - description: Update an existing task. - operationId: update_task_apis_intake_v2_workspaces__workspace__apps__app__tasks__name__patch - parameters: - - name: workspace - in: path - required: true - schema: - type: string - title: Workspace - - name: app - in: path - required: true - schema: - type: string - title: App - - name: name - in: path - required: true - schema: - type: string - title: Name - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/TaskUpdate' - responses: - '200': - description: Successful Response - content: - application/json: - schema: - $ref: '#/components/schemas/Task' - '422': - description: Validation Error - content: - application/json: - schema: - $ref: '#/components/schemas/HTTPValidationError' - delete: - tags: - - Tasks - summary: Delete Task - description: Delete a task. - operationId: delete_task_apis_intake_v2_workspaces__workspace__apps__app__tasks__name__delete - parameters: - - name: workspace - in: path - required: true - schema: - type: string - title: Workspace - - name: app - in: path - required: true - schema: - type: string - title: App - - name: name - in: path - required: true - schema: - type: string - title: Name - responses: - '204': - description: Successful Response - '422': - description: Validation Error - content: - application/json: - schema: - $ref: '#/components/schemas/HTTPValidationError' - /apis/intake/v2/workspaces/{workspace}/apps/{name}: - get: - tags: - - Apps - summary: Get App - description: Get a specific app by workspace and name. - operationId: get_app_apis_intake_v2_workspaces__workspace__apps__name__get - parameters: - - name: workspace - in: path - required: true - schema: - type: string - title: Workspace - - name: name - in: path - required: true - schema: - type: string - title: Name - responses: - '200': - description: Successful Response - content: - application/json: - schema: - $ref: '#/components/schemas/App' - '422': - description: Validation Error - content: - application/json: - schema: - $ref: '#/components/schemas/HTTPValidationError' - patch: - tags: - - Apps - summary: Update App - description: Update an existing app. - operationId: update_app_apis_intake_v2_workspaces__workspace__apps__name__patch - parameters: - - name: workspace - in: path - required: true - schema: - type: string - title: Workspace - - name: name - in: path - required: true - schema: - type: string - title: Name - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/AppUpdate' - responses: - '200': - description: Successful Response - content: - application/json: - schema: - $ref: '#/components/schemas/App' - '422': - description: Validation Error - content: - application/json: - schema: - $ref: '#/components/schemas/HTTPValidationError' - delete: - tags: - - Apps - summary: Delete App - description: Delete an app. - operationId: delete_app_apis_intake_v2_workspaces__workspace__apps__name__delete - parameters: - - name: workspace - in: path - required: true - schema: - type: string - title: Workspace - - name: name - in: path - required: true - schema: - type: string - title: Name - responses: - '204': - description: Successful Response - '422': - description: Validation Error - content: - application/json: - schema: - $ref: '#/components/schemas/HTTPValidationError' - /apis/intake/v2/workspaces/{workspace}/apps/{name}/tasks: - get: - tags: - - Tasks - summary: List Tasks - description: List all tasks for a specific app. - operationId: list_tasks_apis_intake_v2_workspaces__workspace__apps__name__tasks_get - parameters: - - name: workspace - in: path - required: true - schema: - type: string - title: Workspace - - name: name - in: path - required: true - schema: - type: string - title: Name - - name: page - in: query - required: false - schema: - type: integer - description: Page number. - default: 1 - title: Page - description: Page number. - - name: page_size - in: query - required: false - schema: - type: integer - description: Page size. - default: 10 - title: Page Size - description: Page size. - - name: sort - in: query - required: false - schema: - allOf: - - $ref: '#/components/schemas/TaskSortField' - description: The field to sort by. To sort in decreasing order, use `-` - in front of the field name. - default: created_at - description: The field to sort by. To sort in decreasing order, use `-` in - front of the field name. - - in: query - name: filter - style: deepObject - required: false - explode: true - schema: - $ref: '#/components/schemas/TaskFilter' - description: Filter tasks by name, app, description, project, created_at, - and updated_at. - responses: - '200': - description: Successful Response - content: - application/json: - schema: - $ref: '#/components/schemas/TasksPage' - '422': - description: Validation Error - content: - application/json: - schema: - $ref: '#/components/schemas/HTTPValidationError' - post: - tags: - - Tasks - summary: Create Task - description: Create a new task. - operationId: create_task_apis_intake_v2_workspaces__workspace__apps__name__tasks_post - parameters: - - name: workspace - in: path - required: true - schema: - type: string - title: Workspace - - name: name - in: path - required: true - schema: - type: string - title: Name - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/TaskInput' - responses: - '201': - description: Successful Response - content: - application/json: - schema: - $ref: '#/components/schemas/Task' - '422': - description: Validation Error - content: - application/json: - schema: - $ref: '#/components/schemas/HTTPValidationError' - /apis/intake/v2/workspaces/{workspace}/entries: - get: - tags: - - Entries - summary: List Entries - description: 'List all entries with filtering capabilities. - - - When longest_per_thread=true is set in filters, returns only the longest entry - - (by message count) for each unique thread_id.' - operationId: list_entries_apis_intake_v2_workspaces__workspace__entries_get - parameters: - - name: workspace - in: path - required: true - schema: - type: string - title: Workspace - - name: page - in: query - required: false - schema: - type: integer - description: Page number. - default: 1 - title: Page - description: Page number. - - name: page_size - in: query - required: false - schema: - type: integer - description: Page size. - default: 10 - title: Page Size - description: Page size. - - name: sort - in: query - required: false - schema: - allOf: - - $ref: '#/components/schemas/EntrySortField' - description: The field to sort by. To sort in decreasing order, use `-` - in front of the field name. - default: created_at - description: The field to sort by. To sort in decreasing order, use `-` in - front of the field name. - - in: query - name: filter - style: deepObject - required: false - explode: true - schema: - $ref: '#/components/schemas/EntryFilter' - description: Filter entries by id, project, external_id, created_at, updated_at, - usage fields (model), context fields, and user_rating fields. - responses: - '200': - description: Successful Response - content: - application/json: - schema: - $ref: '#/components/schemas/EntrysPage' - '422': - description: Validation Error - content: - application/json: - schema: - $ref: '#/components/schemas/HTTPValidationError' - post: - tags: - - Entries - summary: Create Entry - description: 'Create a new entry. - - - Apps and tasks referenced in the entry context will be auto-created if they - don''t exist.' - operationId: create_entry_apis_intake_v2_workspaces__workspace__entries_post - parameters: - - name: workspace - in: path - required: true - schema: - type: string - title: Workspace - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/EntryInput' - responses: - '201': - description: Successful Response - content: - application/json: - schema: - $ref: '#/components/schemas/Entry' - '422': - description: Validation Error - content: - application/json: - schema: - $ref: '#/components/schemas/HTTPValidationError' - /apis/intake/v2/workspaces/{workspace}/entries/{entry}/events/{name}: - delete: - tags: - - Entries - summary: Delete Event - description: 'Delete a specific event from an entry. - - - Entry can be referenced by ID or external_id using `external:{external_id}` - prefix.' - operationId: delete_event_apis_intake_v2_workspaces__workspace__entries__entry__events__name__delete - parameters: - - name: workspace - in: path - required: true - schema: - type: string - title: Workspace - - name: entry - in: path - required: true - schema: - type: string - title: Entry - - name: name - in: path - required: true - schema: - type: string - title: Name - responses: - '200': - description: Successful Response - content: - application/json: - schema: - $ref: '#/components/schemas/Entry' - '422': - description: Validation Error - content: - application/json: - schema: - $ref: '#/components/schemas/HTTPValidationError' - /apis/intake/v2/workspaces/{workspace}/entries/{name}: - get: - tags: - - Entries - summary: Get Entry - description: 'Get a specific entry by ID or external_id. - - - Use `external:{external_id}` to get by external_id. - - Example: `/v2/workspaces/{workspace}/entries/external:chatcmpl-abc123`' - operationId: get_entry_apis_intake_v2_workspaces__workspace__entries__name__get - parameters: - - name: workspace - in: path - required: true - schema: - type: string - title: Workspace - - name: name - in: path - required: true - schema: - type: string - title: Name - responses: - '200': - description: Successful Response - content: - application/json: - schema: - $ref: '#/components/schemas/Entry' - '422': - description: Validation Error - content: - application/json: - schema: - $ref: '#/components/schemas/HTTPValidationError' - patch: - tags: - - Entries - summary: Update Entry - description: 'Update an existing entry by ID or external_id. - - - Use `external:{external_id}` to update by external_id. - - Example: `/v2/workspaces/{workspace}/entries/external:chatcmpl-abc123`' - operationId: update_entry_apis_intake_v2_workspaces__workspace__entries__name__patch - parameters: - - name: workspace - in: path - required: true - schema: - type: string - title: Workspace - - name: name - in: path - required: true - schema: - type: string - title: Name - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/EntryUpdate' - responses: - '200': - description: Successful Response - content: - application/json: - schema: - $ref: '#/components/schemas/Entry' - '422': - description: Validation Error - content: - application/json: - schema: - $ref: '#/components/schemas/HTTPValidationError' - delete: - tags: - - Entries - summary: Delete Entry - description: 'Delete an entry by ID or external_id. - - - Use `external:{external_id}` to delete by external_id. - - Example: `/v2/workspaces/{workspace}/entries/external:chatcmpl-abc123`' - operationId: delete_entry_apis_intake_v2_workspaces__workspace__entries__name__delete - parameters: - - name: workspace - in: path - required: true - schema: - type: string - title: Workspace - - name: name - in: path - required: true - schema: - type: string - title: Name - responses: - '204': - description: Successful Response - '422': - description: Validation Error - content: - application/json: - schema: - $ref: '#/components/schemas/HTTPValidationError' - /apis/intake/v2/workspaces/{workspace}/entries/{name}/events: - post: - tags: - - Entries - summary: Add Events - description: 'Add events to an entry by ID or external_id. - - - Use `external:{external_id}` to add events by external_id. - - Example: `/v2/workspaces/{workspace}/entries/external:chatcmpl-abc123/events`' - operationId: add_events_apis_intake_v2_workspaces__workspace__entries__name__events_post - parameters: - - name: workspace - in: path - required: true - schema: - type: string - title: Workspace - - name: name - in: path - required: true - schema: - type: string - title: Name - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/EventsCreateRequest' - responses: - '200': - description: Successful Response - content: - application/json: - schema: - $ref: '#/components/schemas/Entry' - '422': - description: Validation Error - content: - application/json: - schema: - $ref: '#/components/schemas/HTTPValidationError' - /apis/intake/v2/workspaces/{workspace}/evaluator-results: - post: - tags: - - Evaluator Results - summary: Create Evaluator Result - operationId: create_evaluator_result_apis_intake_v2_workspaces__workspace__evaluator_results_post - parameters: - - name: workspace - in: path - required: true - schema: - type: string - title: Workspace - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/EvaluatorResultInput' - responses: - '201': - description: Successful Response - content: - application/json: - schema: - $ref: '#/components/schemas/EvaluatorResult' - '422': - description: Validation Error - content: - application/json: - schema: - $ref: '#/components/schemas/HTTPValidationError' - get: - tags: - - Evaluator Results - summary: List Evaluator Results - operationId: list_evaluator_results_apis_intake_v2_workspaces__workspace__evaluator_results_get - parameters: - - name: workspace - in: path - required: true - schema: - type: string - title: Workspace - - name: page - in: query - required: false - schema: - type: integer - minimum: 1 - description: Page number. - default: 1 - title: Page - description: Page number. - - name: page_size - in: query - required: false - schema: - type: integer - maximum: 1000 - minimum: 1 - description: Page size. - default: 10 - title: Page Size - description: Page size. - - name: sort - in: query - required: false - schema: - allOf: - - $ref: '#/components/schemas/EvaluatorResultSortField' - default: -created_at - - in: query - name: filter - style: deepObject - required: false - explode: true - schema: - $ref: '#/components/schemas/EvaluatorResultFilter' - description: Filter evaluator results by span_id, session_id, name, data_type, - created_by, value range, and created_at range. - responses: - '200': - description: Successful Response - content: - application/json: - schema: - $ref: '#/components/schemas/EvaluatorResultsPage' - '422': - description: Validation Error - content: - application/json: - schema: - $ref: '#/components/schemas/HTTPValidationError' - /apis/intake/v2/workspaces/{workspace}/evaluator-results/{evaluator_result_id}: - get: + /apis/intake/v2/workspaces/{workspace}/evaluator-results: + post: tags: - Evaluator Results - summary: Get Evaluator Result - operationId: get_evaluator_result_apis_intake_v2_workspaces__workspace__evaluator_results__evaluator_result_id__get + summary: Create Evaluator Result + operationId: create_evaluator_result_apis_intake_v2_workspaces__workspace__evaluator_results_post parameters: - name: workspace in: path @@ -6590,14 +5768,14 @@ paths: schema: type: string title: Workspace - - name: evaluator_result_id - in: path + requestBody: required: true - schema: - type: string - title: Evaluator Result Id + content: + application/json: + schema: + $ref: '#/components/schemas/EvaluatorResultInput' responses: - '200': + '201': description: Successful Response content: application/json: @@ -6609,16 +5787,11 @@ paths: application/json: schema: $ref: '#/components/schemas/HTTPValidationError' - /apis/intake/v2/workspaces/{workspace}/export/jobs: get: tags: - - Exports - summary: List Export Jobs - description: 'List all export jobs with filtering capabilities. - - - Use `workspace=-` for cross-workspace listing.' - operationId: list_export_jobs_apis_intake_v2_workspaces__workspace__export_jobs_get + - Evaluator Results + summary: List Evaluator Results + operationId: list_evaluator_results_apis_intake_v2_workspaces__workspace__evaluator_results_get parameters: - name: workspace in: path @@ -6631,6 +5804,7 @@ paths: required: false schema: type: integer + minimum: 1 description: Page number. default: 1 title: Page @@ -6640,6 +5814,8 @@ paths: required: false schema: type: integer + maximum: 1000 + minimum: 1 description: Page size. default: 10 title: Page Size @@ -6649,88 +5825,36 @@ paths: required: false schema: allOf: - - $ref: '#/components/schemas/ExportJobSortField' - description: The field to sort by. To sort in decreasing order, use `-` - in front of the field name. - default: created_at - description: The field to sort by. To sort in decreasing order, use `-` in - front of the field name. + - $ref: '#/components/schemas/EvaluatorResultSortField' + default: -created_at - in: query name: filter style: deepObject required: false explode: true schema: - $ref: '#/components/schemas/ExportJobFilter' - description: Filter export jobs by name, status, output_file_url, created_at, - and updated_at. - responses: - '200': - description: Successful Response - content: - application/json: - schema: - $ref: '#/components/schemas/ExportJobsPage' - '422': - description: Validation Error - content: - application/json: - schema: - $ref: '#/components/schemas/HTTPValidationError' - post: - tags: - - Exports - summary: Create Export Job - description: 'Export entries to an external file. - - - Use the `longest_per_thread` filter to export only the longest entry per thread, - - which is useful for thread-based exports. - - - Supported output file URLs: - - - - NeMo Datastore: nds://workspace/dataset_name - - - HuggingFace Dataset: hf://datasets/org/name/path/to/file - - - Local filesystem: file:///path/to/export (for development)' - operationId: create_export_job_apis_intake_v2_workspaces__workspace__export_jobs_post - parameters: - - name: workspace - in: path - required: true - schema: - type: string - title: Workspace - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/ExportJobInput' + $ref: '#/components/schemas/EvaluatorResultFilter' + description: Filter evaluator results by span_id, session_id, name, data_type, + created_by, value range, and created_at range. responses: '200': description: Successful Response content: application/json: schema: - $ref: '#/components/schemas/ExportJob' + $ref: '#/components/schemas/EvaluatorResultsPage' '422': description: Validation Error content: application/json: schema: $ref: '#/components/schemas/HTTPValidationError' - /apis/intake/v2/workspaces/{workspace}/export/jobs/{name}: + /apis/intake/v2/workspaces/{workspace}/evaluator-results/{evaluator_result_id}: get: tags: - - Exports - summary: Get Export Job Status - description: Check the status of an export job. - operationId: get_export_job_status_apis_intake_v2_workspaces__workspace__export_jobs__name__get + - Evaluator Results + summary: Get Evaluator Result + operationId: get_evaluator_result_apis_intake_v2_workspaces__workspace__evaluator_results__evaluator_result_id__get parameters: - name: workspace in: path @@ -6738,52 +5862,19 @@ paths: schema: type: string title: Workspace - - name: name - in: path - required: true - schema: - type: string - title: Name - responses: - '200': - description: Successful Response - content: - application/json: - schema: - $ref: '#/components/schemas/ExportJob' - '422': - description: Validation Error - content: - application/json: - schema: - $ref: '#/components/schemas/HTTPValidationError' - /apis/intake/v2/workspaces/{workspace}/export/preview: - post: - tags: - - Exports - summary: Preview Export - description: Preview export data without writing to a file (max 100 records). - operationId: preview_export_apis_intake_v2_workspaces__workspace__export_preview_post - parameters: - - name: workspace + - name: evaluator_result_id in: path required: true schema: type: string - title: Workspace - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/ExportPreviewRequest' + title: Evaluator Result Id responses: '200': description: Successful Response content: application/json: schema: - $ref: '#/components/schemas/ExportPreviewResponse' + $ref: '#/components/schemas/EvaluatorResult' '422': description: Validation Error content: @@ -11521,157 +10612,6 @@ components: - judge_model title: AnswerAccuracyMetricResponse description: Response type for AnswerAccuracy metrics. - App: - properties: - id: - type: string - title: Id - description: Unique identifier - name: - type: string - title: Name - description: App name - workspace: - type: string - title: Workspace - description: Workspace identifier - description: - title: Description - description: App description - type: string - project: - title: Project - description: The name of the project associated with this app - type: string - locked: - type: boolean - title: Locked - description: Lock status - default: false - created_at: - title: Created At - description: Creation timestamp - type: string - format: date-time - updated_at: - title: Updated At - description: Last update timestamp - type: string - format: date-time - type: object - required: - - id - - name - - workspace - title: App - description: Schema for App responses. - AppFilter: - additionalProperties: false - description: Filter for Apps. - properties: - workspace: - description: Filter by workspace id. - title: Workspace - type: string - name: - description: Filter by app name. - title: Name - type: string - project: - description: Filter by project name. - title: Project - type: string - description: - description: Filter by app description. - title: Description - type: string - created_at: - allOf: - - $ref: '#/components/schemas/DatetimeFilter' - description: Filter entities based on creation date. - updated_at: - allOf: - - $ref: '#/components/schemas/DatetimeFilter' - description: Filter entities based on update date. - title: AppFilter - type: object - AppInput: - properties: - name: - type: string - title: Name - description: App name (unique within workspace) - description: - title: Description - description: App description - type: string - project: - title: Project - description: The name of the project associated with this app - type: string - locked: - type: boolean - title: Locked - description: If true, this record cannot be automatically updated when entries - are ingested. - default: false - type: object - required: - - name - title: AppInput - description: Schema for creating a new App. - AppSortField: - type: string - enum: - - created_at - - -created_at - - name - - -name - - updated_at - - -updated_at - title: AppSortField - description: Sort fields for Apps. - AppUpdate: - properties: - description: - title: Description - description: App description - type: string - project: - title: Project - description: The name of the project associated with this app - type: string - locked: - title: Locked - description: Lock status - type: boolean - type: object - title: AppUpdate - description: Schema for updating an existing App. - AppsPage: - properties: - data: - items: - $ref: '#/components/schemas/App' - type: array - title: Data - pagination: - allOf: - - $ref: '#/components/schemas/PaginationData' - description: Pagination information. - sort: - title: Sort - description: The field on which the results are sorted. - type: string - filter: - title: Filter - description: Filtering information. - additionalProperties: true - type: object - type: object - required: - - data - title: AppsPage AtifAgent: properties: name: @@ -13140,6 +12080,59 @@ components: - not_cacheable title: CacheStatus description: Cache status for files in external storage backends. + CapturedChatCompletionsRequest: + properties: + messages: + items: + $ref: '#/components/schemas/CapturedChatMessage' + type: array + title: Messages + description: Messages comprising the conversation. + model: + type: string + title: Model + description: The model identifier used for this request. + additionalProperties: true + type: object + required: + - messages + - model + title: CapturedChatCompletionsRequest + description: Flexible captured chat-completions request. + CapturedChatCompletionsResponse: + oneOf: + - required: + - choices + - required: + - error + properties: + choices: + title: Choices + items: + additionalProperties: true + type: object + type: array + error: + title: Error + additionalProperties: true + type: object + additionalProperties: true + type: object + title: CapturedChatCompletionsResponse + description: Flexible captured chat-completions response. + CapturedChatMessage: + properties: + role: + allOf: + - $ref: '#/components/schemas/ChatMessageRole' + description: The role of the message sender. + additionalProperties: true + type: object + required: + - role + title: CapturedChatMessage + description: A flexible message model that requires a valid role field but allows + provider-specific fields. ChatCompletionAssistantMessageParam: properties: role: @@ -13329,9 +12322,9 @@ components: ChatCompletionsIngestRequest: properties: request: - $ref: '#/components/schemas/FlexibleEntryRequestInput' + $ref: '#/components/schemas/CapturedChatCompletionsRequest' response: - $ref: '#/components/schemas/FlexibleEntryResponse' + $ref: '#/components/schemas/CapturedChatCompletionsResponse' session_id: title: Session Id description: Groups related chat-completions calls without forcing them @@ -13367,6 +12360,17 @@ components: - session_id - span_id title: ChatCompletionsIngestResponse + ChatMessageRole: + type: string + enum: + - user + - system + - assistant + - developer + - tool + - function + title: ChatMessageRole + description: Valid role values for captured chat-completions messages. ClassifyConfig: properties: enable_classify: @@ -15678,460 +14682,44 @@ components: type: object required: - data - title: EntityCreateInput - description: 'Schema for creating a new entity (name-based routes). - - - Name is optional - if not provided, it will be auto-generated. - - Workspace and entity_type come from the URL path parameters.' - EntityUpdate: - properties: - new_name: - title: New Name - description: Updated entity name (optional). Name must start with a lowercase - letter, be 2-63 characters, and contain only lowercase letters, digits, - and hyphens (no consecutive hyphens, cannot end with a hyphen). - examples: - - my-config - - baseline-model-v1 - type: string - pattern: ^[a-z](?!.*--)[a-z0-9\-@.+_]{1,62}(?`` entity-store paths.' - UserActionEvent: - properties: - id: - title: Id - description: Unique identifier for the event. Populated when retrieved from - database. - type: string - created_at: - type: string - format: date-time - title: Created At - description: UTC timestamp when the record was created. - created_by: - title: Created By - description: Identifier of the user or system that generated the record. - Can be set of key-value pairs. - additionalProperties: - type: string - type: object - event_type: - type: string - const: user_action - title: Event Type - default: user_action - action: - type: string - maxLength: 256 - title: Action - description: "Descriptive name for the action taken by the user (e.g., 'share_clicked',\ - \ 'code_copied', 'link_followed'). Use snake-case or kebab-case. This\ - \ is a label, not a unique identifier\u2014multiple events can have the\ - \ same action name." - metadata: - additionalProperties: - anyOf: - - type: string - maxLength: 256 - - items: - type: string - maxLength: 256 - type: array - - type: boolean - - type: integer - - type: number - propertyNames: - maxLength: 256 - type: object - maxProperties: 16 - title: Metadata - description: 'Optional key-value pairs with additional context about the - action (max 16 entries). Use this for details like user IDs, item IDs, - timestamps, A/B test variants, or any other information useful for downstream - training or evaluation pipelines. Example: {''user_id'': ''12345'', ''experiment'': - ''variant_b'', ''item_purchased'': ''product_456''}.' - additionalProperties: false - type: object - required: - - action - title: UserActionEvent - description: "Free-form user action captured by the client application.\n\n\ - Use this to track arbitrary user interactions with AI responses, such as copying\ - \ code,\nclicking share buttons, making purchases, or any other measurable\ - \ action.\n\nThe action identifier must be \u2264256 characters and should\ - \ use snake-case or kebab-case\nfor consistency (e.g., ``share_clicked``,\ - \ ``code_copied``, ``purchase_made``)." - UserFeedbackEvent: - properties: - id: - title: Id - description: Unique identifier for the event. Populated when retrieved from - database. - type: string - created_at: - type: string - format: date-time - title: Created At - description: UTC timestamp when the record was created. - created_by: - title: Created By - description: Identifier of the user or system that generated the record. - Can be set of key-value pairs. - additionalProperties: - type: string - type: object - event_type: - type: string - const: user_feedback - title: Event Type - default: user_feedback - thumb: - allOf: - - $ref: '#/components/schemas/ThumbDirection' - description: "Binary feedback: \"up\" for \U0001F44D or \"down\" for \U0001F44E\ - . Mutually exclusive with `rating`. Use this for simple thumbs up/down\ - \ UI elements." - rating: - title: Rating - description: Numeric rating (e.g., 1-5 stars) provided by the end user. - Mutually exclusive with `thumb`. Use this for star ratings or numeric - scales. - type: number - minimum: 0.0 - opinion: - title: Opinion - description: Free-text comment from the end user describing their opinion - of the response. - type: string - maxLength: 2000 - minLength: 1 - rewrite: - title: Rewrite - description: End-user's suggested text replacement for the generated response. - This is the user's idea of what the response should have been. - type: string - maxLength: 10000 - minLength: 1 - chosen_index: - title: Chosen Index - description: Zero-based index of the response option the user selected when - multiple responses were returned. Use this when showing users multiple - completion choices and tracking which one they picked. - type: integer - minimum: 0.0 - categories: - title: Categories - description: 'Application-specific category ratings as key-value pairs. - Use this for custom rating dimensions (e.g., {''helpfulness'': 4, ''accuracy'': - 5, ''tone'': ''professional''}). Useful for radio buttons, dropdowns, - or multi-dimensional rating systems.' - additionalProperties: - anyOf: - - type: number - - type: string - type: object - additionalProperties: false - type: object - title: UserFeedbackEvent - description: 'Structured feedback supplied by an end-user. - - - This event captures various forms of end-user feedback about a model''s response, - - including binary thumbs up/down ratings, numeric scores, free-text opinions, - - suggested rewrites, and structured category ratings. - - - Either `thumb` or `rating` should be provided (they are mutually exclusive), - but all - - feedback fields are optional to accommodate different feedback collection - patterns.' UserMessagesConfig: properties: embeddings_only: @@ -31545,71 +29100,6 @@ components: type: object title: UserMessagesConfig description: Configuration for how the user messages are interpreted. - UserRating: - properties: - thumb: - allOf: - - $ref: '#/components/schemas/ThumbDirection' - description: "Binary feedback: \"up\" for \U0001F44D or \"down\" for \U0001F44E\ - . Mutually exclusive with `rating`. Use this for simple thumbs up/down\ - \ UI elements." - rating: - title: Rating - description: Numeric rating (e.g., 1-5 stars) provided by the end user. - Mutually exclusive with `thumb`. Use this for star ratings or numeric - scales. - type: number - minimum: 0.0 - opinion: - title: Opinion - description: Free-text comment from the end user describing their opinion - of the response. - type: string - maxLength: 2000 - minLength: 1 - rewrite: - title: Rewrite - description: End-user's suggested text replacement for the generated response. - This is the user's idea of what the response should have been. - type: string - maxLength: 10000 - minLength: 1 - chosen_index: - title: Chosen Index - description: Zero-based index of the response option the user selected when - multiple responses were returned. Use this when showing users multiple - completion choices and tracking which one they picked. - type: integer - minimum: 0.0 - categories: - title: Categories - description: 'Application-specific category ratings as key-value pairs. - Use this for custom rating dimensions (e.g., {''helpfulness'': 4, ''accuracy'': - 5, ''tone'': ''professional''}). Useful for radio buttons, dropdowns, - or multi-dimensional rating systems.' - additionalProperties: - anyOf: - - type: number - - type: string - type: object - type: object - title: UserRating - description: 'User''s rating/evaluation of an AI response. - - - This captures various forms of end-user feedback about a model''s response, - including - - binary thumbs up/down ratings, numeric scores, free-text opinions, suggested - rewrites, - - and structured category ratings. - - - Either `thumb` or `rating` should be provided (they are mutually exclusive), - but all - - fields are optional to accommodate different feedback collection patterns.' ValidationError: properties: loc: diff --git a/openapi/ga/openapi.yaml b/openapi/ga/openapi.yaml index 874f04cbd3..f3c6662bf1 100644 --- a/openapi/ga/openapi.yaml +++ b/openapi/ga/openapi.yaml @@ -5755,834 +5755,12 @@ paths: application/json: schema: $ref: '#/components/schemas/HTTPValidationError' - /apis/intake/v2/workspaces/{workspace}/apps: - get: - tags: - - Apps - summary: List Apps - description: List all apps with filtering capabilities. - operationId: list_apps_apis_intake_v2_workspaces__workspace__apps_get - parameters: - - name: workspace - in: path - required: true - schema: - type: string - title: Workspace - - name: page - in: query - required: false - schema: - type: integer - description: Page number. - default: 1 - title: Page - description: Page number. - - name: page_size - in: query - required: false - schema: - type: integer - description: Page size. - default: 10 - title: Page Size - description: Page size. - - name: sort - in: query - required: false - schema: - allOf: - - $ref: '#/components/schemas/AppSortField' - description: The field to sort by. To sort in decreasing order, use `-` - in front of the field name. - default: created_at - description: The field to sort by. To sort in decreasing order, use `-` in - front of the field name. - - in: query - name: filter - style: deepObject - required: false - explode: true - schema: - $ref: '#/components/schemas/AppFilter' - description: Filter apps by name, description, project, created_at, and updated_at. - responses: - '200': - description: Successful Response - content: - application/json: - schema: - $ref: '#/components/schemas/AppsPage' - '422': - description: Validation Error - content: - application/json: - schema: - $ref: '#/components/schemas/HTTPValidationError' - post: - tags: - - Apps - summary: Create App - description: Create a new app. - operationId: create_app_apis_intake_v2_workspaces__workspace__apps_post - parameters: - - name: workspace - in: path - required: true - schema: - type: string - title: Workspace - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/AppInput' - responses: - '201': - description: Successful Response - content: - application/json: - schema: - $ref: '#/components/schemas/App' - '200': - description: Successful Response - '409': - description: App already exists - '422': - description: Validation Error - /apis/intake/v2/workspaces/{workspace}/apps/{app}/tasks/{name}: - get: - tags: - - Tasks - summary: Get Task - description: Get a specific task. - operationId: get_task_apis_intake_v2_workspaces__workspace__apps__app__tasks__name__get - parameters: - - name: workspace - in: path - required: true - schema: - type: string - title: Workspace - - name: app - in: path - required: true - schema: - type: string - title: App - - name: name - in: path - required: true - schema: - type: string - title: Name - responses: - '200': - description: Successful Response - content: - application/json: - schema: - $ref: '#/components/schemas/Task' - '422': - description: Validation Error - content: - application/json: - schema: - $ref: '#/components/schemas/HTTPValidationError' - patch: - tags: - - Tasks - summary: Update Task - description: Update an existing task. - operationId: update_task_apis_intake_v2_workspaces__workspace__apps__app__tasks__name__patch - parameters: - - name: workspace - in: path - required: true - schema: - type: string - title: Workspace - - name: app - in: path - required: true - schema: - type: string - title: App - - name: name - in: path - required: true - schema: - type: string - title: Name - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/TaskUpdate' - responses: - '200': - description: Successful Response - content: - application/json: - schema: - $ref: '#/components/schemas/Task' - '422': - description: Validation Error - content: - application/json: - schema: - $ref: '#/components/schemas/HTTPValidationError' - delete: - tags: - - Tasks - summary: Delete Task - description: Delete a task. - operationId: delete_task_apis_intake_v2_workspaces__workspace__apps__app__tasks__name__delete - parameters: - - name: workspace - in: path - required: true - schema: - type: string - title: Workspace - - name: app - in: path - required: true - schema: - type: string - title: App - - name: name - in: path - required: true - schema: - type: string - title: Name - responses: - '204': - description: Successful Response - '422': - description: Validation Error - content: - application/json: - schema: - $ref: '#/components/schemas/HTTPValidationError' - /apis/intake/v2/workspaces/{workspace}/apps/{name}: - get: - tags: - - Apps - summary: Get App - description: Get a specific app by workspace and name. - operationId: get_app_apis_intake_v2_workspaces__workspace__apps__name__get - parameters: - - name: workspace - in: path - required: true - schema: - type: string - title: Workspace - - name: name - in: path - required: true - schema: - type: string - title: Name - responses: - '200': - description: Successful Response - content: - application/json: - schema: - $ref: '#/components/schemas/App' - '422': - description: Validation Error - content: - application/json: - schema: - $ref: '#/components/schemas/HTTPValidationError' - patch: - tags: - - Apps - summary: Update App - description: Update an existing app. - operationId: update_app_apis_intake_v2_workspaces__workspace__apps__name__patch - parameters: - - name: workspace - in: path - required: true - schema: - type: string - title: Workspace - - name: name - in: path - required: true - schema: - type: string - title: Name - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/AppUpdate' - responses: - '200': - description: Successful Response - content: - application/json: - schema: - $ref: '#/components/schemas/App' - '422': - description: Validation Error - content: - application/json: - schema: - $ref: '#/components/schemas/HTTPValidationError' - delete: - tags: - - Apps - summary: Delete App - description: Delete an app. - operationId: delete_app_apis_intake_v2_workspaces__workspace__apps__name__delete - parameters: - - name: workspace - in: path - required: true - schema: - type: string - title: Workspace - - name: name - in: path - required: true - schema: - type: string - title: Name - responses: - '204': - description: Successful Response - '422': - description: Validation Error - content: - application/json: - schema: - $ref: '#/components/schemas/HTTPValidationError' - /apis/intake/v2/workspaces/{workspace}/apps/{name}/tasks: - get: - tags: - - Tasks - summary: List Tasks - description: List all tasks for a specific app. - operationId: list_tasks_apis_intake_v2_workspaces__workspace__apps__name__tasks_get - parameters: - - name: workspace - in: path - required: true - schema: - type: string - title: Workspace - - name: name - in: path - required: true - schema: - type: string - title: Name - - name: page - in: query - required: false - schema: - type: integer - description: Page number. - default: 1 - title: Page - description: Page number. - - name: page_size - in: query - required: false - schema: - type: integer - description: Page size. - default: 10 - title: Page Size - description: Page size. - - name: sort - in: query - required: false - schema: - allOf: - - $ref: '#/components/schemas/TaskSortField' - description: The field to sort by. To sort in decreasing order, use `-` - in front of the field name. - default: created_at - description: The field to sort by. To sort in decreasing order, use `-` in - front of the field name. - - in: query - name: filter - style: deepObject - required: false - explode: true - schema: - $ref: '#/components/schemas/TaskFilter' - description: Filter tasks by name, app, description, project, created_at, - and updated_at. - responses: - '200': - description: Successful Response - content: - application/json: - schema: - $ref: '#/components/schemas/TasksPage' - '422': - description: Validation Error - content: - application/json: - schema: - $ref: '#/components/schemas/HTTPValidationError' - post: - tags: - - Tasks - summary: Create Task - description: Create a new task. - operationId: create_task_apis_intake_v2_workspaces__workspace__apps__name__tasks_post - parameters: - - name: workspace - in: path - required: true - schema: - type: string - title: Workspace - - name: name - in: path - required: true - schema: - type: string - title: Name - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/TaskInput' - responses: - '201': - description: Successful Response - content: - application/json: - schema: - $ref: '#/components/schemas/Task' - '422': - description: Validation Error - content: - application/json: - schema: - $ref: '#/components/schemas/HTTPValidationError' - /apis/intake/v2/workspaces/{workspace}/entries: - get: - tags: - - Entries - summary: List Entries - description: 'List all entries with filtering capabilities. - - - When longest_per_thread=true is set in filters, returns only the longest entry - - (by message count) for each unique thread_id.' - operationId: list_entries_apis_intake_v2_workspaces__workspace__entries_get - parameters: - - name: workspace - in: path - required: true - schema: - type: string - title: Workspace - - name: page - in: query - required: false - schema: - type: integer - description: Page number. - default: 1 - title: Page - description: Page number. - - name: page_size - in: query - required: false - schema: - type: integer - description: Page size. - default: 10 - title: Page Size - description: Page size. - - name: sort - in: query - required: false - schema: - allOf: - - $ref: '#/components/schemas/EntrySortField' - description: The field to sort by. To sort in decreasing order, use `-` - in front of the field name. - default: created_at - description: The field to sort by. To sort in decreasing order, use `-` in - front of the field name. - - in: query - name: filter - style: deepObject - required: false - explode: true - schema: - $ref: '#/components/schemas/EntryFilter' - description: Filter entries by id, project, external_id, created_at, updated_at, - usage fields (model), context fields, and user_rating fields. - responses: - '200': - description: Successful Response - content: - application/json: - schema: - $ref: '#/components/schemas/EntrysPage' - '422': - description: Validation Error - content: - application/json: - schema: - $ref: '#/components/schemas/HTTPValidationError' - post: - tags: - - Entries - summary: Create Entry - description: 'Create a new entry. - - - Apps and tasks referenced in the entry context will be auto-created if they - don''t exist.' - operationId: create_entry_apis_intake_v2_workspaces__workspace__entries_post - parameters: - - name: workspace - in: path - required: true - schema: - type: string - title: Workspace - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/EntryInput' - responses: - '201': - description: Successful Response - content: - application/json: - schema: - $ref: '#/components/schemas/Entry' - '422': - description: Validation Error - content: - application/json: - schema: - $ref: '#/components/schemas/HTTPValidationError' - /apis/intake/v2/workspaces/{workspace}/entries/{entry}/events/{name}: - delete: - tags: - - Entries - summary: Delete Event - description: 'Delete a specific event from an entry. - - - Entry can be referenced by ID or external_id using `external:{external_id}` - prefix.' - operationId: delete_event_apis_intake_v2_workspaces__workspace__entries__entry__events__name__delete - parameters: - - name: workspace - in: path - required: true - schema: - type: string - title: Workspace - - name: entry - in: path - required: true - schema: - type: string - title: Entry - - name: name - in: path - required: true - schema: - type: string - title: Name - responses: - '200': - description: Successful Response - content: - application/json: - schema: - $ref: '#/components/schemas/Entry' - '422': - description: Validation Error - content: - application/json: - schema: - $ref: '#/components/schemas/HTTPValidationError' - /apis/intake/v2/workspaces/{workspace}/entries/{name}: - get: - tags: - - Entries - summary: Get Entry - description: 'Get a specific entry by ID or external_id. - - - Use `external:{external_id}` to get by external_id. - - Example: `/v2/workspaces/{workspace}/entries/external:chatcmpl-abc123`' - operationId: get_entry_apis_intake_v2_workspaces__workspace__entries__name__get - parameters: - - name: workspace - in: path - required: true - schema: - type: string - title: Workspace - - name: name - in: path - required: true - schema: - type: string - title: Name - responses: - '200': - description: Successful Response - content: - application/json: - schema: - $ref: '#/components/schemas/Entry' - '422': - description: Validation Error - content: - application/json: - schema: - $ref: '#/components/schemas/HTTPValidationError' - patch: - tags: - - Entries - summary: Update Entry - description: 'Update an existing entry by ID or external_id. - - - Use `external:{external_id}` to update by external_id. - - Example: `/v2/workspaces/{workspace}/entries/external:chatcmpl-abc123`' - operationId: update_entry_apis_intake_v2_workspaces__workspace__entries__name__patch - parameters: - - name: workspace - in: path - required: true - schema: - type: string - title: Workspace - - name: name - in: path - required: true - schema: - type: string - title: Name - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/EntryUpdate' - responses: - '200': - description: Successful Response - content: - application/json: - schema: - $ref: '#/components/schemas/Entry' - '422': - description: Validation Error - content: - application/json: - schema: - $ref: '#/components/schemas/HTTPValidationError' - delete: - tags: - - Entries - summary: Delete Entry - description: 'Delete an entry by ID or external_id. - - - Use `external:{external_id}` to delete by external_id. - - Example: `/v2/workspaces/{workspace}/entries/external:chatcmpl-abc123`' - operationId: delete_entry_apis_intake_v2_workspaces__workspace__entries__name__delete - parameters: - - name: workspace - in: path - required: true - schema: - type: string - title: Workspace - - name: name - in: path - required: true - schema: - type: string - title: Name - responses: - '204': - description: Successful Response - '422': - description: Validation Error - content: - application/json: - schema: - $ref: '#/components/schemas/HTTPValidationError' - /apis/intake/v2/workspaces/{workspace}/entries/{name}/events: - post: - tags: - - Entries - summary: Add Events - description: 'Add events to an entry by ID or external_id. - - - Use `external:{external_id}` to add events by external_id. - - Example: `/v2/workspaces/{workspace}/entries/external:chatcmpl-abc123/events`' - operationId: add_events_apis_intake_v2_workspaces__workspace__entries__name__events_post - parameters: - - name: workspace - in: path - required: true - schema: - type: string - title: Workspace - - name: name - in: path - required: true - schema: - type: string - title: Name - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/EventsCreateRequest' - responses: - '200': - description: Successful Response - content: - application/json: - schema: - $ref: '#/components/schemas/Entry' - '422': - description: Validation Error - content: - application/json: - schema: - $ref: '#/components/schemas/HTTPValidationError' - /apis/intake/v2/workspaces/{workspace}/evaluator-results: - post: - tags: - - Evaluator Results - summary: Create Evaluator Result - operationId: create_evaluator_result_apis_intake_v2_workspaces__workspace__evaluator_results_post - parameters: - - name: workspace - in: path - required: true - schema: - type: string - title: Workspace - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/EvaluatorResultInput' - responses: - '201': - description: Successful Response - content: - application/json: - schema: - $ref: '#/components/schemas/EvaluatorResult' - '422': - description: Validation Error - content: - application/json: - schema: - $ref: '#/components/schemas/HTTPValidationError' - get: - tags: - - Evaluator Results - summary: List Evaluator Results - operationId: list_evaluator_results_apis_intake_v2_workspaces__workspace__evaluator_results_get - parameters: - - name: workspace - in: path - required: true - schema: - type: string - title: Workspace - - name: page - in: query - required: false - schema: - type: integer - minimum: 1 - description: Page number. - default: 1 - title: Page - description: Page number. - - name: page_size - in: query - required: false - schema: - type: integer - maximum: 1000 - minimum: 1 - description: Page size. - default: 10 - title: Page Size - description: Page size. - - name: sort - in: query - required: false - schema: - allOf: - - $ref: '#/components/schemas/EvaluatorResultSortField' - default: -created_at - - in: query - name: filter - style: deepObject - required: false - explode: true - schema: - $ref: '#/components/schemas/EvaluatorResultFilter' - description: Filter evaluator results by span_id, session_id, name, data_type, - created_by, value range, and created_at range. - responses: - '200': - description: Successful Response - content: - application/json: - schema: - $ref: '#/components/schemas/EvaluatorResultsPage' - '422': - description: Validation Error - content: - application/json: - schema: - $ref: '#/components/schemas/HTTPValidationError' - /apis/intake/v2/workspaces/{workspace}/evaluator-results/{evaluator_result_id}: - get: + /apis/intake/v2/workspaces/{workspace}/evaluator-results: + post: tags: - Evaluator Results - summary: Get Evaluator Result - operationId: get_evaluator_result_apis_intake_v2_workspaces__workspace__evaluator_results__evaluator_result_id__get + summary: Create Evaluator Result + operationId: create_evaluator_result_apis_intake_v2_workspaces__workspace__evaluator_results_post parameters: - name: workspace in: path @@ -6590,14 +5768,14 @@ paths: schema: type: string title: Workspace - - name: evaluator_result_id - in: path + requestBody: required: true - schema: - type: string - title: Evaluator Result Id + content: + application/json: + schema: + $ref: '#/components/schemas/EvaluatorResultInput' responses: - '200': + '201': description: Successful Response content: application/json: @@ -6609,16 +5787,11 @@ paths: application/json: schema: $ref: '#/components/schemas/HTTPValidationError' - /apis/intake/v2/workspaces/{workspace}/export/jobs: get: tags: - - Exports - summary: List Export Jobs - description: 'List all export jobs with filtering capabilities. - - - Use `workspace=-` for cross-workspace listing.' - operationId: list_export_jobs_apis_intake_v2_workspaces__workspace__export_jobs_get + - Evaluator Results + summary: List Evaluator Results + operationId: list_evaluator_results_apis_intake_v2_workspaces__workspace__evaluator_results_get parameters: - name: workspace in: path @@ -6631,6 +5804,7 @@ paths: required: false schema: type: integer + minimum: 1 description: Page number. default: 1 title: Page @@ -6640,6 +5814,8 @@ paths: required: false schema: type: integer + maximum: 1000 + minimum: 1 description: Page size. default: 10 title: Page Size @@ -6649,88 +5825,36 @@ paths: required: false schema: allOf: - - $ref: '#/components/schemas/ExportJobSortField' - description: The field to sort by. To sort in decreasing order, use `-` - in front of the field name. - default: created_at - description: The field to sort by. To sort in decreasing order, use `-` in - front of the field name. + - $ref: '#/components/schemas/EvaluatorResultSortField' + default: -created_at - in: query name: filter style: deepObject required: false explode: true schema: - $ref: '#/components/schemas/ExportJobFilter' - description: Filter export jobs by name, status, output_file_url, created_at, - and updated_at. - responses: - '200': - description: Successful Response - content: - application/json: - schema: - $ref: '#/components/schemas/ExportJobsPage' - '422': - description: Validation Error - content: - application/json: - schema: - $ref: '#/components/schemas/HTTPValidationError' - post: - tags: - - Exports - summary: Create Export Job - description: 'Export entries to an external file. - - - Use the `longest_per_thread` filter to export only the longest entry per thread, - - which is useful for thread-based exports. - - - Supported output file URLs: - - - - NeMo Datastore: nds://workspace/dataset_name - - - HuggingFace Dataset: hf://datasets/org/name/path/to/file - - - Local filesystem: file:///path/to/export (for development)' - operationId: create_export_job_apis_intake_v2_workspaces__workspace__export_jobs_post - parameters: - - name: workspace - in: path - required: true - schema: - type: string - title: Workspace - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/ExportJobInput' + $ref: '#/components/schemas/EvaluatorResultFilter' + description: Filter evaluator results by span_id, session_id, name, data_type, + created_by, value range, and created_at range. responses: '200': description: Successful Response content: application/json: schema: - $ref: '#/components/schemas/ExportJob' + $ref: '#/components/schemas/EvaluatorResultsPage' '422': description: Validation Error content: application/json: schema: $ref: '#/components/schemas/HTTPValidationError' - /apis/intake/v2/workspaces/{workspace}/export/jobs/{name}: + /apis/intake/v2/workspaces/{workspace}/evaluator-results/{evaluator_result_id}: get: tags: - - Exports - summary: Get Export Job Status - description: Check the status of an export job. - operationId: get_export_job_status_apis_intake_v2_workspaces__workspace__export_jobs__name__get + - Evaluator Results + summary: Get Evaluator Result + operationId: get_evaluator_result_apis_intake_v2_workspaces__workspace__evaluator_results__evaluator_result_id__get parameters: - name: workspace in: path @@ -6738,52 +5862,19 @@ paths: schema: type: string title: Workspace - - name: name - in: path - required: true - schema: - type: string - title: Name - responses: - '200': - description: Successful Response - content: - application/json: - schema: - $ref: '#/components/schemas/ExportJob' - '422': - description: Validation Error - content: - application/json: - schema: - $ref: '#/components/schemas/HTTPValidationError' - /apis/intake/v2/workspaces/{workspace}/export/preview: - post: - tags: - - Exports - summary: Preview Export - description: Preview export data without writing to a file (max 100 records). - operationId: preview_export_apis_intake_v2_workspaces__workspace__export_preview_post - parameters: - - name: workspace + - name: evaluator_result_id in: path required: true schema: type: string - title: Workspace - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/ExportPreviewRequest' + title: Evaluator Result Id responses: '200': description: Successful Response content: application/json: schema: - $ref: '#/components/schemas/ExportPreviewResponse' + $ref: '#/components/schemas/EvaluatorResult' '422': description: Validation Error content: @@ -11521,157 +10612,6 @@ components: - judge_model title: AnswerAccuracyMetricResponse description: Response type for AnswerAccuracy metrics. - App: - properties: - id: - type: string - title: Id - description: Unique identifier - name: - type: string - title: Name - description: App name - workspace: - type: string - title: Workspace - description: Workspace identifier - description: - title: Description - description: App description - type: string - project: - title: Project - description: The name of the project associated with this app - type: string - locked: - type: boolean - title: Locked - description: Lock status - default: false - created_at: - title: Created At - description: Creation timestamp - type: string - format: date-time - updated_at: - title: Updated At - description: Last update timestamp - type: string - format: date-time - type: object - required: - - id - - name - - workspace - title: App - description: Schema for App responses. - AppFilter: - additionalProperties: false - description: Filter for Apps. - properties: - workspace: - description: Filter by workspace id. - title: Workspace - type: string - name: - description: Filter by app name. - title: Name - type: string - project: - description: Filter by project name. - title: Project - type: string - description: - description: Filter by app description. - title: Description - type: string - created_at: - allOf: - - $ref: '#/components/schemas/DatetimeFilter' - description: Filter entities based on creation date. - updated_at: - allOf: - - $ref: '#/components/schemas/DatetimeFilter' - description: Filter entities based on update date. - title: AppFilter - type: object - AppInput: - properties: - name: - type: string - title: Name - description: App name (unique within workspace) - description: - title: Description - description: App description - type: string - project: - title: Project - description: The name of the project associated with this app - type: string - locked: - type: boolean - title: Locked - description: If true, this record cannot be automatically updated when entries - are ingested. - default: false - type: object - required: - - name - title: AppInput - description: Schema for creating a new App. - AppSortField: - type: string - enum: - - created_at - - -created_at - - name - - -name - - updated_at - - -updated_at - title: AppSortField - description: Sort fields for Apps. - AppUpdate: - properties: - description: - title: Description - description: App description - type: string - project: - title: Project - description: The name of the project associated with this app - type: string - locked: - title: Locked - description: Lock status - type: boolean - type: object - title: AppUpdate - description: Schema for updating an existing App. - AppsPage: - properties: - data: - items: - $ref: '#/components/schemas/App' - type: array - title: Data - pagination: - allOf: - - $ref: '#/components/schemas/PaginationData' - description: Pagination information. - sort: - title: Sort - description: The field on which the results are sorted. - type: string - filter: - title: Filter - description: Filtering information. - additionalProperties: true - type: object - type: object - required: - - data - title: AppsPage AtifAgent: properties: name: @@ -13140,6 +12080,59 @@ components: - not_cacheable title: CacheStatus description: Cache status for files in external storage backends. + CapturedChatCompletionsRequest: + properties: + messages: + items: + $ref: '#/components/schemas/CapturedChatMessage' + type: array + title: Messages + description: Messages comprising the conversation. + model: + type: string + title: Model + description: The model identifier used for this request. + additionalProperties: true + type: object + required: + - messages + - model + title: CapturedChatCompletionsRequest + description: Flexible captured chat-completions request. + CapturedChatCompletionsResponse: + oneOf: + - required: + - choices + - required: + - error + properties: + choices: + title: Choices + items: + additionalProperties: true + type: object + type: array + error: + title: Error + additionalProperties: true + type: object + additionalProperties: true + type: object + title: CapturedChatCompletionsResponse + description: Flexible captured chat-completions response. + CapturedChatMessage: + properties: + role: + allOf: + - $ref: '#/components/schemas/ChatMessageRole' + description: The role of the message sender. + additionalProperties: true + type: object + required: + - role + title: CapturedChatMessage + description: A flexible message model that requires a valid role field but allows + provider-specific fields. ChatCompletionAssistantMessageParam: properties: role: @@ -13329,9 +12322,9 @@ components: ChatCompletionsIngestRequest: properties: request: - $ref: '#/components/schemas/FlexibleEntryRequestInput' + $ref: '#/components/schemas/CapturedChatCompletionsRequest' response: - $ref: '#/components/schemas/FlexibleEntryResponse' + $ref: '#/components/schemas/CapturedChatCompletionsResponse' session_id: title: Session Id description: Groups related chat-completions calls without forcing them @@ -13367,6 +12360,17 @@ components: - session_id - span_id title: ChatCompletionsIngestResponse + ChatMessageRole: + type: string + enum: + - user + - system + - assistant + - developer + - tool + - function + title: ChatMessageRole + description: Valid role values for captured chat-completions messages. ClassifyConfig: properties: enable_classify: @@ -15678,460 +14682,44 @@ components: type: object required: - data - title: EntityCreateInput - description: 'Schema for creating a new entity (name-based routes). - - - Name is optional - if not provided, it will be auto-generated. - - Workspace and entity_type come from the URL path parameters.' - EntityUpdate: - properties: - new_name: - title: New Name - description: Updated entity name (optional). Name must start with a lowercase - letter, be 2-63 characters, and contain only lowercase letters, digits, - and hyphens (no consecutive hyphens, cannot end with a hyphen). - examples: - - my-config - - baseline-model-v1 - type: string - pattern: ^[a-z](?!.*--)[a-z0-9\-@.+_]{1,62}(?`` entity-store paths.' - UserActionEvent: - properties: - id: - title: Id - description: Unique identifier for the event. Populated when retrieved from - database. - type: string - created_at: - type: string - format: date-time - title: Created At - description: UTC timestamp when the record was created. - created_by: - title: Created By - description: Identifier of the user or system that generated the record. - Can be set of key-value pairs. - additionalProperties: - type: string - type: object - event_type: - type: string - const: user_action - title: Event Type - default: user_action - action: - type: string - maxLength: 256 - title: Action - description: "Descriptive name for the action taken by the user (e.g., 'share_clicked',\ - \ 'code_copied', 'link_followed'). Use snake-case or kebab-case. This\ - \ is a label, not a unique identifier\u2014multiple events can have the\ - \ same action name." - metadata: - additionalProperties: - anyOf: - - type: string - maxLength: 256 - - items: - type: string - maxLength: 256 - type: array - - type: boolean - - type: integer - - type: number - propertyNames: - maxLength: 256 - type: object - maxProperties: 16 - title: Metadata - description: 'Optional key-value pairs with additional context about the - action (max 16 entries). Use this for details like user IDs, item IDs, - timestamps, A/B test variants, or any other information useful for downstream - training or evaluation pipelines. Example: {''user_id'': ''12345'', ''experiment'': - ''variant_b'', ''item_purchased'': ''product_456''}.' - additionalProperties: false - type: object - required: - - action - title: UserActionEvent - description: "Free-form user action captured by the client application.\n\n\ - Use this to track arbitrary user interactions with AI responses, such as copying\ - \ code,\nclicking share buttons, making purchases, or any other measurable\ - \ action.\n\nThe action identifier must be \u2264256 characters and should\ - \ use snake-case or kebab-case\nfor consistency (e.g., ``share_clicked``,\ - \ ``code_copied``, ``purchase_made``)." - UserFeedbackEvent: - properties: - id: - title: Id - description: Unique identifier for the event. Populated when retrieved from - database. - type: string - created_at: - type: string - format: date-time - title: Created At - description: UTC timestamp when the record was created. - created_by: - title: Created By - description: Identifier of the user or system that generated the record. - Can be set of key-value pairs. - additionalProperties: - type: string - type: object - event_type: - type: string - const: user_feedback - title: Event Type - default: user_feedback - thumb: - allOf: - - $ref: '#/components/schemas/ThumbDirection' - description: "Binary feedback: \"up\" for \U0001F44D or \"down\" for \U0001F44E\ - . Mutually exclusive with `rating`. Use this for simple thumbs up/down\ - \ UI elements." - rating: - title: Rating - description: Numeric rating (e.g., 1-5 stars) provided by the end user. - Mutually exclusive with `thumb`. Use this for star ratings or numeric - scales. - type: number - minimum: 0.0 - opinion: - title: Opinion - description: Free-text comment from the end user describing their opinion - of the response. - type: string - maxLength: 2000 - minLength: 1 - rewrite: - title: Rewrite - description: End-user's suggested text replacement for the generated response. - This is the user's idea of what the response should have been. - type: string - maxLength: 10000 - minLength: 1 - chosen_index: - title: Chosen Index - description: Zero-based index of the response option the user selected when - multiple responses were returned. Use this when showing users multiple - completion choices and tracking which one they picked. - type: integer - minimum: 0.0 - categories: - title: Categories - description: 'Application-specific category ratings as key-value pairs. - Use this for custom rating dimensions (e.g., {''helpfulness'': 4, ''accuracy'': - 5, ''tone'': ''professional''}). Useful for radio buttons, dropdowns, - or multi-dimensional rating systems.' - additionalProperties: - anyOf: - - type: number - - type: string - type: object - additionalProperties: false - type: object - title: UserFeedbackEvent - description: 'Structured feedback supplied by an end-user. - - - This event captures various forms of end-user feedback about a model''s response, - - including binary thumbs up/down ratings, numeric scores, free-text opinions, - - suggested rewrites, and structured category ratings. - - - Either `thumb` or `rating` should be provided (they are mutually exclusive), - but all - - feedback fields are optional to accommodate different feedback collection - patterns.' UserMessagesConfig: properties: embeddings_only: @@ -31545,71 +29100,6 @@ components: type: object title: UserMessagesConfig description: Configuration for how the user messages are interpreted. - UserRating: - properties: - thumb: - allOf: - - $ref: '#/components/schemas/ThumbDirection' - description: "Binary feedback: \"up\" for \U0001F44D or \"down\" for \U0001F44E\ - . Mutually exclusive with `rating`. Use this for simple thumbs up/down\ - \ UI elements." - rating: - title: Rating - description: Numeric rating (e.g., 1-5 stars) provided by the end user. - Mutually exclusive with `thumb`. Use this for star ratings or numeric - scales. - type: number - minimum: 0.0 - opinion: - title: Opinion - description: Free-text comment from the end user describing their opinion - of the response. - type: string - maxLength: 2000 - minLength: 1 - rewrite: - title: Rewrite - description: End-user's suggested text replacement for the generated response. - This is the user's idea of what the response should have been. - type: string - maxLength: 10000 - minLength: 1 - chosen_index: - title: Chosen Index - description: Zero-based index of the response option the user selected when - multiple responses were returned. Use this when showing users multiple - completion choices and tracking which one they picked. - type: integer - minimum: 0.0 - categories: - title: Categories - description: 'Application-specific category ratings as key-value pairs. - Use this for custom rating dimensions (e.g., {''helpfulness'': 4, ''accuracy'': - 5, ''tone'': ''professional''}). Useful for radio buttons, dropdowns, - or multi-dimensional rating systems.' - additionalProperties: - anyOf: - - type: number - - type: string - type: object - type: object - title: UserRating - description: 'User''s rating/evaluation of an AI response. - - - This captures various forms of end-user feedback about a model''s response, - including - - binary thumbs up/down ratings, numeric scores, free-text opinions, suggested - rewrites, - - and structured category ratings. - - - Either `thumb` or `rating` should be provided (they are mutually exclusive), - but all - - fields are optional to accommodate different feedback collection patterns.' ValidationError: properties: loc: diff --git a/openapi/openapi.yaml b/openapi/openapi.yaml index 874f04cbd3..f3c6662bf1 100644 --- a/openapi/openapi.yaml +++ b/openapi/openapi.yaml @@ -5755,834 +5755,12 @@ paths: application/json: schema: $ref: '#/components/schemas/HTTPValidationError' - /apis/intake/v2/workspaces/{workspace}/apps: - get: - tags: - - Apps - summary: List Apps - description: List all apps with filtering capabilities. - operationId: list_apps_apis_intake_v2_workspaces__workspace__apps_get - parameters: - - name: workspace - in: path - required: true - schema: - type: string - title: Workspace - - name: page - in: query - required: false - schema: - type: integer - description: Page number. - default: 1 - title: Page - description: Page number. - - name: page_size - in: query - required: false - schema: - type: integer - description: Page size. - default: 10 - title: Page Size - description: Page size. - - name: sort - in: query - required: false - schema: - allOf: - - $ref: '#/components/schemas/AppSortField' - description: The field to sort by. To sort in decreasing order, use `-` - in front of the field name. - default: created_at - description: The field to sort by. To sort in decreasing order, use `-` in - front of the field name. - - in: query - name: filter - style: deepObject - required: false - explode: true - schema: - $ref: '#/components/schemas/AppFilter' - description: Filter apps by name, description, project, created_at, and updated_at. - responses: - '200': - description: Successful Response - content: - application/json: - schema: - $ref: '#/components/schemas/AppsPage' - '422': - description: Validation Error - content: - application/json: - schema: - $ref: '#/components/schemas/HTTPValidationError' - post: - tags: - - Apps - summary: Create App - description: Create a new app. - operationId: create_app_apis_intake_v2_workspaces__workspace__apps_post - parameters: - - name: workspace - in: path - required: true - schema: - type: string - title: Workspace - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/AppInput' - responses: - '201': - description: Successful Response - content: - application/json: - schema: - $ref: '#/components/schemas/App' - '200': - description: Successful Response - '409': - description: App already exists - '422': - description: Validation Error - /apis/intake/v2/workspaces/{workspace}/apps/{app}/tasks/{name}: - get: - tags: - - Tasks - summary: Get Task - description: Get a specific task. - operationId: get_task_apis_intake_v2_workspaces__workspace__apps__app__tasks__name__get - parameters: - - name: workspace - in: path - required: true - schema: - type: string - title: Workspace - - name: app - in: path - required: true - schema: - type: string - title: App - - name: name - in: path - required: true - schema: - type: string - title: Name - responses: - '200': - description: Successful Response - content: - application/json: - schema: - $ref: '#/components/schemas/Task' - '422': - description: Validation Error - content: - application/json: - schema: - $ref: '#/components/schemas/HTTPValidationError' - patch: - tags: - - Tasks - summary: Update Task - description: Update an existing task. - operationId: update_task_apis_intake_v2_workspaces__workspace__apps__app__tasks__name__patch - parameters: - - name: workspace - in: path - required: true - schema: - type: string - title: Workspace - - name: app - in: path - required: true - schema: - type: string - title: App - - name: name - in: path - required: true - schema: - type: string - title: Name - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/TaskUpdate' - responses: - '200': - description: Successful Response - content: - application/json: - schema: - $ref: '#/components/schemas/Task' - '422': - description: Validation Error - content: - application/json: - schema: - $ref: '#/components/schemas/HTTPValidationError' - delete: - tags: - - Tasks - summary: Delete Task - description: Delete a task. - operationId: delete_task_apis_intake_v2_workspaces__workspace__apps__app__tasks__name__delete - parameters: - - name: workspace - in: path - required: true - schema: - type: string - title: Workspace - - name: app - in: path - required: true - schema: - type: string - title: App - - name: name - in: path - required: true - schema: - type: string - title: Name - responses: - '204': - description: Successful Response - '422': - description: Validation Error - content: - application/json: - schema: - $ref: '#/components/schemas/HTTPValidationError' - /apis/intake/v2/workspaces/{workspace}/apps/{name}: - get: - tags: - - Apps - summary: Get App - description: Get a specific app by workspace and name. - operationId: get_app_apis_intake_v2_workspaces__workspace__apps__name__get - parameters: - - name: workspace - in: path - required: true - schema: - type: string - title: Workspace - - name: name - in: path - required: true - schema: - type: string - title: Name - responses: - '200': - description: Successful Response - content: - application/json: - schema: - $ref: '#/components/schemas/App' - '422': - description: Validation Error - content: - application/json: - schema: - $ref: '#/components/schemas/HTTPValidationError' - patch: - tags: - - Apps - summary: Update App - description: Update an existing app. - operationId: update_app_apis_intake_v2_workspaces__workspace__apps__name__patch - parameters: - - name: workspace - in: path - required: true - schema: - type: string - title: Workspace - - name: name - in: path - required: true - schema: - type: string - title: Name - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/AppUpdate' - responses: - '200': - description: Successful Response - content: - application/json: - schema: - $ref: '#/components/schemas/App' - '422': - description: Validation Error - content: - application/json: - schema: - $ref: '#/components/schemas/HTTPValidationError' - delete: - tags: - - Apps - summary: Delete App - description: Delete an app. - operationId: delete_app_apis_intake_v2_workspaces__workspace__apps__name__delete - parameters: - - name: workspace - in: path - required: true - schema: - type: string - title: Workspace - - name: name - in: path - required: true - schema: - type: string - title: Name - responses: - '204': - description: Successful Response - '422': - description: Validation Error - content: - application/json: - schema: - $ref: '#/components/schemas/HTTPValidationError' - /apis/intake/v2/workspaces/{workspace}/apps/{name}/tasks: - get: - tags: - - Tasks - summary: List Tasks - description: List all tasks for a specific app. - operationId: list_tasks_apis_intake_v2_workspaces__workspace__apps__name__tasks_get - parameters: - - name: workspace - in: path - required: true - schema: - type: string - title: Workspace - - name: name - in: path - required: true - schema: - type: string - title: Name - - name: page - in: query - required: false - schema: - type: integer - description: Page number. - default: 1 - title: Page - description: Page number. - - name: page_size - in: query - required: false - schema: - type: integer - description: Page size. - default: 10 - title: Page Size - description: Page size. - - name: sort - in: query - required: false - schema: - allOf: - - $ref: '#/components/schemas/TaskSortField' - description: The field to sort by. To sort in decreasing order, use `-` - in front of the field name. - default: created_at - description: The field to sort by. To sort in decreasing order, use `-` in - front of the field name. - - in: query - name: filter - style: deepObject - required: false - explode: true - schema: - $ref: '#/components/schemas/TaskFilter' - description: Filter tasks by name, app, description, project, created_at, - and updated_at. - responses: - '200': - description: Successful Response - content: - application/json: - schema: - $ref: '#/components/schemas/TasksPage' - '422': - description: Validation Error - content: - application/json: - schema: - $ref: '#/components/schemas/HTTPValidationError' - post: - tags: - - Tasks - summary: Create Task - description: Create a new task. - operationId: create_task_apis_intake_v2_workspaces__workspace__apps__name__tasks_post - parameters: - - name: workspace - in: path - required: true - schema: - type: string - title: Workspace - - name: name - in: path - required: true - schema: - type: string - title: Name - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/TaskInput' - responses: - '201': - description: Successful Response - content: - application/json: - schema: - $ref: '#/components/schemas/Task' - '422': - description: Validation Error - content: - application/json: - schema: - $ref: '#/components/schemas/HTTPValidationError' - /apis/intake/v2/workspaces/{workspace}/entries: - get: - tags: - - Entries - summary: List Entries - description: 'List all entries with filtering capabilities. - - - When longest_per_thread=true is set in filters, returns only the longest entry - - (by message count) for each unique thread_id.' - operationId: list_entries_apis_intake_v2_workspaces__workspace__entries_get - parameters: - - name: workspace - in: path - required: true - schema: - type: string - title: Workspace - - name: page - in: query - required: false - schema: - type: integer - description: Page number. - default: 1 - title: Page - description: Page number. - - name: page_size - in: query - required: false - schema: - type: integer - description: Page size. - default: 10 - title: Page Size - description: Page size. - - name: sort - in: query - required: false - schema: - allOf: - - $ref: '#/components/schemas/EntrySortField' - description: The field to sort by. To sort in decreasing order, use `-` - in front of the field name. - default: created_at - description: The field to sort by. To sort in decreasing order, use `-` in - front of the field name. - - in: query - name: filter - style: deepObject - required: false - explode: true - schema: - $ref: '#/components/schemas/EntryFilter' - description: Filter entries by id, project, external_id, created_at, updated_at, - usage fields (model), context fields, and user_rating fields. - responses: - '200': - description: Successful Response - content: - application/json: - schema: - $ref: '#/components/schemas/EntrysPage' - '422': - description: Validation Error - content: - application/json: - schema: - $ref: '#/components/schemas/HTTPValidationError' - post: - tags: - - Entries - summary: Create Entry - description: 'Create a new entry. - - - Apps and tasks referenced in the entry context will be auto-created if they - don''t exist.' - operationId: create_entry_apis_intake_v2_workspaces__workspace__entries_post - parameters: - - name: workspace - in: path - required: true - schema: - type: string - title: Workspace - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/EntryInput' - responses: - '201': - description: Successful Response - content: - application/json: - schema: - $ref: '#/components/schemas/Entry' - '422': - description: Validation Error - content: - application/json: - schema: - $ref: '#/components/schemas/HTTPValidationError' - /apis/intake/v2/workspaces/{workspace}/entries/{entry}/events/{name}: - delete: - tags: - - Entries - summary: Delete Event - description: 'Delete a specific event from an entry. - - - Entry can be referenced by ID or external_id using `external:{external_id}` - prefix.' - operationId: delete_event_apis_intake_v2_workspaces__workspace__entries__entry__events__name__delete - parameters: - - name: workspace - in: path - required: true - schema: - type: string - title: Workspace - - name: entry - in: path - required: true - schema: - type: string - title: Entry - - name: name - in: path - required: true - schema: - type: string - title: Name - responses: - '200': - description: Successful Response - content: - application/json: - schema: - $ref: '#/components/schemas/Entry' - '422': - description: Validation Error - content: - application/json: - schema: - $ref: '#/components/schemas/HTTPValidationError' - /apis/intake/v2/workspaces/{workspace}/entries/{name}: - get: - tags: - - Entries - summary: Get Entry - description: 'Get a specific entry by ID or external_id. - - - Use `external:{external_id}` to get by external_id. - - Example: `/v2/workspaces/{workspace}/entries/external:chatcmpl-abc123`' - operationId: get_entry_apis_intake_v2_workspaces__workspace__entries__name__get - parameters: - - name: workspace - in: path - required: true - schema: - type: string - title: Workspace - - name: name - in: path - required: true - schema: - type: string - title: Name - responses: - '200': - description: Successful Response - content: - application/json: - schema: - $ref: '#/components/schemas/Entry' - '422': - description: Validation Error - content: - application/json: - schema: - $ref: '#/components/schemas/HTTPValidationError' - patch: - tags: - - Entries - summary: Update Entry - description: 'Update an existing entry by ID or external_id. - - - Use `external:{external_id}` to update by external_id. - - Example: `/v2/workspaces/{workspace}/entries/external:chatcmpl-abc123`' - operationId: update_entry_apis_intake_v2_workspaces__workspace__entries__name__patch - parameters: - - name: workspace - in: path - required: true - schema: - type: string - title: Workspace - - name: name - in: path - required: true - schema: - type: string - title: Name - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/EntryUpdate' - responses: - '200': - description: Successful Response - content: - application/json: - schema: - $ref: '#/components/schemas/Entry' - '422': - description: Validation Error - content: - application/json: - schema: - $ref: '#/components/schemas/HTTPValidationError' - delete: - tags: - - Entries - summary: Delete Entry - description: 'Delete an entry by ID or external_id. - - - Use `external:{external_id}` to delete by external_id. - - Example: `/v2/workspaces/{workspace}/entries/external:chatcmpl-abc123`' - operationId: delete_entry_apis_intake_v2_workspaces__workspace__entries__name__delete - parameters: - - name: workspace - in: path - required: true - schema: - type: string - title: Workspace - - name: name - in: path - required: true - schema: - type: string - title: Name - responses: - '204': - description: Successful Response - '422': - description: Validation Error - content: - application/json: - schema: - $ref: '#/components/schemas/HTTPValidationError' - /apis/intake/v2/workspaces/{workspace}/entries/{name}/events: - post: - tags: - - Entries - summary: Add Events - description: 'Add events to an entry by ID or external_id. - - - Use `external:{external_id}` to add events by external_id. - - Example: `/v2/workspaces/{workspace}/entries/external:chatcmpl-abc123/events`' - operationId: add_events_apis_intake_v2_workspaces__workspace__entries__name__events_post - parameters: - - name: workspace - in: path - required: true - schema: - type: string - title: Workspace - - name: name - in: path - required: true - schema: - type: string - title: Name - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/EventsCreateRequest' - responses: - '200': - description: Successful Response - content: - application/json: - schema: - $ref: '#/components/schemas/Entry' - '422': - description: Validation Error - content: - application/json: - schema: - $ref: '#/components/schemas/HTTPValidationError' - /apis/intake/v2/workspaces/{workspace}/evaluator-results: - post: - tags: - - Evaluator Results - summary: Create Evaluator Result - operationId: create_evaluator_result_apis_intake_v2_workspaces__workspace__evaluator_results_post - parameters: - - name: workspace - in: path - required: true - schema: - type: string - title: Workspace - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/EvaluatorResultInput' - responses: - '201': - description: Successful Response - content: - application/json: - schema: - $ref: '#/components/schemas/EvaluatorResult' - '422': - description: Validation Error - content: - application/json: - schema: - $ref: '#/components/schemas/HTTPValidationError' - get: - tags: - - Evaluator Results - summary: List Evaluator Results - operationId: list_evaluator_results_apis_intake_v2_workspaces__workspace__evaluator_results_get - parameters: - - name: workspace - in: path - required: true - schema: - type: string - title: Workspace - - name: page - in: query - required: false - schema: - type: integer - minimum: 1 - description: Page number. - default: 1 - title: Page - description: Page number. - - name: page_size - in: query - required: false - schema: - type: integer - maximum: 1000 - minimum: 1 - description: Page size. - default: 10 - title: Page Size - description: Page size. - - name: sort - in: query - required: false - schema: - allOf: - - $ref: '#/components/schemas/EvaluatorResultSortField' - default: -created_at - - in: query - name: filter - style: deepObject - required: false - explode: true - schema: - $ref: '#/components/schemas/EvaluatorResultFilter' - description: Filter evaluator results by span_id, session_id, name, data_type, - created_by, value range, and created_at range. - responses: - '200': - description: Successful Response - content: - application/json: - schema: - $ref: '#/components/schemas/EvaluatorResultsPage' - '422': - description: Validation Error - content: - application/json: - schema: - $ref: '#/components/schemas/HTTPValidationError' - /apis/intake/v2/workspaces/{workspace}/evaluator-results/{evaluator_result_id}: - get: + /apis/intake/v2/workspaces/{workspace}/evaluator-results: + post: tags: - Evaluator Results - summary: Get Evaluator Result - operationId: get_evaluator_result_apis_intake_v2_workspaces__workspace__evaluator_results__evaluator_result_id__get + summary: Create Evaluator Result + operationId: create_evaluator_result_apis_intake_v2_workspaces__workspace__evaluator_results_post parameters: - name: workspace in: path @@ -6590,14 +5768,14 @@ paths: schema: type: string title: Workspace - - name: evaluator_result_id - in: path + requestBody: required: true - schema: - type: string - title: Evaluator Result Id + content: + application/json: + schema: + $ref: '#/components/schemas/EvaluatorResultInput' responses: - '200': + '201': description: Successful Response content: application/json: @@ -6609,16 +5787,11 @@ paths: application/json: schema: $ref: '#/components/schemas/HTTPValidationError' - /apis/intake/v2/workspaces/{workspace}/export/jobs: get: tags: - - Exports - summary: List Export Jobs - description: 'List all export jobs with filtering capabilities. - - - Use `workspace=-` for cross-workspace listing.' - operationId: list_export_jobs_apis_intake_v2_workspaces__workspace__export_jobs_get + - Evaluator Results + summary: List Evaluator Results + operationId: list_evaluator_results_apis_intake_v2_workspaces__workspace__evaluator_results_get parameters: - name: workspace in: path @@ -6631,6 +5804,7 @@ paths: required: false schema: type: integer + minimum: 1 description: Page number. default: 1 title: Page @@ -6640,6 +5814,8 @@ paths: required: false schema: type: integer + maximum: 1000 + minimum: 1 description: Page size. default: 10 title: Page Size @@ -6649,88 +5825,36 @@ paths: required: false schema: allOf: - - $ref: '#/components/schemas/ExportJobSortField' - description: The field to sort by. To sort in decreasing order, use `-` - in front of the field name. - default: created_at - description: The field to sort by. To sort in decreasing order, use `-` in - front of the field name. + - $ref: '#/components/schemas/EvaluatorResultSortField' + default: -created_at - in: query name: filter style: deepObject required: false explode: true schema: - $ref: '#/components/schemas/ExportJobFilter' - description: Filter export jobs by name, status, output_file_url, created_at, - and updated_at. - responses: - '200': - description: Successful Response - content: - application/json: - schema: - $ref: '#/components/schemas/ExportJobsPage' - '422': - description: Validation Error - content: - application/json: - schema: - $ref: '#/components/schemas/HTTPValidationError' - post: - tags: - - Exports - summary: Create Export Job - description: 'Export entries to an external file. - - - Use the `longest_per_thread` filter to export only the longest entry per thread, - - which is useful for thread-based exports. - - - Supported output file URLs: - - - - NeMo Datastore: nds://workspace/dataset_name - - - HuggingFace Dataset: hf://datasets/org/name/path/to/file - - - Local filesystem: file:///path/to/export (for development)' - operationId: create_export_job_apis_intake_v2_workspaces__workspace__export_jobs_post - parameters: - - name: workspace - in: path - required: true - schema: - type: string - title: Workspace - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/ExportJobInput' + $ref: '#/components/schemas/EvaluatorResultFilter' + description: Filter evaluator results by span_id, session_id, name, data_type, + created_by, value range, and created_at range. responses: '200': description: Successful Response content: application/json: schema: - $ref: '#/components/schemas/ExportJob' + $ref: '#/components/schemas/EvaluatorResultsPage' '422': description: Validation Error content: application/json: schema: $ref: '#/components/schemas/HTTPValidationError' - /apis/intake/v2/workspaces/{workspace}/export/jobs/{name}: + /apis/intake/v2/workspaces/{workspace}/evaluator-results/{evaluator_result_id}: get: tags: - - Exports - summary: Get Export Job Status - description: Check the status of an export job. - operationId: get_export_job_status_apis_intake_v2_workspaces__workspace__export_jobs__name__get + - Evaluator Results + summary: Get Evaluator Result + operationId: get_evaluator_result_apis_intake_v2_workspaces__workspace__evaluator_results__evaluator_result_id__get parameters: - name: workspace in: path @@ -6738,52 +5862,19 @@ paths: schema: type: string title: Workspace - - name: name - in: path - required: true - schema: - type: string - title: Name - responses: - '200': - description: Successful Response - content: - application/json: - schema: - $ref: '#/components/schemas/ExportJob' - '422': - description: Validation Error - content: - application/json: - schema: - $ref: '#/components/schemas/HTTPValidationError' - /apis/intake/v2/workspaces/{workspace}/export/preview: - post: - tags: - - Exports - summary: Preview Export - description: Preview export data without writing to a file (max 100 records). - operationId: preview_export_apis_intake_v2_workspaces__workspace__export_preview_post - parameters: - - name: workspace + - name: evaluator_result_id in: path required: true schema: type: string - title: Workspace - requestBody: - required: true - content: - application/json: - schema: - $ref: '#/components/schemas/ExportPreviewRequest' + title: Evaluator Result Id responses: '200': description: Successful Response content: application/json: schema: - $ref: '#/components/schemas/ExportPreviewResponse' + $ref: '#/components/schemas/EvaluatorResult' '422': description: Validation Error content: @@ -11521,157 +10612,6 @@ components: - judge_model title: AnswerAccuracyMetricResponse description: Response type for AnswerAccuracy metrics. - App: - properties: - id: - type: string - title: Id - description: Unique identifier - name: - type: string - title: Name - description: App name - workspace: - type: string - title: Workspace - description: Workspace identifier - description: - title: Description - description: App description - type: string - project: - title: Project - description: The name of the project associated with this app - type: string - locked: - type: boolean - title: Locked - description: Lock status - default: false - created_at: - title: Created At - description: Creation timestamp - type: string - format: date-time - updated_at: - title: Updated At - description: Last update timestamp - type: string - format: date-time - type: object - required: - - id - - name - - workspace - title: App - description: Schema for App responses. - AppFilter: - additionalProperties: false - description: Filter for Apps. - properties: - workspace: - description: Filter by workspace id. - title: Workspace - type: string - name: - description: Filter by app name. - title: Name - type: string - project: - description: Filter by project name. - title: Project - type: string - description: - description: Filter by app description. - title: Description - type: string - created_at: - allOf: - - $ref: '#/components/schemas/DatetimeFilter' - description: Filter entities based on creation date. - updated_at: - allOf: - - $ref: '#/components/schemas/DatetimeFilter' - description: Filter entities based on update date. - title: AppFilter - type: object - AppInput: - properties: - name: - type: string - title: Name - description: App name (unique within workspace) - description: - title: Description - description: App description - type: string - project: - title: Project - description: The name of the project associated with this app - type: string - locked: - type: boolean - title: Locked - description: If true, this record cannot be automatically updated when entries - are ingested. - default: false - type: object - required: - - name - title: AppInput - description: Schema for creating a new App. - AppSortField: - type: string - enum: - - created_at - - -created_at - - name - - -name - - updated_at - - -updated_at - title: AppSortField - description: Sort fields for Apps. - AppUpdate: - properties: - description: - title: Description - description: App description - type: string - project: - title: Project - description: The name of the project associated with this app - type: string - locked: - title: Locked - description: Lock status - type: boolean - type: object - title: AppUpdate - description: Schema for updating an existing App. - AppsPage: - properties: - data: - items: - $ref: '#/components/schemas/App' - type: array - title: Data - pagination: - allOf: - - $ref: '#/components/schemas/PaginationData' - description: Pagination information. - sort: - title: Sort - description: The field on which the results are sorted. - type: string - filter: - title: Filter - description: Filtering information. - additionalProperties: true - type: object - type: object - required: - - data - title: AppsPage AtifAgent: properties: name: @@ -13140,6 +12080,59 @@ components: - not_cacheable title: CacheStatus description: Cache status for files in external storage backends. + CapturedChatCompletionsRequest: + properties: + messages: + items: + $ref: '#/components/schemas/CapturedChatMessage' + type: array + title: Messages + description: Messages comprising the conversation. + model: + type: string + title: Model + description: The model identifier used for this request. + additionalProperties: true + type: object + required: + - messages + - model + title: CapturedChatCompletionsRequest + description: Flexible captured chat-completions request. + CapturedChatCompletionsResponse: + oneOf: + - required: + - choices + - required: + - error + properties: + choices: + title: Choices + items: + additionalProperties: true + type: object + type: array + error: + title: Error + additionalProperties: true + type: object + additionalProperties: true + type: object + title: CapturedChatCompletionsResponse + description: Flexible captured chat-completions response. + CapturedChatMessage: + properties: + role: + allOf: + - $ref: '#/components/schemas/ChatMessageRole' + description: The role of the message sender. + additionalProperties: true + type: object + required: + - role + title: CapturedChatMessage + description: A flexible message model that requires a valid role field but allows + provider-specific fields. ChatCompletionAssistantMessageParam: properties: role: @@ -13329,9 +12322,9 @@ components: ChatCompletionsIngestRequest: properties: request: - $ref: '#/components/schemas/FlexibleEntryRequestInput' + $ref: '#/components/schemas/CapturedChatCompletionsRequest' response: - $ref: '#/components/schemas/FlexibleEntryResponse' + $ref: '#/components/schemas/CapturedChatCompletionsResponse' session_id: title: Session Id description: Groups related chat-completions calls without forcing them @@ -13367,6 +12360,17 @@ components: - session_id - span_id title: ChatCompletionsIngestResponse + ChatMessageRole: + type: string + enum: + - user + - system + - assistant + - developer + - tool + - function + title: ChatMessageRole + description: Valid role values for captured chat-completions messages. ClassifyConfig: properties: enable_classify: @@ -15678,460 +14682,44 @@ components: type: object required: - data - title: EntityCreateInput - description: 'Schema for creating a new entity (name-based routes). - - - Name is optional - if not provided, it will be auto-generated. - - Workspace and entity_type come from the URL path parameters.' - EntityUpdate: - properties: - new_name: - title: New Name - description: Updated entity name (optional). Name must start with a lowercase - letter, be 2-63 characters, and contain only lowercase letters, digits, - and hyphens (no consecutive hyphens, cannot end with a hyphen). - examples: - - my-config - - baseline-model-v1 - type: string - pattern: ^[a-z](?!.*--)[a-z0-9\-@.+_]{1,62}(?`` entity-store paths.' - UserActionEvent: - properties: - id: - title: Id - description: Unique identifier for the event. Populated when retrieved from - database. - type: string - created_at: - type: string - format: date-time - title: Created At - description: UTC timestamp when the record was created. - created_by: - title: Created By - description: Identifier of the user or system that generated the record. - Can be set of key-value pairs. - additionalProperties: - type: string - type: object - event_type: - type: string - const: user_action - title: Event Type - default: user_action - action: - type: string - maxLength: 256 - title: Action - description: "Descriptive name for the action taken by the user (e.g., 'share_clicked',\ - \ 'code_copied', 'link_followed'). Use snake-case or kebab-case. This\ - \ is a label, not a unique identifier\u2014multiple events can have the\ - \ same action name." - metadata: - additionalProperties: - anyOf: - - type: string - maxLength: 256 - - items: - type: string - maxLength: 256 - type: array - - type: boolean - - type: integer - - type: number - propertyNames: - maxLength: 256 - type: object - maxProperties: 16 - title: Metadata - description: 'Optional key-value pairs with additional context about the - action (max 16 entries). Use this for details like user IDs, item IDs, - timestamps, A/B test variants, or any other information useful for downstream - training or evaluation pipelines. Example: {''user_id'': ''12345'', ''experiment'': - ''variant_b'', ''item_purchased'': ''product_456''}.' - additionalProperties: false - type: object - required: - - action - title: UserActionEvent - description: "Free-form user action captured by the client application.\n\n\ - Use this to track arbitrary user interactions with AI responses, such as copying\ - \ code,\nclicking share buttons, making purchases, or any other measurable\ - \ action.\n\nThe action identifier must be \u2264256 characters and should\ - \ use snake-case or kebab-case\nfor consistency (e.g., ``share_clicked``,\ - \ ``code_copied``, ``purchase_made``)." - UserFeedbackEvent: - properties: - id: - title: Id - description: Unique identifier for the event. Populated when retrieved from - database. - type: string - created_at: - type: string - format: date-time - title: Created At - description: UTC timestamp when the record was created. - created_by: - title: Created By - description: Identifier of the user or system that generated the record. - Can be set of key-value pairs. - additionalProperties: - type: string - type: object - event_type: - type: string - const: user_feedback - title: Event Type - default: user_feedback - thumb: - allOf: - - $ref: '#/components/schemas/ThumbDirection' - description: "Binary feedback: \"up\" for \U0001F44D or \"down\" for \U0001F44E\ - . Mutually exclusive with `rating`. Use this for simple thumbs up/down\ - \ UI elements." - rating: - title: Rating - description: Numeric rating (e.g., 1-5 stars) provided by the end user. - Mutually exclusive with `thumb`. Use this for star ratings or numeric - scales. - type: number - minimum: 0.0 - opinion: - title: Opinion - description: Free-text comment from the end user describing their opinion - of the response. - type: string - maxLength: 2000 - minLength: 1 - rewrite: - title: Rewrite - description: End-user's suggested text replacement for the generated response. - This is the user's idea of what the response should have been. - type: string - maxLength: 10000 - minLength: 1 - chosen_index: - title: Chosen Index - description: Zero-based index of the response option the user selected when - multiple responses were returned. Use this when showing users multiple - completion choices and tracking which one they picked. - type: integer - minimum: 0.0 - categories: - title: Categories - description: 'Application-specific category ratings as key-value pairs. - Use this for custom rating dimensions (e.g., {''helpfulness'': 4, ''accuracy'': - 5, ''tone'': ''professional''}). Useful for radio buttons, dropdowns, - or multi-dimensional rating systems.' - additionalProperties: - anyOf: - - type: number - - type: string - type: object - additionalProperties: false - type: object - title: UserFeedbackEvent - description: 'Structured feedback supplied by an end-user. - - - This event captures various forms of end-user feedback about a model''s response, - - including binary thumbs up/down ratings, numeric scores, free-text opinions, - - suggested rewrites, and structured category ratings. - - - Either `thumb` or `rating` should be provided (they are mutually exclusive), - but all - - feedback fields are optional to accommodate different feedback collection - patterns.' UserMessagesConfig: properties: embeddings_only: @@ -31545,71 +29100,6 @@ components: type: object title: UserMessagesConfig description: Configuration for how the user messages are interpreted. - UserRating: - properties: - thumb: - allOf: - - $ref: '#/components/schemas/ThumbDirection' - description: "Binary feedback: \"up\" for \U0001F44D or \"down\" for \U0001F44E\ - . Mutually exclusive with `rating`. Use this for simple thumbs up/down\ - \ UI elements." - rating: - title: Rating - description: Numeric rating (e.g., 1-5 stars) provided by the end user. - Mutually exclusive with `thumb`. Use this for star ratings or numeric - scales. - type: number - minimum: 0.0 - opinion: - title: Opinion - description: Free-text comment from the end user describing their opinion - of the response. - type: string - maxLength: 2000 - minLength: 1 - rewrite: - title: Rewrite - description: End-user's suggested text replacement for the generated response. - This is the user's idea of what the response should have been. - type: string - maxLength: 10000 - minLength: 1 - chosen_index: - title: Chosen Index - description: Zero-based index of the response option the user selected when - multiple responses were returned. Use this when showing users multiple - completion choices and tracking which one they picked. - type: integer - minimum: 0.0 - categories: - title: Categories - description: 'Application-specific category ratings as key-value pairs. - Use this for custom rating dimensions (e.g., {''helpfulness'': 4, ''accuracy'': - 5, ''tone'': ''professional''}). Useful for radio buttons, dropdowns, - or multi-dimensional rating systems.' - additionalProperties: - anyOf: - - type: number - - type: string - type: object - type: object - title: UserRating - description: 'User''s rating/evaluation of an AI response. - - - This captures various forms of end-user feedback about a model''s response, - including - - binary thumbs up/down ratings, numeric scores, free-text opinions, suggested - rewrites, - - and structured category ratings. - - - Either `thumb` or `rating` should be provided (they are mutually exclusive), - but all - - fields are optional to accommodate different feedback collection patterns.' ValidationError: properties: loc: diff --git a/sdk/stainless.yaml b/sdk/stainless.yaml index 95a4e4df43..aa5610797e 100644 --- a/sdk/stainless.yaml +++ b/sdk/stainless.yaml @@ -1023,73 +1023,6 @@ resources: intake: standalone_api: true subresources: - apps: - models: - app: App - app_filter: AppFilter - app_param: AppInput - app_sort_field: AppSortField - app_update: AppUpdate - apps_page: AppsPage - methods: - list: get /apis/intake/v2/workspaces/{workspace}/apps - create: post /apis/intake/v2/workspaces/{workspace}/apps - retrieve: get /apis/intake/v2/workspaces/{workspace}/apps/{name} - patch: patch /apis/intake/v2/workspaces/{workspace}/apps/{name} - delete: delete /apis/intake/v2/workspaces/{workspace}/apps/{name} - subresources: - tasks: - models: - task: Task - task_filter: TaskFilter - task_param: TaskInput - task_sort_field: TaskSortField - task_update: TaskUpdate - tasks_page: TasksPage - methods: - list: get /apis/intake/v2/workspaces/{workspace}/apps/{name}/tasks - create: post /apis/intake/v2/workspaces/{workspace}/apps/{name}/tasks - retrieve: get /apis/intake/v2/workspaces/{workspace}/apps/{app}/tasks/{name} - patch: patch /apis/intake/v2/workspaces/{workspace}/apps/{app}/tasks/{name} - delete: delete /apis/intake/v2/workspaces/{workspace}/apps/{app}/tasks/{name} - entries: - models: - entry: Entry - entry_context: EntryContext - entry_context_filter: EntryContextFilter - entry_data: EntryDataOutput - entry_data_param: EntryDataInput - entry_filter: EntryFilter - entry_param: EntryInput - entry_sort_field: EntrySortField - entry_update: EntryUpdate - entry_user_rating_filter: EntryUserRatingFilter - entrys_page: EntrysPage - evaluator_result_event: EvaluatorResultEvent - flexible_entry_request: FlexibleEntryRequestOutput - flexible_entry_request_param: FlexibleEntryRequestInput - flexible_entry_response: FlexibleEntryResponse - flexible_message: FlexibleMessage - message_role: MessageRole - reviewer_annotation_event: ReviewerAnnotationEvent - thumb_direction: ThumbDirection - usage: Usage - user_action_event: UserActionEvent - user_feedback_event: UserFeedbackEvent - user_rating: UserRating - methods: - list: get /apis/intake/v2/workspaces/{workspace}/entries - create: post /apis/intake/v2/workspaces/{workspace}/entries - retrieve: get /apis/intake/v2/workspaces/{workspace}/entries/{name} - patch: patch /apis/intake/v2/workspaces/{workspace}/entries/{name} - delete: delete /apis/intake/v2/workspaces/{workspace}/entries/{name} - subresources: - events: - models: - events_create_request: EventsCreateRequest - methods: - create: post /apis/intake/v2/workspaces/{workspace}/entries/{name}/events - delete: delete /apis/intake/v2/workspaces/{workspace}/entries/{entry}/events/{name} evaluator_results: models: evaluator_result: EvaluatorResult @@ -1125,28 +1058,6 @@ resources: list: get /apis/intake/v2/workspaces/{workspace}/annotations retrieve: get /apis/intake/v2/workspaces/{workspace}/annotations/{annotation_id} delete: delete /apis/intake/v2/workspaces/{workspace}/annotations/{annotation_id} - exports: - models: - export_config_param: ExportConfigInput - export_preview_request: ExportPreviewRequest - export_preview_response: ExportPreviewResponse - methods: - preview: post /apis/intake/v2/workspaces/{workspace}/export/preview - subresources: - jobs: - models: - export_config: ExportConfig - export_job: ExportJob - export_job_filter: ExportJobFilter - export_job_param: ExportJobInput - export_job_sort_field: ExportJobSortField - export_jobs_page: ExportJobsPage - export_status_details: ExportStatusDetails - job_status: JobStatus - methods: - list: get /apis/intake/v2/workspaces/{workspace}/export/jobs - create: post /apis/intake/v2/workspaces/{workspace}/export/jobs - retrieve: get /apis/intake/v2/workspaces/{workspace}/export/jobs/{name} ingest: models: evaluation_context: EvaluationContext diff --git a/services/intake/README.md b/services/intake/README.md index 10fd8947ae..fe7a0c54e6 100644 --- a/services/intake/README.md +++ b/services/intake/README.md @@ -1,405 +1,84 @@ # Intake Service -**The front door for LLM data in the NeMo Flywheel platform** +Intake is the telemetry ingestion and read API for NeMo Platform. It stores span +and trace data in ClickHouse, accepts OpenTelemetry traces, and supports +post-hoc annotations and evaluator result lookup. -## Overview +## API Surface -Intake is a microservice that stores LLM entries and user feedback, providing a stable HTTP API and normalized data model for the entire NeMo platform. It follows Entity Store patterns with async-first architecture, comprehensive filtering, and namespace-scoped resources. +Active v2 workspace endpoints: -## What it stores +- `GET /apis/intake/v2/workspaces/{workspace}/spans` +- `GET /apis/intake/v2/workspaces/{workspace}/spans/{span_id}` +- `GET /apis/intake/v2/workspaces/{workspace}/traces` +- `GET /apis/intake/v2/workspaces/{workspace}/traces/{id}` +- `GET /apis/intake/v2/workspaces/{workspace}/annotations` +- `POST /apis/intake/v2/workspaces/{workspace}/annotations` +- `DELETE /apis/intake/v2/workspaces/{workspace}/annotations/{annotation_id}` +- `GET /apis/intake/v2/workspaces/{workspace}/evaluator-results` +- `POST /apis/intake/v2/workspaces/{workspace}/ingest/otlp/v1/traces` +- `POST /apis/intake/v2/workspaces/{workspace}/ingest/chat-completions` +- `POST /apis/intake/v2/workspaces/{workspace}/ingest/atif` -- **Apps** - Applications that produce entries (namespace-scoped) -- **Tasks** - Specific tasks within apps (e.g., chat, completion) -- **Entries** - Full LLM interactions (request + response + context + feedback) -- **Events** - User feedback and actions on entries +Legacy apps, tasks, entries, events, and export-job endpoints have been removed. -## Quick Example - -```bash -# Store an entry (app and task will be auto-created if needed) -curl -X POST http://localhost:8080/v1/intake/entries \ - -H "Content-Type: application/json" \ - -d '{ - "external_id": "chatcmpl-abc123", - "namespace": "default", - "data": { - "request": { - "model": "gpt-4", - "messages": [{"role": "user", "content": "What is 2+2?"}] - }, - "response": { - "choices": [{"message": {"role": "assistant", "content": "4"}}] - } - }, - "context": { - "app": "default/my-app", - "task": "chat", - "thread_id": "conv_123" - } - }' - -# Get entry by external_id -curl http://localhost:8080/v1/intake/entries/external:chatcmpl-abc123 - -# Add user feedback -curl -X POST http://localhost:8080/v1/intake/entries/external:chatcmpl-abc123/events \ - -H "Content-Type: application/json" \ - -d '{ - "events": [{ - "event_type": "user_feedback", - "thumb": "up" - }] - }' -``` - -## API Endpoints - -### Apps - -- `GET /v1/intake/apps` - List apps with filter/search -- `POST /v1/intake/apps` - Create app -- `GET /v1/intake/apps/{namespace}/{app_name}` - Get app -- `PATCH /v1/intake/apps/{namespace}/{app_name}` - Update app -- `DELETE /v1/intake/apps/{namespace}/{app_name}` - Delete app - -### Tasks (sub-resources of Apps) - -- `GET /v1/intake/apps/{namespace}/{app_name}/tasks` - List tasks for app -- `POST /v1/intake/apps/{namespace}/{app_name}/tasks` - Create task -- `GET /v1/intake/apps/{namespace}/{app_name}/tasks/{task_name}` - Get task -- `PATCH /v1/intake/apps/{namespace}/{app_name}/tasks/{task_name}` - Update task -- `DELETE /v1/intake/apps/{namespace}/{app_name}/tasks/{task_name}` - Delete task - -### Entries - -- `GET /v1/intake/entries` - List/search entries with advanced filtering -- `POST /v1/intake/entries` - Create entry (auto-registers app/task if needed) -- `GET /v1/intake/entries/{entry_id}` - Get entry by ID -- `GET /v1/intake/entries/external:{external_id}` - Get entry by external_id -- `PATCH /v1/intake/entries/{entry_id}` - Update entry -- `DELETE /v1/intake/entries/{entry_id}` - Delete entry -- `POST /v1/intake/entries/{entry_id}/events` - Add events to entry -- `DELETE /v1/intake/entries/{entry_id}/events/{event_id}` - Delete event - -## Key Features - -- **Auto-registration**: Apps and tasks are automatically created when entries reference them -- **ID and external_id**: All entries get an auto-generated `id` (e.g., `entry-abc123`). You can optionally provide an `external_id` for your own reference IDs -- **external_id pattern**: Use `external:` prefix to reference entries by client-provided IDs (e.g., `/v1/intake/entries/external:chatcmpl-abc123`) -- **Namespace-scoped**: All resources are scoped by namespace for multi-tenancy -- **Advanced filtering**: Filter entries by namespace, app, task, thread_id, feedback, timestamps -- **Thread aggregation**: Use `longest_per_thread=true` filter to get only the longest entry per thread -- **Async-first**: All operations use async/await for better performance - -## Architecture - -- **Database**: PostgreSQL (primary storage) or SQLite (for testing) -- **Framework**: FastAPI with async endpoints -- **Persistence**: nmp_persistence EntityStorage interface -- **Data Model**: Entities defined in nmp_common -- **Deployment**: Docker container with uvicorn - -## Development +## Local Development Run these commands from the repository root unless a command says otherwise. Intake tests rely on shared platform test helpers, so use the root `uv` environment instead of package-scoped `uv run --package ...` commands. -### Intake Bootstrap - -```bash -make clean-python -make bootstrap-python PYTORCH_DEPS=cpu -make update-sdk -``` - -Run `make update-sdk` after Intake route, schema, OpenAPI, or Stainless changes. -It refreshes the OpenAPI output, Stainless config, web SDK, and generated CLI -surface. - -`make update-sdk` and the Stainless step in `make lint-fix` require -`STAINLESS_API_KEY`. If the key is missing or Stainless does not pull the -regenerated Python SDK, CLI generation will log missing -`nemo_platform.resources.intake` imports because the checked-in SDK is still -behind the updated `sdk/stainless.yaml`. - -### Test Intake - -```bash -make test-service SERVICE=intake -uv run --frozen pytest packages/nmp_platform_runner/tests -q -uv run --frozen pytest services/core/auth/tests/test_embedded_pdp.py -q -``` - -For a narrower Intake-only loop: - -```bash -uv run --frozen pytest \ - services/intake/tests/test_entries.py \ - services/intake/tests/test_exports.py \ - services/intake/tests/test_export_utils.py \ - -q -``` - -For a narrower registry/auth loop: - -```bash -uv run --frozen pytest \ - packages/nmp_platform_runner/tests/test_registry.py \ - packages/nmp_platform_runner/tests/test_config.py \ - services/core/auth/tests/test_embedded_pdp.py \ - -q -``` - -### Run Intake Locally - -Start the platform runner with Intake and the core services it depends on: - -```bash -uv run nemo services run \ - --services auth,entities,intake \ - --host 127.0.0.1 \ - --port 8080 -``` - -The bundled local runner config disables auth by default. For a quick API smoke -test: - -```bash -BASE=http://127.0.0.1:8080 - -curl -i -X POST "$BASE/apis/entities/v2/workspaces" \ - -H 'Content-Type: application/json' \ - -d '{"name":"default"}' - -curl -i -X POST "$BASE/apis/intake/v2/workspaces/default/apps" \ - -H 'Content-Type: application/json' \ - -d '{"name":"test-app","description":"Local test app"}' - -curl -i -X POST "$BASE/apis/intake/v2/workspaces/default/apps/test-app/tasks" \ - -H 'Content-Type: application/json' \ - -d '{"name":"chat","description":"Local chat task"}' - -curl -i -X POST "$BASE/apis/intake/v2/workspaces/default/entries" \ - -H 'Content-Type: application/json' \ - -d '{ - "external_id": "local-entry-1", - "data": { - "request": { - "model": "gpt-4", - "messages": [{"role": "user", "content": "What is 2+2?"}] - }, - "response": { - "choices": [{"message": {"role": "assistant", "content": "4"}}] - } - }, - "context": { - "app": "default/test-app", - "task": "chat", - "thread_id": "local-thread-1" - } - }' - -curl -i "$BASE/apis/intake/v2/workspaces/default/entries" -``` - -### Run Spans POC Locally - -Prerequisites: - -- Run from the repository root with the uv workspace synced. -- Docker must be running for the local ClickHouse container. -- The local service command below starts Auth, Entities, and Intake because Intake routes depend on platform auth context. - -Start ClickHouse for the spans POC: +Start a local ClickHouse container for span and trace storage: ```bash services/intake/scripts/spans/run_clickhouse.sh ``` -Intake can start without ClickHouse. In that mode the existing Intake routes remain available, while the spans/trace ingest routes return 503 until ClickHouse is running and the first trace request can initialize the schema. - -Run Intake, then send a sample OTLP trace: - -```bash -uv run nemo services run \ - --services auth,entities,intake \ - --host 127.0.0.1 \ - --port 8000 - -uv run services/intake/examples/send_otel_sample.py - -curl -i "http://127.0.0.1:8000/apis/intake/v2/workspaces/default/spans?filter[session_id]=sample-session" -``` - -Run a minimal LangChain agent with OpenInference instrumentation: - -```bash -export OPENAI_API_KEY="..." - -uv run --package nmp-intake \ - --with 'langchain>=1.0.0' \ - --with 'langchain-openai>=1.1.14' \ - --with 'openinference-instrumentation-langchain>=0.1.63' \ - services/intake/examples/send_langchain_openinference_agent.py \ - --endpoint "http://127.0.0.1:8000/apis/intake/v2/workspaces/default/ingest/otlp/v1/traces" - -curl -i "http://127.0.0.1:8000/apis/intake/v2/workspaces/default/spans?filter[session_id]=langchain-openinference-smoke" -``` - -### Test Permissions Locally - -Create a small auth-enabled local config: - -```bash -mkdir -p tmp -cat > tmp/nmp-intake-auth.yaml <<'YAML' -platform: - runtime: "docker" - base_url: "http://127.0.0.1:8080" - -auth: - enabled: true - policy_decision_point_provider: embedded - policy_decision_point_base_url: "http://127.0.0.1:8080" - policy_data_refresh_interval: 1 - bundle_cache_seconds: 0 - admin_email: "admin@example.com" - -entities: {} -intake: {} -YAML -``` - -Run the same local service set with that config: +Start Intake with the platform runner: ```bash uv run nemo services run \ --services auth,entities,intake \ - --config tmp/nmp-intake-auth.yaml \ --host 127.0.0.1 \ --port 8080 ``` -Seed a workspace and role bindings with a service principal: +Send a minimal OTLP trace after the service is running: ```bash -BASE=http://127.0.0.1:8080 -SERVICE='X-NMP-Principal-Id: service:local-test' - -curl -i -X POST "$BASE/apis/entities/v2/workspaces" \ - -H "$SERVICE" \ - -H 'Content-Type: application/json' \ - -d '{"name":"default"}' - -curl -i -X POST "$BASE/apis/auth/v2/iam/role-bindings?wait_role_propagation=false" \ - -H "$SERVICE" \ - -H 'Content-Type: application/json' \ - -d '{"principal":"viewer@test.com","workspace":"default","role":"Viewer"}' - -curl -i -X POST "$BASE/apis/auth/v2/iam/role-bindings?wait_role_propagation=false" \ - -H "$SERVICE" \ - -H 'Content-Type: application/json' \ - -d '{"principal":"editor@test.com","workspace":"default","role":"Editor"}' +uv run services/intake/examples/send_otel_sample.py ``` -Expected permission checks: +Read it back: ```bash -# Viewer can read Intake data in the workspace. -curl -i "$BASE/apis/intake/v2/workspaces/default/entries" \ - -H 'X-NMP-Principal-Id: viewer@test.com' - -# Viewer cannot create Intake data. -curl -i -X POST "$BASE/apis/intake/v2/workspaces/default/entries" \ - -H 'X-NMP-Principal-Id: viewer@test.com' \ - -H 'Content-Type: application/json' \ - -d '{"data":{"request":{"messages":[]},"response":{"choices":[]}},"context":{"app":"default/test-app","task":"chat"}}' - -# Editor can create Intake data. -curl -i -X POST "$BASE/apis/intake/v2/workspaces/default/entries" \ - -H 'X-NMP-Principal-Id: editor@test.com' \ - -H 'Content-Type: application/json' \ - -d '{"data":{"request":{"messages":[]},"response":{"choices":[]}},"context":{"app":"default/test-app","task":"chat"}}' - -# The path workspace is authoritative over query filters. -curl --globoff -i "$BASE/apis/intake/v2/workspaces/default/entries?filter[workspace]=other" \ - -H 'X-NMP-Principal-Id: viewer@test.com' +curl -i "http://127.0.0.1:8080/apis/intake/v2/workspaces/default/spans?filter[session_id]=sample-session" ``` -The Studio Intake UI remains disabled by default in -`web/packages/studio/src/constants/environment.ts`. When UI exposure is ready, -wire `INTAKE_ENABLED` back to `featureFlags.intakeEnabled` and use -`VITE_FF_INTAKE_ENABLED=true` with `VITE_INTAKE_MICROSERVICE_URL` pointing at -the local platform runner. - -### Studio and Azure Scopes +## Testing -The shared Azure app registration currently exposes the broad platform scopes, -not the Intake-specific scopes. Keep local Studio auth scopes at: +Focused route-surface test: ```bash -VITE_AUTH_SCOPES="platform:read platform:write openid profile email offline_access" +uv run --frozen pytest services/intake/tests/integration/test_intake.py -q ``` -Do not add `intake:read` or `intake:write` to local Studio env files or shared -defaults until the Azure app registration for -`api://e9174c91-5abf-4e3c-acd5-8d78bd971a30` exposes those scopes and admin -consent has been granted. If Studio redirects back to `/auth/success` with an -`AADSTS65005` error saying `intake:read` does not exist, remove the Intake -scopes from `web/packages/studio/env/.env.dev.local`, restart the Vite dev -server, and clear browser auth storage before logging in again. - -The backend authorization config allows both forms for Intake endpoints: - -- `platform:read` or `intake:read` for read/list endpoints -- `platform:write` or `intake:write` for create/update/delete endpoints - -### TODOs - -- Keep Studio Intake disabled by default until the v2 Intake UI path is ready. -- Before enabling `intake:read` and `intake:write` in Studio defaults, add both - scopes to the Azure app registration and grant consent. -- After Azure scopes exist, test Studio login with Intake enabled and verify - Viewer/Editor role behavior against `/apis/intake/v2/workspaces/{workspace}`. +Focused ingest/read tests: ```bash -# Install dependencies (from repo root) -uv sync --dev - -# Run tests -cd services/intake -pytest tests/test_api_refactored.py -v - -# Start postgres database -docker-compose up -d postgres - -# Start service -export POSTGRES_USER=nemo_user -export POSTGRES_PASSWORD=nemo_password -export POSTGRES_HOST=localhost -export POSTGRES_PORT=5432 -export POSTGRES_DB=nemo_db -python src/entrypoint.py +uv run --frozen pytest \ + services/intake/tests/integration/spans/test_chat_completions_ingest.py \ + services/intake/tests/test_atif_v17.py \ + -q ``` -## API Documentation - -The full API specification is available at `/docs` when the service is running (e.g., http://localhost:8080/docs). - -## Testing - -Run the comprehensive test suite: +Run the full Intake service test suite: ```bash -pytest tests/test_api_refactored.py -v +make test-service SERVICE=intake ``` -Tests cover: +## Generated API Artifacts -- Apps CRUD operations (6 tests) -- Tasks CRUD operations (5 tests) -- Entries CRUD operations (8 tests) -- Auto-registration of apps/tasks -- longest_per_thread filtering -- Health checks +Run `make refresh-openapi` after Intake route or schema changes. The Stainless +resource config lives in `sdk/stainless.yaml`. diff --git a/services/intake/alembic.ini b/services/intake/alembic.ini deleted file mode 100644 index 9cbb8a4f1a..0000000000 --- a/services/intake/alembic.ini +++ /dev/null @@ -1,120 +0,0 @@ -# A generic, single database configuration. - -[alembic] -# path to migration scripts -# Use forward slashes (/) also on windows to provide an os agnostic path -script_location = alembic - -# template used to generate migration file names; The default value is %%(rev)s_%%(slug)s -# Uncomment the line below if you want the files to be prepended with date and time -# see https://alembic.sqlalchemy.org/en/latest/tutorial.html#editing-the-ini-file -# for all available tokens -file_template = %%(year)d_%%(month).2d_%%(day).2d_%%(hour).2d%%(minute).2d-%%(rev)s_%%(slug)s - -# sys.path path, will be prepended to sys.path if present. -# defaults to the current working directory. -prepend_sys_path = . - -# timezone to use when rendering the date within the migration file -# as well as the filename. -# If specified, requires the Python zoneinfo module and tzdata library. -# Any required deps can installed by adding `alembic[tz]` to the pip requirements -# string value is passed to ZoneInfo() -# leave blank for localtime -# timezone = - -# max length of characters to apply to the "slug" field -# truncate_slug_length = 40 - -# set to 'true' to run the environment during -# the 'revision' command, regardless of autogenerate -# revision_environment = false - -# set to 'true' to allow .pyc and .pyo files without -# a source .py file to be detected as revisions in the -# versions/ directory -# sourceless = false - -# version location specification; This defaults -# to alembic/versions. When using multiple version -# directories, initial revisions must be specified with --version-path. -# The path separator used here should be the separator specified by "version_path_separator" below. -# version_locations = %(here)s/bar:%(here)s/bat:alembic/versions - -# version path separator; As mentioned above, this is the character used to split -# version_locations. The default within new alembic.ini files is "os", which uses os.pathsep. -# If this key is omitted entirely, it falls back to the legacy behavior of splitting on spaces and/or commas. -# Valid values for version_path_separator are: -# -# version_path_separator = : -# version_path_separator = ; -# version_path_separator = space -# version_path_separator = newline -# -# Use os.pathsep. Default configuration used for new projects. -version_path_separator = os - -# set to 'true' to search source files recursively -# in each "version_locations" directory -# new in Alembic version 1.10 -# recursive_version_locations = false - -# the output encoding used when revision files -# are written from script.py.mako -# output_encoding = utf-8 - -# Use environment variable for database URL -# This will be set programmatically in env.py -# sqlalchemy.url = - -[post_write_hooks] -# post_write_hooks defines scripts or Python functions that are run -# on newly generated revision scripts. See the documentation for further -# detail and examples - -# format using "black" - use the console_scripts runner, against the "black" entrypoint -# hooks = black -# black.type = console_scripts -# black.entrypoint = black -# black.options = -l 79 REVISION_SCRIPT_FILENAME - -# lint with attempts to fix using "ruff" - use the exec runner, execute a binary -# hooks = ruff -# ruff.type = exec -# ruff.executable = %(here)s/.venv/bin/ruff -# ruff.options = --fix REVISION_SCRIPT_FILENAME - -# Logging configuration -[loggers] -keys = root,sqlalchemy,alembic - -[handlers] -keys = console - -[formatters] -keys = generic - -[logger_root] -level = WARNING -handlers = console -qualname = - -[logger_sqlalchemy] -level = WARNING -handlers = -qualname = sqlalchemy.engine - -[logger_alembic] -level = INFO -handlers = -qualname = alembic - -[handler_console] -class = StreamHandler -args = (sys.stderr,) -level = NOTSET -formatter = generic - -[formatter_generic] -format = %(levelname)-5.5s [%(name)s] %(message)s -datefmt = %H:%M:%S diff --git a/services/intake/alembic/README b/services/intake/alembic/README deleted file mode 100644 index 98e4f9c44e..0000000000 --- a/services/intake/alembic/README +++ /dev/null @@ -1 +0,0 @@ -Generic single-database configuration. \ No newline at end of file diff --git a/services/intake/alembic/env.py b/services/intake/alembic/env.py deleted file mode 100644 index c26e6c3f2b..0000000000 --- a/services/intake/alembic/env.py +++ /dev/null @@ -1,98 +0,0 @@ -# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. -# SPDX-License-Identifier: Apache-2.0 - -import os -import sys -from logging.config import fileConfig - -from dotenv import load_dotenv - -# Load environment variables from .env file -load_dotenv() - -from alembic import context # noqa: E402 -from sqlalchemy import engine_from_config, pool # noqa: E402 -from sqlmodel import SQLModel # noqa: E402 - -# Add the src directory to the path so we can import our modules -sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "src")) - -# Import SQLModel Row types so Alembic can discover them for autogeneration -from api.db import get_pg_conn_string # noqa: E402 -from nmp_persistence.postgres.models.intake import ( # noqa: E402 - AppRow, # noqa: F401 - EntryRow, # noqa: F401 - ExportJobRow, # noqa: F401 - TaskRow, # noqa: F401 -) # noqa: E402 - -# this is the Alembic Config object, which provides -# access to the values within the .ini file in use. -config = context.config - -# Set the database URL from environment variable using the same function as the app -config.set_main_option("sqlalchemy.url", get_pg_conn_string()) - -# Interpret the config file for Python logging. -# This line sets up loggers basically. -if config.config_file_name is not None: - fileConfig(config.config_file_name) - -# add your model's MetaData object here -# for 'autogenerate' support -target_metadata = SQLModel.metadata - -# other values from the config, defined by the needs of env.py, -# can be acquired: -# my_important_option = config.get_main_option("my_important_option") -# ... etc. - - -def run_migrations_offline() -> None: - """Run migrations in 'offline' mode. - - This configures the context with just a URL - and not an Engine, though an Engine is acceptable - here as well. By skipping the Engine creation - we don't even need a DBAPI to be available. - - Calls to context.execute() here emit the given string to the - script output. - - """ - url = config.get_main_option("sqlalchemy.url") - context.configure( - url=url, - target_metadata=target_metadata, - literal_binds=True, - dialect_opts={"paramstyle": "named"}, - ) - - with context.begin_transaction(): - context.run_migrations() - - -def run_migrations_online() -> None: - """Run migrations in 'online' mode. - - In this scenario we need to create an Engine - and associate a connection with the context. - - """ - connectable = engine_from_config( - config.get_section(config.config_ini_section, {}), - prefix="sqlalchemy.", - poolclass=pool.NullPool, - ) - - with connectable.connect() as connection: - context.configure(connection=connection, target_metadata=target_metadata) - - with context.begin_transaction(): - context.run_migrations() - - -if context.is_offline_mode(): - run_migrations_offline() -else: - run_migrations_online() diff --git a/services/intake/alembic/script.py.mako b/services/intake/alembic/script.py.mako deleted file mode 100644 index fbc4b07dce..0000000000 --- a/services/intake/alembic/script.py.mako +++ /dev/null @@ -1,26 +0,0 @@ -"""${message} - -Revision ID: ${up_revision} -Revises: ${down_revision | comma,n} -Create Date: ${create_date} - -""" -from typing import Sequence, Union - -from alembic import op -import sqlalchemy as sa -${imports if imports else ""} - -# revision identifiers, used by Alembic. -revision: str = ${repr(up_revision)} -down_revision: Union[str, None] = ${repr(down_revision)} -branch_labels: Union[str, Sequence[str], None] = ${repr(branch_labels)} -depends_on: Union[str, Sequence[str], None] = ${repr(depends_on)} - - -def upgrade() -> None: - ${upgrades if upgrades else "pass"} - - -def downgrade() -> None: - ${downgrades if downgrades else "pass"} diff --git a/services/intake/alembic/versions/2025_10_14_1101-a7e3b1e7355b_initial_migration.py b/services/intake/alembic/versions/2025_10_14_1101-a7e3b1e7355b_initial_migration.py deleted file mode 100644 index bc55629dfa..0000000000 --- a/services/intake/alembic/versions/2025_10_14_1101-a7e3b1e7355b_initial_migration.py +++ /dev/null @@ -1,124 +0,0 @@ -# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. -# SPDX-License-Identifier: Apache-2.0 - -"""initial_migration - -Revision ID: a7e3b1e7355b -Revises: -Create Date: 2025-10-14 11:01:23.029457 - -""" - -from typing import Sequence, Union - -import sqlalchemy as sa -import sqlmodel -from alembic import op - -# revision identifiers, used by Alembic. -revision: str = "a7e3b1e7355b" -down_revision: Union[str, None] = None -branch_labels: Union[str, Sequence[str], None] = None -depends_on: Union[str, Sequence[str], None] = None - - -def upgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - op.create_table( - "intake_apps", - sa.Column("type_prefix", sqlmodel.sql.sqltypes.AutoString(), nullable=True), - sa.Column("locked", sa.Boolean(), nullable=False), - sa.Column("name", sqlmodel.sql.sqltypes.AutoString(), nullable=False), - sa.Column("schema_version", sqlmodel.sql.sqltypes.AutoString(), nullable=False), - sa.Column("id", sqlmodel.sql.sqltypes.AutoString(), nullable=False), - sa.Column("description", sqlmodel.sql.sqltypes.AutoString(), nullable=True), - sa.Column("namespace", sqlmodel.sql.sqltypes.AutoString(), nullable=True), - sa.Column("project", sqlmodel.sql.sqltypes.AutoString(), nullable=True), - sa.Column("created_at", sa.DateTime(), nullable=False), - sa.Column("updated_at", sa.DateTime(), nullable=False), - sa.Column("custom_fields", sa.JSON(), nullable=True), - sa.Column("ownership", sa.JSON(), nullable=True), - sa.PrimaryKeyConstraint("id"), - sa.UniqueConstraint("namespace", "name"), - ) - op.create_table( - "intake_entries", - sa.Column("type_prefix", sqlmodel.sql.sqltypes.AutoString(), nullable=True), - sa.Column("external_id", sqlmodel.sql.sqltypes.AutoString(), nullable=True), - sa.Column("data", sa.JSON(), nullable=True), - sa.Column("context", sa.JSON(), nullable=True), - sa.Column("user_rating", sa.JSON(), nullable=True), - sa.Column("events", sa.JSON(), nullable=True), - sa.Column("schema_version", sqlmodel.sql.sqltypes.AutoString(), nullable=False), - sa.Column("id", sqlmodel.sql.sqltypes.AutoString(), nullable=False), - sa.Column("description", sqlmodel.sql.sqltypes.AutoString(), nullable=True), - sa.Column("namespace", sqlmodel.sql.sqltypes.AutoString(), nullable=True), - sa.Column("project", sqlmodel.sql.sqltypes.AutoString(), nullable=True), - sa.Column("created_at", sa.DateTime(), nullable=False), - sa.Column("updated_at", sa.DateTime(), nullable=False), - sa.Column("custom_fields", sa.JSON(), nullable=True), - sa.Column("ownership", sa.JSON(), nullable=True), - sa.PrimaryKeyConstraint("id"), - sa.UniqueConstraint("external_id", name="uq_entry_external_id"), - ) - op.create_table( - "intake_export_jobs", - sa.Column("type_prefix", sqlmodel.sql.sqltypes.AutoString(), nullable=True), - sa.Column("config", sa.JSON(), nullable=True), - sa.Column("output_file_url", sa.String(), nullable=True), - sa.Column("status_details", sa.JSON(), nullable=True), - sa.Column( - "status", - sa.Enum( - "CREATED", - "PENDING", - "RUNNING", - "CANCELLED", - "CANCELLING", - "FAILED", - "COMPLETED", - "READY", - "UNKNOWN", - name="jobstatus", - ), - nullable=False, - ), - sa.Column("schema_version", sqlmodel.sql.sqltypes.AutoString(), nullable=False), - sa.Column("id", sqlmodel.sql.sqltypes.AutoString(), nullable=False), - sa.Column("description", sqlmodel.sql.sqltypes.AutoString(), nullable=True), - sa.Column("namespace", sqlmodel.sql.sqltypes.AutoString(), nullable=True), - sa.Column("project", sqlmodel.sql.sqltypes.AutoString(), nullable=True), - sa.Column("created_at", sa.DateTime(), nullable=False), - sa.Column("updated_at", sa.DateTime(), nullable=False), - sa.Column("custom_fields", sa.JSON(), nullable=True), - sa.Column("ownership", sa.JSON(), nullable=True), - sa.PrimaryKeyConstraint("id"), - ) - op.create_table( - "intake_tasks", - sa.Column("type_prefix", sqlmodel.sql.sqltypes.AutoString(), nullable=True), - sa.Column("app", sqlmodel.sql.sqltypes.AutoString(), nullable=False), - sa.Column("locked", sa.Boolean(), nullable=False), - sa.Column("name", sqlmodel.sql.sqltypes.AutoString(), nullable=False), - sa.Column("schema_version", sqlmodel.sql.sqltypes.AutoString(), nullable=False), - sa.Column("id", sqlmodel.sql.sqltypes.AutoString(), nullable=False), - sa.Column("description", sqlmodel.sql.sqltypes.AutoString(), nullable=True), - sa.Column("namespace", sqlmodel.sql.sqltypes.AutoString(), nullable=True), - sa.Column("project", sqlmodel.sql.sqltypes.AutoString(), nullable=True), - sa.Column("created_at", sa.DateTime(), nullable=False), - sa.Column("updated_at", sa.DateTime(), nullable=False), - sa.Column("custom_fields", sa.JSON(), nullable=True), - sa.Column("ownership", sa.JSON(), nullable=True), - sa.PrimaryKeyConstraint("id"), - sa.UniqueConstraint("namespace", "app", "name"), - ) - # ### end Alembic commands ### - - -def downgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - op.drop_table("intake_tasks") - op.drop_table("intake_export_jobs") - op.drop_table("intake_entries") - op.drop_table("intake_apps") - # ### end Alembic commands ### diff --git a/services/intake/pyproject.toml b/services/intake/pyproject.toml index d0e30841df..586ef0e047 100644 --- a/services/intake/pyproject.toml +++ b/services/intake/pyproject.toml @@ -1,7 +1,7 @@ [project] name = "nmp-intake" version = "0.0.1" -description = "Intake service for NeMo Platform - collecting, enriching, and triaging logs, entries, and events" +description = "Intake service for NeMo Platform - ingesting and reading spans, traces, annotations, and evaluator results" readme = "README.md" requires-python = ">=3.11,<3.14" @@ -15,30 +15,9 @@ dependencies = [ # Intake has no separate task containers - all deps are API deps "fastapi>=0.115.5, <1.0.0", "uvicorn>=0.22.0, <1.0.0", - "sqlalchemy>=2.0", - "alembic>=1.10.4, <2.0.0", "pydantic>=2.9.2, <3.0.0", "pydantic-settings>=2.6.1, <3.0.0", - "asyncpg>=0.30.0, <1.0.0", - "python-dotenv>=1.0.0, <2.0.0", - "httpx>=0.24.1, <1.0.0", - "huggingface-hub>=1.0.1, <2.0.0", - "openai>=1.51.0", - "requests>=2.32.3, <3.0.0", - "types-requests>=2.32.0.20241016, <3.0.0.0", - "pyyaml>=6.0.2, <7.0.0", - "base58>=2.1.1", "nmp-common", - "opentelemetry-api>=1.27.0", - "opentelemetry-instrumentation-fastapi>=0.48b0", - "opentelemetry-sdk>=1.27.0", - "opentelemetry-exporter-otlp>=1.27.0", - "opentelemetry-instrumentation-openai>=0.33.9", - "psycopg2-binary>=2.9.10", - "celery>=5.5.3", - "redis>=5.0.1", - "flower>=2.0.1", - "watchdog[watchmedo]>=6.0.0", "clickhouse-connect>=0.7,<1.0", "opentelemetry-proto>=1.27.0", ] @@ -51,19 +30,14 @@ intake-server = "nmp.intake.main:run_standalone" dev = [ "nmp-testing", - "psycopg[binary,pool]>=3.2.4", - "psycopg2-binary>=2.9.10", - "pytest-postgresql>=6.1.1, <7.0.0", "pytest>=9.0.3", "pytest-asyncio>=0.21.0, <2.0.0", "pytest-env>=1.1.5, <2.0.0", "flake8>=6.1.0, <7.0.0", "isort>=5.12.0, <6.0.0", "mypy>=1.13.0, <2.0.0", - "types-requests>=2.31.0, <3.0.0", "pre-commit>=3.7.1, <4.0.0", "setuptools>=78.1.1", - "testcontainers[postgres]>=4.12.0", "testcontainers[clickhouse]>=4.12.0", ] diff --git a/services/intake/scripts/README.md b/services/intake/scripts/README.md deleted file mode 100644 index 2f85457347..0000000000 --- a/services/intake/scripts/README.md +++ /dev/null @@ -1,67 +0,0 @@ -# Intake Scripts - -This directory contains utility scripts for the Intake service. - -## seed-entries.py - -A script to seed the database with test entry records. Supports two modes: - -### API Mode (Default) - -Makes HTTP requests to the Intake service API. This is the recommended approach as it ensures tasks and clients are properly created through the server's business logic. - -**Usage:** -```bash -# Use default localhost endpoint -python -m scripts.seed-entries - -# Use custom API endpoint -python -m scripts.seed-entries --api-url https://your-api-endpoint.com/v1/intake/entries - -# Custom count and timeout -python -m scripts.seed-entries --count 25 --timeout 60 - -# Verbose logging -python -m scripts.seed-entries --verbose -``` - -### Database Mode - -Directly inserts records into the database, bypassing API server logic. This mode is legacy and not recommended as it doesn't create tasks and clients properly. - -**Prerequisites:** -- `cd` into `services/intake` -- Intake is running in a container via `docker-compose -f docker-compose.test.yaml up --build` - -**Usage:** -```bash -# Run inside the container -docker exec -it nemo-intake python -m scripts.seed-entries --mode database - -# Or with custom count -docker exec -it nemo-intake python -m scripts.seed-entries --mode database --count 100 -``` - -### Command Line Options - -- `--mode`: Choose between `api` (default) or `database` -- `--api-url`: API endpoint URL (default: `http://localhost:8000/v1/intake/entries`) -- `--count`: Number of entries to create (default: 50) -- `--timeout`: HTTP timeout in seconds for API mode (default: 30) -- `--verbose`: Enable verbose logging - -### Examples - -```bash -# API mode with default localhost endpoint (recommended) -python -m scripts.seed-entries - -# API mode with 100 entries -python -m scripts.seed-entries --count 100 - -# API mode with custom endpoint and verbose logging -python -m scripts.seed-entries --api-url https://staging.example.com/v1/intake/entries --count 25 --verbose - -# Database mode (legacy) -docker exec -it nemo-intake python -m scripts.seed-entries --mode database --count 100 -``` \ No newline at end of file diff --git a/services/intake/scripts/__init__.py b/services/intake/scripts/__init__.py deleted file mode 100644 index f70e6ca3c8..0000000000 --- a/services/intake/scripts/__init__.py +++ /dev/null @@ -1,9 +0,0 @@ -# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. -# SPDX-License-Identifier: Apache-2.0 - -"""Test data scripts for the Intake API. - -This package contains scripts for generating and loading test data: -- generate_test_dataset.py: Creates realistic entry data for testing -- load_test_data.py: Loads the generated data via HTTP requests to the API -""" diff --git a/services/intake/scripts/generate_test_dataset.py b/services/intake/scripts/generate_test_dataset.py deleted file mode 100644 index c4ad07873a..0000000000 --- a/services/intake/scripts/generate_test_dataset.py +++ /dev/null @@ -1,696 +0,0 @@ -#!/usr/bin/env python3 -# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. -# SPDX-License-Identifier: Apache-2.0 - -"""Generate comprehensive test dataset for Intake API. - -This script creates a realistic dataset of ~100 entry records across multiple -scenarios to test and demonstrate the Intake system capabilities. - -Dataset Overview: -- Single-turn entries across multiple tasks and timeframes -- Multi-turn conversations (threads) with various event types -- User feedback events (thumbs up/down, ratings, opinions) -- User action events (sharing, copying, etc.) -- Events targeting different message parts (responses, assistant messages) - -The generated data demonstrates realistic AI interaction patterns and provides -comprehensive test coverage for the export and search functionality. -""" - -from __future__ import annotations - -import argparse -import json -import random -import uuid -from dataclasses import dataclass -from pathlib import Path -from typing import Any, Dict, List, Optional, Tuple, TypedDict - -# --------------------------------------------------------------------------- -# Configuration and Utilities -# --------------------------------------------------------------------------- - -DEFAULT_WORKSPACE = "default" - -DEFAULT_OUTPUT_DIR = Path(__file__).resolve().parents[3] / ".tmp" / "intake" / "test_data" - - -class Conversation(TypedDict): - """Multi-turn conversation fixture: one app/task with a sequence of (prompt, response) turns.""" - - app_id: str - task_id: str - turns: List[Tuple[str, str]] - - -class Scenario(TypedDict): - """Single-turn scenario fixture with attached events (feedback or actions).""" - - prompt: str - response: str - events: List[Dict[str, Any]] - - -# Task definitions for realistic scenarios -TASKS = { - "customer-support": "AI-powered customer service chat assistant", - "code-generation": "Software development code entry and generation", - "content-writing": "Marketing content and blog post generation", - "data-analysis": "Business intelligence and data insights generation", - "legal-research": "Legal document analysis and research assistance", - "medical-qa": "Medical information and diagnostic assistance", - "education": "Educational content and tutoring assistance", - "translation": "Multi-language translation and localization", -} - -CLIENT_IDS = ["acme-corp", "globodyne", "initech", "hooli", "pied-piper"] - - -def generate_entry_id() -> str: - """Generate realistic entry ID.""" - return f"comp_{uuid.uuid4().hex[:12]}" - - -def generate_thread_id() -> str: - """Generate realistic thread ID.""" - return f"thread_{uuid.uuid4().hex[:8]}" - - -@dataclass -class EntryDraft: - """In-memory representation of a v2 EntryInput payload.""" - - workspace: str - external_id: str - model: str - messages: List[Dict[str, Any]] - response_text: str - usage: Dict[str, Any] - app_id: str - task_id: str - thread_id: Optional[str] = None - events: Optional[List[Dict[str, Any]]] = None - - def to_dict(self) -> Dict[str, Any]: - """Serialize to the JSON shape accepted by POST /entries.""" - entry: Dict[str, Any] = { - "external_id": self.external_id, - "data": { - "request": {"model": self.model, "messages": self.messages}, - "response": { - "choices": [ - { - "message": {"role": "assistant", "content": self.response_text}, - "finish_reason": "stop", - } - ], - "usage": self.usage, - }, - }, - "context": { - "app": f"{self.workspace}/{self.app_id}", - "task": self.task_id, - "thread_id": self.thread_id, - }, - } - if self.events: - entry["events"] = self.events - return entry - - -# --------------------------------------------------------------------------- -# Single-Turn Entry Generators -# --------------------------------------------------------------------------- - - -def create_single_turn_entries(workspace: str) -> List[Dict[str, Any]]: - """ - Create 40 single-turn entries across multiple tasks and clients. - - These represent standalone AI interactions without follow-up conversations. - Each entry has: - - Random app_id and task_id - - Realistic prompts and responses for the task type - - Timestamps spread across the last 30 days - - No events (pure entry data) - - Returns list of entry payloads ready for POST /entries - """ - entries = [] - - # Templates for different task types - scenarios = [ - ( - "customer-support", - "How do I reset my password?", - "I can help you reset your password. Please visit the account settings page and click 'Forgot Password'. You'll receive a reset link via email within 5 minutes.", - ), - ( - "customer-support", - "My order hasn't arrived yet, can you help?", - "Let me check on your order status. I see it was shipped yesterday and should arrive within 2-3 business days. Here's your tracking number: ABC123456789.", - ), - ( - "customer-support", - "I'm having trouble with the mobile app", - "I understand the app issues you're experiencing. Try clearing the cache and data, then restart the app. If the problem persists, please update to the latest version.", - ), - ( - "code-generation", - "Write a Python function to validate email addresses", - "```python\nimport re\n\ndef validate_email(email):\n pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$'\n return re.match(pattern, email) is not None\n```", - ), - ( - "code-generation", - "Create a REST API endpoint for user authentication", - "```python\nfrom flask import Flask, request, jsonify\nfrom werkzeug.security import check_password_hash\n\n@app.route('/auth', methods=['POST'])\ndef authenticate():\n data = request.get_json()\n # Authentication logic here\n return jsonify({'token': 'jwt_token_here'})\n```", - ), - ( - "content-writing", - "Write a blog post about sustainable living", - "# Sustainable Living: Small Changes, Big Impact\n\nIn today's world, making environmentally conscious choices has never been more important. Here are 10 simple ways to reduce your carbon footprint and contribute to a healthier planet...", - ), - ( - "content-writing", - "Create social media copy for a new product launch", - "🚀 Introducing our revolutionary new product! Experience the future of innovation with cutting-edge technology that transforms how you work and play. #Innovation #TechLaunch #GameChanger", - ), - ( - "data-analysis", - "Analyze customer churn patterns in our dataset", - "Based on the analysis, customer churn is highest in months 3-6 after signup (28% churn rate). Key factors include lack of feature usage (45% correlation) and poor onboarding entry (62% of churned users didn't complete setup).", - ), - ( - "data-analysis", - "Create a dashboard showing sales performance metrics", - "The sales dashboard reveals a 15% increase in Q3 performance with strongest growth in the enterprise segment (34% YoY). Regional breakdown shows North America leading at $2.3M, followed by Europe at $1.8M.", - ), - ( - "legal-research", - "Research trademark infringement laws", - "Trademark infringement occurs when a party uses a mark that is identical or confusingly similar to a registered trademark. Key factors include: likelihood of confusion, strength of the mark, and similarity of goods/services.", - ), - ( - "medical-qa", - "Explain the symptoms of diabetes", - "Common diabetes symptoms include frequent urination, excessive thirst, unexplained weight loss, fatigue, and blurred vision. If you experience these symptoms, consult a healthcare provider for proper evaluation and testing.", - ), - ( - "education", - "Explain photosynthesis to a middle school student", - "Photosynthesis is like a plant's way of making food using sunlight! Plants take in carbon dioxide from the air, water from their roots, and use sunlight energy to create glucose (sugar) and release oxygen.", - ), - ( - "translation", - "Translate 'Hello, how are you?' to Spanish", - "The translation of 'Hello, how are you?' to Spanish is: 'Hola, ¿cómo estás?' (informal) or 'Hola, ¿cómo está usted?' (formal).", - ), - ] - - for i in range(40): - app_id = random.choice(CLIENT_IDS) - task_key = random.choice(list(TASKS.keys())) - - # Pick a scenario that matches this task or use a generic one - matching_scenarios = [s for s in scenarios if s[0] == task_key] - if matching_scenarios: - _, prompt, response = random.choice(matching_scenarios) - else: - prompt = f"Generic prompt for {task_key} task" - response = f"Generic response for {task_key}" - - prompt_tokens = random.randint(20, 100) - completion_tokens = random.randint(50, 200) - entries.append( - EntryDraft( - workspace=workspace, - external_id=generate_entry_id(), - model="llama-3.1-8b-instruct", - messages=[{"role": "user", "content": prompt}], - response_text=response, - usage={ - "prompt_tokens": prompt_tokens, - "completion_tokens": completion_tokens, - "total_tokens": prompt_tokens + completion_tokens, - }, - app_id=app_id, - task_id=task_key, - thread_id=None, - ).to_dict() - ) - - return entries - - -def create_conversation_threads(workspace: str) -> List[Dict[str, Any]]: - """ - Create multi-turn conversation threads with realistic back-and-forth. - - These represent ongoing conversations where users have follow-up questions. - Each thread contains: - - 3-4 turns of conversation - - Realistic conversation flow for the task type - - Events targeting different parts of the conversation - - Consistent thread_id across all turns - - Returns list of entry payloads ready for POST /entries - """ - entries = [] - - # Conversation scenarios - conversations: List[Conversation] = [ - { - "app_id": "acme-corp", - "task_id": "customer-support", - "turns": [ - ( - "I'm having trouble logging into my account", - "I can help you with that login issue. Can you tell me what error message you're seeing?", - ), - ( - "It says 'invalid credentials' but I'm sure my password is correct", - "That error usually means either the email or password is incorrect. Let's try resetting your password to be sure. I'll send you a reset link.", - ), - ( - "I got the email and reset it, but it's still not working", - "I see the issue now - your account was temporarily locked due to multiple failed attempts. I've unlocked it for you. Please try logging in again with your new password.", - ), - ], - }, - { - "app_id": "hooli", - "task_id": "code-generation", - "turns": [ - ( - "I need to create a user registration API endpoint", - "I'll help you create a user registration endpoint. Here's a basic structure:\n\n```python\nfrom flask import Flask, request, jsonify\nfrom werkzeug.security import generate_password_hash\n\n@app.route('/register', methods=['POST'])\ndef register():\n data = request.get_json()\n email = data.get('email')\n password = data.get('password')\n \n # Hash password and save user\n hashed_password = generate_password_hash(password)\n # Save to database...\n \n return jsonify({'message': 'User created successfully'}), 201\n```", - ), - ( - "This looks good, but I also need email validation and duplicate checking", - "Great point! Let me enhance that with proper validation:\n\n```python\nimport re\nfrom sqlalchemy.exc import IntegrityError\n\ndef validate_email(email):\n pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$'\n return re.match(pattern, email) is not None\n\n@app.route('/register', methods=['POST'])\ndef register():\n data = request.get_json()\n email = data.get('email')\n password = data.get('password')\n \n # Validate input\n if not email or not password:\n return jsonify({'error': 'Email and password required'}), 400\n \n if not validate_email(email):\n return jsonify({'error': 'Invalid email format'}), 400\n \n # Check for existing user\n existing_user = User.query.filter_by(email=email).first()\n if existing_user:\n return jsonify({'error': 'User already exists'}), 409\n \n # Create user...\n```", - ), - ( - "Perfect! Can you also add password strength validation?", - "Absolutely! Here's a password strength validator:\n\n```python\ndef validate_password_strength(password):\n if len(password) < 8:\n return False, 'Password must be at least 8 characters long'\n if not re.search(r'[A-Z]', password):\n return False, 'Password must contain at least one uppercase letter'\n if not re.search(r'[a-z]', password):\n return False, 'Password must contain at least one lowercase letter'\n if not re.search(r'\\d', password):\n return False, 'Password must contain at least one number'\n if not re.search(r'[!@#$%^&*(),.?\":{}|<>]', password):\n return False, 'Password must contain at least one special character'\n return True, 'Password is strong'\n\n# Add this validation before creating the user:\nis_strong, message = validate_password_strength(password)\nif not is_strong:\n return jsonify({'error': message}), 400\n```", - ), - ], - }, - { - "app_id": "globodyne", - "task_id": "data-analysis", - "turns": [ - ( - "Can you help me analyze our sales data trends?", - "I'd be happy to help analyze your sales data! What specific aspects would you like to focus on? For example: monthly trends, regional performance, product categories, or seasonal patterns?", - ), - ( - "I want to understand why our Q2 sales dropped compared to Q1", - "Let's investigate the Q2 sales decline. I'll need to examine several factors:\n\n1. **Monthly breakdown**: Compare April, May, June individually\n2. **Product performance**: Which products/categories were most affected\n3. **Regional analysis**: Geographic impact patterns\n4. **External factors**: Market conditions, competition, seasonality\n\nCan you share your sales data or tell me what data sources you have available?", - ), - ( - "We have sales by month, product category, and region. Q1 was $2.4M, Q2 was $1.8M", - "That's a significant 25% decline from Q1 to Q2 ($600k drop). Here's my analysis approach:\n\n**Key Questions:**\n- Which product categories drove the decline?\n- Was the drop consistent across all regions?\n- Did it happen gradually or sudden in a specific month?\n\n**Recommended Analysis:**\n1. Break down the $600k loss by category and region\n2. Compare month-over-month trends within Q2\n3. Look for external factors (market conditions, competitor launches)\n4. Check if this aligns with historical seasonal patterns\n\nThis will help identify if it's a temporary market condition or a deeper business issue requiring strategic changes.", - ), - ], - }, - { - "app_id": "initech", - "task_id": "content-writing", - "turns": [ - ( - "I need help writing a press release for our new product launch", - "I'd be happy to help you write a compelling press release! To create the most effective announcement, I'll need some key details:\n\n1. **Product name and category**\n2. **Key features and benefits**\n3. **Target audience**\n4. **Launch date and availability**\n5. **Company background**\n6. **Any notable partnerships or achievements**\n\nWhat's the product you're launching?", - ), - ( - "It's a new project management software called 'TaskFlow' that uses AI to optimize team workflows", - "Excellent! TaskFlow sounds innovative. Here's a draft press release structure:\n\n**FOR IMMEDIATE RELEASE**\n\n**Initech Launches TaskFlow: AI-Powered Project Management Platform Revolutionizes Team Productivity**\n\n*Revolutionary software leverages artificial intelligence to optimize workflows and boost team efficiency*\n\n[City, Date] – Initech today announced the launch of TaskFlow, a cutting-edge project management platform that harnesses the power of artificial intelligence to optimize team workflows and dramatically improve productivity.\n\nWould you like me to continue with specific features and benefits? What are TaskFlow's main AI capabilities?", - ), - ( - "The AI can predict project bottlenecks, automatically assign tasks based on team skills, and suggest optimal deadlines", - 'Perfect! Here\'s the complete press release:\n\n**FOR IMMEDIATE RELEASE**\n\n**Initech Launches TaskFlow: AI-Powered Project Management Platform Revolutionizes Team Productivity**\n\n*Revolutionary software leverages artificial intelligence to optimize workflows and boost team efficiency by up to 40%*\n\n[City, Date] – Initech today announced the launch of TaskFlow, a cutting-edge project management platform that harnesses artificial intelligence to transform how teams collaborate and execute projects.\n\n**Key Features:**\n• **Predictive Analytics**: AI algorithms identify potential bottlenecks before they impact deadlines\n• **Smart Task Assignment**: Automatically matches tasks to team members based on skills, availability, and task\n• **Optimized Scheduling**: Suggests realistic deadlines using historical data and team capacity\n\n"TaskFlow represents the future of project management," said [Spokesperson Name], [Title] at Initech. "Our AI doesn\'t just track progress – it actively optimizes your team\'s workflow to ensure projects succeed."\n\nTaskFlow is available immediately with pricing starting at $X per user per month. For more information, visit [website] or contact [email].\n\n**About Initech**\n[Company description]\n\n###', - ), - ], - }, - ] - - # Generate entries for each conversation - for conv in conversations: - thread_id = generate_thread_id() - - for turn_idx, (prompt, response) in enumerate(conv["turns"]): - # Build message history (all previous turns) - messages = [] - for prev_idx in range(turn_idx): - prev_prompt, prev_response = conv["turns"][prev_idx] - messages.extend( - [ - {"role": "user", "content": prev_prompt}, - {"role": "assistant", "content": prev_response}, - ] - ) - - # Add current user message - messages.append({"role": "user", "content": prompt}) - - # Create events for some turns - events = [] - - # Add thumbs up on the first response of each thread - if turn_idx == 0: - events.append({"event_type": "user_feedback", "thumb": "up"}) - - # Add copy action on code responses - if "```" in response and turn_idx == 1: - events.append({"event_type": "user_action", "action": "copy_clicked", "metadata": {"code_block": True}}) - - # Add rating on final turn - if turn_idx == len(conv["turns"]) - 1: - events.append({"event_type": "user_feedback", "rating": 5}) - - entries.append( - EntryDraft( - workspace=workspace, - external_id=generate_entry_id(), - model="llama-3.1-70b-instruct", - messages=messages, - response_text=response, - usage={ - "prompt_tokens": 200 + turn_idx * 50, - "completion_tokens": 300 + turn_idx * 100, - "total_tokens": 500 + turn_idx * 150, - }, - app_id=conv["app_id"], - task_id=conv["task_id"], - thread_id=thread_id, - events=events or None, - ).to_dict() - ) - - return entries - - -def create_feedback_heavy_entries(workspace: str) -> List[Dict[str, Any]]: - """ - Create 30 entries with heavy user feedback events. - - These demonstrate various types of user feedback: - - Thumbs up/down with reasons - - 1-5 star ratings - - Written opinions and comments - - Multiple events per entry - - Returns list of entry payloads ready for POST /entries - """ - entries = [] - - feedback_scenarios: List[Scenario] = [ - { - "prompt": "Explain quantum computing in simple terms", - "response": "Quantum computing is like having a super-powered computer that can explore many solutions to a problem simultaneously, rather than checking them one by one like regular computers. Think of it as the difference between reading every book in a library one at a time versus somehow reading them all at once!", - "events": [ - {"event_type": "user_feedback", "thumb": "up"}, - {"event_type": "user_feedback", "rating": 4}, - { - "event_type": "user_feedback", - "opinion": "Great analogy! Really helped me understand the concept.", - }, - ], - }, - { - "prompt": "Write a Python function to reverse a string", - "response": "```python\ndef reverse_string(s):\n return s[::-1]\n```", - "events": [ - {"event_type": "user_feedback", "thumb": "down"}, - { - "event_type": "user_feedback", - "opinion": "This works but could you explain how slicing works?", - }, - ], - }, - { - "prompt": "What are the benefits of renewable energy?", - "response": "Renewable energy offers numerous benefits: 1) Environmental - reduces greenhouse gas emissions and air pollution, 2) Economic - creates jobs and reduces energy costs long-term, 3) Energy security - reduces dependence on fossil fuel imports, 4) Sustainability - inexhaustible energy sources, 5) Health - cleaner air leads to better public health outcomes.", - "events": [ - {"event_type": "user_feedback", "thumb": "up"}, - {"event_type": "user_feedback", "rating": 5}, - {"event_type": "user_action", "action": "share_clicked", "metadata": {"share_platform": "twitter"}}, - ], - }, - ] - - # Add the specific feedback scenarios - for scenario in feedback_scenarios: - app_id = random.choice(CLIENT_IDS) - task_id = random.choice(list(TASKS.keys())) - - entries.append( - EntryDraft( - workspace=workspace, - external_id=generate_entry_id(), - model="llama-3.1-8b-instruct", - messages=[{"role": "user", "content": scenario["prompt"]}], - response_text=scenario["response"], - usage={"prompt_tokens": 25, "completion_tokens": 75, "total_tokens": 100}, - app_id=app_id, - task_id=task_id, - thread_id=None, - events=scenario["events"], - ).to_dict() - ) - - # Generate additional random feedback entries - for i in range(27): # 27 more for total of 30 - app_id = random.choice(CLIENT_IDS) - task_id = random.choice(list(TASKS.keys())) - - events = [] - - # Random feedback patterns - if random.random() < 0.7: # 70% get thumbs - thumb = "up" if random.random() < 0.8 else "down" # 80% positive - events.append({"event_type": "user_feedback", "thumb": thumb}) - - if random.random() < 0.3: # 30% get ratings - rating = random.randint(1, 5) - events.append({"event_type": "user_feedback", "rating": rating}) - - if random.random() < 0.2: # 20% get opinions - opinions = [ - "Very helpful response!", - "Could be more detailed", - "Perfect explanation", - "Not quite what I was looking for", - "This solved my problem exactly", - "Good information but needs examples", - "Clear and concise, thanks!", - "Could use more context", - ] - events.append( - { - "event_type": "user_feedback", - "opinion": random.choice(opinions), - } - ) - - prompt = f"Sample question about {task_id}" - entries.append( - EntryDraft( - workspace=workspace, - external_id=generate_entry_id(), - model="llama-3.1-8b-instruct", - messages=[{"role": "user", "content": prompt}], - response_text=f"Sample response for {task_id} with feedback potential", - usage={"prompt_tokens": 20, "completion_tokens": 50, "total_tokens": 70}, - app_id=app_id, - task_id=task_id, - thread_id=None, - events=events or None, - ).to_dict() - ) - - return entries - - -def create_action_heavy_entries(workspace: str) -> List[Dict[str, Any]]: - """ - Create 20 entries with user action events. - - These demonstrate user interactions beyond feedback: - - Copy button clicks - - Share actions - - Link clicks - - Export actions - - Returns list of entry payloads ready for POST /entries - """ - entries = [] - - action_scenarios: List[Scenario] = [ - { - "prompt": "Generate a SQL query to find active users", - "response": "```sql\nSELECT user_id, email, last_login\nFROM users \nWHERE last_login >= DATE_SUB(NOW(), INTERVAL 30 DAY)\nAND status = 'active'\nORDER BY last_login DESC;\n```", - "events": [ - {"event_type": "user_action", "action": "copy_clicked", "metadata": {"copied_content": "sql_query"}}, - { - "event_type": "user_action", - "action": "share_clicked", - "metadata": {"share_platform": "slack", "team_share": True}, - }, - ], - }, - { - "prompt": "Show me how to deploy a React app to production", - "response": "Here's a step-by-step guide to deploy your React app:\n\n1. Build the production version:\n```bash\nnpm run build\n```\n\n2. Choose a hosting platform (Netlify, Vercel, or AWS S3)\n\n3. For Netlify:\n- Connect your GitHub repo\n- Set build command: `npm run build`\n- Set publish directory: `build`\n\n4. Configure environment variables if needed\n\nYour app will be live with automatic SSL and CDN!", - "events": [ - {"event_type": "user_action", "action": "copy_clicked", "metadata": {"copied_content": "bash_command"}}, - {"event_type": "user_action", "action": "link_clicked", "metadata": {"external_link": "netlify.com"}}, - ], - }, - ] - - # Add specific action scenarios - for scenario in action_scenarios: - app_id = random.choice(CLIENT_IDS) - task_id = "code-generation" # These are code-related - - entries.append( - EntryDraft( - workspace=workspace, - external_id=generate_entry_id(), - model="llama-3.1-8b-instruct", - messages=[{"role": "user", "content": scenario["prompt"]}], - response_text=scenario["response"], - usage={"prompt_tokens": 30, "completion_tokens": 120, "total_tokens": 150}, - app_id=app_id, - task_id=task_id, - thread_id=None, - events=scenario["events"], - ).to_dict() - ) - - # Generate more random action entries - for i in range(18): # 18 more for total of 20 - app_id = random.choice(CLIENT_IDS) - task_id = random.choice(list(TASKS.keys())) - - # Random action events - events = [] - action_types = ["copy_clicked", "share_clicked", "link_clicked", "export_clicked", "bookmark_clicked"] - - if random.random() < 0.8: # 80% get at least one action - action = random.choice(action_types) - events.append({"event_type": "user_action", "action": action, "metadata": {"action_context": task_id}}) - - if random.random() < 0.3: # 30% get second action - action = random.choice(action_types) - events.append({"event_type": "user_action", "action": action, "metadata": {"secondary_action": True}}) - - prompt = f"Sample prompt for {task_id} task" - entries.append( - EntryDraft( - workspace=workspace, - external_id=generate_entry_id(), - model="llama-3.1-8b-instruct", - messages=[{"role": "user", "content": prompt}], - response_text=f"Response with actionable content for {task_id}", - usage={"prompt_tokens": 30, "completion_tokens": 80, "total_tokens": 110}, - app_id=app_id, - task_id=task_id, - thread_id=None, - events=events or None, - ).to_dict() - ) - - return entries - - -# --------------------------------------------------------------------------- -# Main Generation Function -# --------------------------------------------------------------------------- - - -def generate_complete_dataset(workspace: str = DEFAULT_WORKSPACE) -> Dict[str, List[Dict[str, Any]]]: - """ - Generate the complete test dataset with all scenarios. - - Returns: - Dictionary with categorized entry data: - - single_turns: 40 standalone entries - - conversations: ~12 multi-turn conversation entries - - feedback_heavy: 30 entries with user feedback - - action_heavy: 20 entries with user actions - - Total: ~102 entry records - """ - print(f"🎯 Generating comprehensive test dataset for workspace '{workspace}'...") - - dataset = { - "single_turns": create_single_turn_entries(workspace), - "conversations": create_conversation_threads(workspace), - "feedback_heavy": create_feedback_heavy_entries(workspace), - "action_heavy": create_action_heavy_entries(workspace), - } - - total_entries = sum(len(entries) for entries in dataset.values()) - - print("✅ Dataset generation complete!") - print(f" • Single-turn entries: {len(dataset['single_turns'])}") - print(f" • Conversation entries: {len(dataset['conversations'])}") - print(f" • Feedback-heavy entries: {len(dataset['feedback_heavy'])}") - print(f" • Action-heavy entries: {len(dataset['action_heavy'])}") - print(f" • Total entries: {total_entries}") - - return dataset - - -def save_dataset_to_files(dataset: Dict[str, List[Dict[str, Any]]], output_dir: Path = DEFAULT_OUTPUT_DIR) -> None: - """Save the generated dataset to JSON files for loading via API.""" - output_path = Path(output_dir) - output_path.mkdir(parents=True, exist_ok=True) - - for category, entries in dataset.items(): - file_path = output_path / f"{category}.json" - with open(file_path, "w") as f: - json.dump(entries, f, indent=2, default=str) - print(f"💾 Saved {len(entries)} entries to {file_path}") - - # Also save everything as one big file - all_entries = [] - for entries in dataset.values(): - all_entries.extend(entries) - - with open(output_path / "all_entries.json", "w") as f: - json.dump(all_entries, f, indent=2, default=str) - - print(f"💾 Saved all {len(all_entries)} entries to {output_path}/all_entries.json") - - -def parse_args() -> argparse.Namespace: - parser = argparse.ArgumentParser(description="Generate a v2 Intake API test dataset as JSON files.") - parser.add_argument( - "--workspace", - default=DEFAULT_WORKSPACE, - help=( - "Workspace to embed in each entry's context.app " - "(must match load_test_data.py --workspace; default: default)" - ), - ) - parser.add_argument( - "--output-dir", - type=Path, - default=DEFAULT_OUTPUT_DIR, - help=f"Directory to write JSON files (default: {DEFAULT_OUTPUT_DIR})", - ) - return parser.parse_args() - - -if __name__ == "__main__": - args = parse_args() - - dataset = generate_complete_dataset(workspace=args.workspace) - - save_dataset_to_files(dataset, output_dir=args.output_dir) - - print("\n🚀 Ready to load data! Use the load_test_data.py script to POST to the API.") diff --git a/services/intake/scripts/init-multiple-dbs.sh b/services/intake/scripts/init-multiple-dbs.sh deleted file mode 100755 index 0bac74f394..0000000000 --- a/services/intake/scripts/init-multiple-dbs.sh +++ /dev/null @@ -1,6 +0,0 @@ -#!/bin/bash -set -e -psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" <<-EOSQL - CREATE DATABASE nemo_db; - CREATE DATABASE nemo_test_db; -EOSQL \ No newline at end of file diff --git a/services/intake/scripts/load_test_data.py b/services/intake/scripts/load_test_data.py deleted file mode 100644 index ebe4c3a913..0000000000 --- a/services/intake/scripts/load_test_data.py +++ /dev/null @@ -1,419 +0,0 @@ -#!/usr/bin/env python3 -# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. -# SPDX-License-Identifier: Apache-2.0 - -"""Load generated test dataset into Intake API via HTTP requests. - -This script takes the JSON files generated by generate_test_dataset.py and -POSTs them to the Intake API /entries endpoint. - -Features: -- Parallel loading with configurable concurrency -- Progress tracking with real-time statistics -- Comprehensive error handling and reporting -- Dry-run mode for validation -- Flexible data directory and URL configuration -- Optional category filtering - -Usage: - python load_test_data.py # Load all data to localhost:8080 - python load_test_data.py --dry-run # Validate without posting - python load_test_data.py --url https://nemo.example.com --workspace my-team - python load_test_data.py --categories single_turns conversations # Load specific categories -""" - -from __future__ import annotations - -import argparse -import json -import sys -import time -from concurrent.futures import ThreadPoolExecutor, as_completed -from pathlib import Path -from typing import Any, Dict, List, Optional, Tuple - -import requests -from requests.adapters import HTTPAdapter - -# --------------------------------------------------------------------------- -# HTTP Client Configuration -# --------------------------------------------------------------------------- - - -def create_http_session(pool_size: int = 10) -> requests.Session: - """Create HTTP session with retry strategy and a connection pool large enough for concurrent workers.""" - session = requests.Session() - - adapter = HTTPAdapter(max_retries=3, pool_connections=pool_size, pool_maxsize=pool_size) - session.mount("http://", adapter) - session.mount("https://", adapter) - - return session - - -def entries_url(base_url: str, workspace: str) -> str: - """Build the v2 entries endpoint URL.""" - return f"{base_url.rstrip('/')}/apis/intake/v2/workspaces/{workspace}/entries" - - -def verify_api_connectivity(post_url: str) -> bool: - """Verify the entries endpoint is reachable by listing one entry.""" - try: - response = requests.get(post_url, params={"page": 1, "page_size": 1}, timeout=5) - if response.status_code == 200: - print(f"✅ Entries endpoint reachable: {post_url}") - return True - print(f"❌ Entries endpoint returned {response.status_code}: {response.text[:200]}") - return False - except requests.RequestException as e: - print(f"❌ Cannot reach entries endpoint at {post_url}: {e}") - return False - - -# --------------------------------------------------------------------------- -# Data Loading Functions -# --------------------------------------------------------------------------- - - -def load_entry_data(data_dir: Path, categories: Optional[List[str]] = None) -> Dict[str, List[Dict[str, Any]]]: - """ - Load entry data from JSON files. - - Args: - data_dir: Directory containing JSON files - categories: Optional list of specific categories to load - - Returns: - Dictionary mapping category names to entry lists - """ - if not data_dir.exists(): - raise FileNotFoundError(f"Data directory not found: {data_dir}") - - available_files = { - "single_turns": data_dir / "single_turns.json", - "conversations": data_dir / "conversations.json", - "feedback_heavy": data_dir / "feedback_heavy.json", - "action_heavy": data_dir / "action_heavy.json", - "all_entries": data_dir / "all_entries.json", - } - - # Filter to requested categories - if categories: - files_to_load = {cat: available_files[cat] for cat in categories if cat in available_files} - missing = set(categories) - set(available_files.keys()) - if missing: - print(f"⚠️ Unknown categories: {missing}") - else: - # Load all except the combined file - files_to_load = {k: v for k, v in available_files.items() if k != "all_entries"} - - dataset = {} - total_entries = 0 - - for category, file_path in files_to_load.items(): - if not file_path.exists(): - print(f"⚠️ File not found: {file_path}") - continue - - try: - with open(file_path, "r") as f: - entries = json.load(f) - - dataset[category] = entries - total_entries += len(entries) - print(f"📁 Loaded {len(entries)} entries from {category}") - - except (json.JSONDecodeError, IOError) as e: - print(f"❌ Error loading {file_path}: {e}") - - print(f"📊 Total entries loaded: {total_entries}") - return dataset - - -def post_entry( - session: requests.Session, - post_url: str, - entry: Dict[str, Any], - dry_run: bool = False, - timeout: float = 30.0, -) -> Tuple[bool, Optional[str]]: - """ - POST a single entry to the v2 entries endpoint. - - Args: - session: HTTP session - post_url: Full URL of the v2 entries endpoint - entry: Entry data to post - dry_run: If True, validate but don't send request - timeout: Per-request timeout in seconds - - Returns: - Tuple of (success, error_message) - """ - if dry_run: - required_fields = ["data", "context"] - missing = [field for field in required_fields if field not in entry] - if missing: - return False, f"Missing required fields: {missing}" - - request = entry["data"].get("request") - response = entry["data"].get("response") - context = entry.get("context") - - if not isinstance(request, dict) or not request.get("model") or not request.get("messages"): - return False, "Invalid data.request; expected non-empty model and messages" - if not isinstance(response, dict) or not isinstance(response.get("choices"), list) or not response["choices"]: - return False, "Invalid data.response; expected non-empty choices" - first_choice = response["choices"][0] - message = first_choice.get("message") if isinstance(first_choice, dict) else None - if not isinstance(message, dict) or "role" not in message: - return False, "Invalid data.response.choices[0].message" - if not isinstance(context, dict) or "app" not in context or "task" not in context: - return False, "Invalid context; expected app and task" - return True, None - - try: - response = session.post( - post_url, - json=entry, - headers={"Content-Type": "application/json"}, - timeout=timeout, - ) - - if response.status_code in [200, 201]: - return True, None - else: - return False, f"HTTP {response.status_code}: {response.text[:200]}" - - except requests.RequestException as e: - return False, f"Request failed: {str(e)[:100]}" - - -# --------------------------------------------------------------------------- -# Parallel Loading with Progress Tracking -# --------------------------------------------------------------------------- - - -class LoadingProgress: - """Track loading progress and statistics.""" - - def __init__(self, total_count: int): - self.total_count = total_count - self.completed = 0 - self.successful = 0 - self.failed = 0 - self.start_time = time.time() - self.errors: List[str] = [] - - def update(self, success: bool, error: Optional[str] = None) -> None: - """Update progress counters.""" - self.completed += 1 - if success: - self.successful += 1 - else: - self.failed += 1 - if error: - self.errors.append(error) - - def print_progress(self) -> None: - """Print current progress to console.""" - elapsed = time.time() - self.start_time - rate = self.completed / elapsed if elapsed > 0 else 0 - - progress_pct = (self.completed / self.total_count) * 100 - - print( - f"\r📈 Progress: {self.completed}/{self.total_count} ({progress_pct:.1f}%) | " - f"✅ {self.successful} | ❌ {self.failed} | {rate:.1f}/sec", - end="", - flush=True, - ) - - def print_summary(self) -> None: - """Print final loading summary.""" - elapsed = time.time() - self.start_time - print("\n\n📊 Loading Summary:") - print(f" • Total entries: {self.total_count}") - print(f" • Successful: {self.successful}") - print(f" • Failed: {self.failed}") - print(f" • Success rate: {(self.successful / self.total_count) * 100:.1f}%") - print(f" • Total time: {elapsed:.1f}s") - print(f" • Average rate: {self.total_count / elapsed:.1f} entries/sec") - - if self.errors: - print("\n❌ Error samples:") - for error in self.errors[:5]: # Show first 5 errors - print(f" • {error}") - if len(self.errors) > 5: - print(f" • ... and {len(self.errors) - 5} more errors") - - -def load_dataset_parallel( - session: requests.Session, - post_url: str, - dataset: Dict[str, List[Dict[str, Any]]], - dry_run: bool = False, - max_workers: int = 10, -) -> LoadingProgress: - """ - Load all entries with progress tracking, optionally in parallel. - - Args: - session: HTTP session (requests.Session is thread-safe for send()) - post_url: Full URL of the v2 entries endpoint - dataset: Dictionary of categorized entry data - dry_run: If True, validate but don't send requests - max_workers: Maximum concurrent requests. 1 runs sequentially. - - Returns: - LoadingProgress object with final statistics - """ - all_entries = [entry for entries in dataset.values() for entry in entries] - if not all_entries: - raise ValueError("No entries found in the selected dataset") - - progress = LoadingProgress(len(all_entries)) - - mode = "dry-run validation" if dry_run else "data loading" - effective_workers = 1 if dry_run else max(1, max_workers) - print(f"🚀 Starting {mode} with {effective_workers} worker(s)...") - - def record(success: bool, err: Optional[str]) -> None: - progress.update(success, err) - if progress.completed % 10 == 0 or progress.completed == progress.total_count: - progress.print_progress() - - if effective_workers == 1: - for entry in all_entries: - record(*post_entry(session, post_url, entry, dry_run)) - else: - with ThreadPoolExecutor(max_workers=effective_workers) as executor: - futures = [executor.submit(post_entry, session, post_url, entry, dry_run) for entry in all_entries] - for future in as_completed(futures): - record(*future.result()) - - progress.print_summary() - - return progress - - -# --------------------------------------------------------------------------- -# CLI Interface -# --------------------------------------------------------------------------- - - -def parse_args() -> argparse.Namespace: - """Parse command line arguments.""" - parser = argparse.ArgumentParser( - description="Load test entry data into Intake API", - formatter_class=argparse.RawDescriptionHelpFormatter, - epilog=""" -Examples: - %(prog)s # Load all data to localhost - %(prog)s --dry-run # Validate without posting - %(prog)s --url http://api.example.com:8000 # Load to custom API - %(prog)s --categories single_turns # Load only single-turn entries - %(prog)s --data-dir /path/to/data # Use custom data directory - %(prog)s --workers 20 # Use 20 concurrent workers - """, - ) - - parser.add_argument( - "--url", - default="http://localhost:8080", - help="Base URL of the NeMo Platform gateway (default: http://localhost:8080)", - ) - - parser.add_argument( - "--workspace", - default="default", - help="Workspace to write entries into (default: default)", - ) - - parser.add_argument( - "--data-dir", - type=Path, - default=Path(__file__).resolve().parents[3] / ".tmp" / "intake" / "test_data", - help="Directory containing JSON data files (default: /.tmp/intake/test_data)", - ) - - parser.add_argument( - "--categories", - nargs="*", - choices=["single_turns", "conversations", "feedback_heavy", "action_heavy"], - help="Specific categories to load (default: all)", - ) - - parser.add_argument( - "--workers", - type=int, - default=3, - help="Number of concurrent workers (default: 3). Higher values can overwhelm a local intake instance.", - ) - - parser.add_argument("--dry-run", action="store_true", help="Validate data without sending requests") - - return parser.parse_args() - - -# --------------------------------------------------------------------------- -# Main Function -# --------------------------------------------------------------------------- - - -def main() -> int: - """Main loading function.""" - args = parse_args() - - print("🎯 Intake API Data Loader") - print("=" * 50) - print(f"API URL: {args.url}") - print(f"Workspace: {args.workspace}") - print(f"Data directory: {args.data_dir}") - print(f"Categories: {args.categories or 'all'}") - print(f"Workers: {args.workers}") - print(f"Dry run: {args.dry_run}") - print() - - try: - post_url = entries_url(args.url, args.workspace) - - # Verify API connectivity (unless dry run) - if not args.dry_run: - if not verify_api_connectivity(post_url): - return 1 - - # Load entry data - dataset = load_entry_data(args.data_dir, args.categories) - if not dataset: - print("❌ No data to load") - return 1 - - # Create HTTP session with a connection pool sized for the worker count - session = create_http_session(pool_size=max(1, args.workers)) - - progress = load_dataset_parallel( - session=session, post_url=post_url, dataset=dataset, dry_run=args.dry_run, max_workers=args.workers - ) - - # Return appropriate exit code - if progress.failed == 0: - print(f"\n✅ {'Validation' if args.dry_run else 'Loading'} completed successfully!") - return 0 - elif progress.successful > 0: - print(f"\n⚠️ {'Validation' if args.dry_run else 'Loading'} completed with {progress.failed} errors") - return 1 - else: - print(f"\n❌ {'Validation' if args.dry_run else 'Loading'} failed completely") - return 2 - - except KeyboardInterrupt: - print("\n\n⏹️ Loading interrupted by user") - return 130 - except Exception as e: - print(f"\n💥 Unexpected error: {e}") - return 1 - - -if __name__ == "__main__": - sys.exit(main()) diff --git a/services/intake/scripts/seed-entries.py b/services/intake/scripts/seed-entries.py deleted file mode 100644 index c0e9abc613..0000000000 --- a/services/intake/scripts/seed-entries.py +++ /dev/null @@ -1,963 +0,0 @@ -# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. -# SPDX-License-Identifier: Apache-2.0 - -"""Script to seed the Intake service with diverse, realistic entry records. - -Posts to the v2 Intake API. Creates three types of diverse tasks: -1. Conversational Support - Progressive 10-turn conversation with user feedback events -2. Tool Calling Examples - Single-turn tool calling scenarios with occasional user feedback -3. Query Rewriting - Complex question rewriting with user feedback and reviewer corrections - -Examples: - # Seed the local quickstart - python -m scripts.seed-entries - - # Seed a deployed environment - python -m scripts.seed-entries --api-url https://nemo.example.com - - # Seed a non-default workspace - python -m scripts.seed-entries --workspace my-team - - # Delete all existing entries in the target workspace before seeding - python -m scripts.seed-entries --workspace my-team --clear -""" - -import argparse -import logging -import random -from datetime import datetime, timezone -from typing import Any, Dict, List, Optional - -import httpx - -logger = logging.getLogger(__name__) - - -def entries_url(base_url: str, workspace: str) -> str: - """Build the v2 entries endpoint URL for the given workspace.""" - return f"{base_url.rstrip('/')}/apis/intake/v2/workspaces/{workspace}/entries" - - -# Conversation Data -------------------------------------------------------- - -CONVERSATION_TOPICS = [ - { - "topic": "Python debugging help", - "turns": [ - { - "user": "I'm getting a weird error in my Python code. It says 'list index out of range' but I can't figure out where.", - "assistant": "I'd be happy to help you debug that IndexError! To better assist you, could you share the specific code that's causing the error and the full error traceback? The 'list index out of range' error typically occurs when you're trying to access an index that doesn't exist in your list.", - }, - { - "user": "Here's my code:\n```python\nnumbers = [1, 2, 3]\nfor i in range(5):\n print(numbers[i])\n```", - "assistant": "I found the issue! Your list `numbers` has only 3 elements (indices 0, 1, 2), but your loop runs 5 times (i goes from 0 to 4). When i becomes 3 or 4, you're trying to access `numbers[3]` and `numbers[4]` which don't exist.\n\nHere's the fix:\n```python\nnumbers = [1, 2, 3]\nfor i in range(len(numbers)): # This ensures i only goes from 0 to 2\n print(numbers[i])\n```\n\nOr even better, use direct iteration:\n```python\nnumbers = [1, 2, 3]\nfor number in numbers:\n print(number)\n```", - }, - { - "user": "That makes sense! But what if I actually need the index for something else?", - "assistant": 'Great question! When you need both the index and the value, use the `enumerate()` function. It\'s the Pythonic way to get both:\n\n```python\nnumbers = [1, 2, 3]\nfor i, number in enumerate(numbers):\n print(f"Index {i}: {number}")\n```\n\nThis will output:\n```\nIndex 0: 1\nIndex 1: 2\nIndex 2: 3\n```\n\nThe `enumerate()` function returns tuples of (index, value) for each item in your list, so you avoid the index out of range error while still having access to both pieces of information.', - }, - { - "user": "Perfect! One more question - what if I'm working with a list that might be empty?", - "assistant": 'Excellent question! Empty lists are a common edge case. Here are a few approaches:\n\n1. **Check if the list is empty first:**\n```python\nnumbers = []\nif numbers: # This is False for empty lists\n for i, number in enumerate(numbers):\n print(f"Index {i}: {number}")\nelse:\n print("List is empty!")\n```\n\n2. **Use try-except for specific operations:**\n```python\nnumbers = []\ntry:\n first_item = numbers[0]\n print(f"First item: {first_item}")\nexcept IndexError:\n print("No items in the list")\n```\n\n3. **Use get-like methods for safe access:**\n```python\nnumbers = []\n# Safe way to get first item with default\nfirst_item = numbers[0] if numbers else "No items"\nprint(first_item)\n```\n\nThe `enumerate()` and direct iteration approaches handle empty lists gracefully - they simply don\'t execute the loop body.', - }, - { - "user": "This is really helpful! I think I understand lists better now. Do you have any tips for avoiding these kinds of errors in general?", - "assistant": "Absolutely! Here are some best practices to avoid index errors and write more robust Python code:\n\n**1. Prefer iteration over indexing:**\n```python\n# Good\nfor item in my_list:\n process(item)\n\n# Less preferred\nfor i in range(len(my_list)):\n process(my_list[i])\n```\n\n**2. Use built-in functions:**\n- `enumerate()` when you need indices\n- `zip()` for parallel iteration\n- `len()` to check bounds\n\n**3. Validate inputs:**\n```python\ndef get_item(lst, index):\n if 0 <= index < len(lst):\n return lst[index]\n return None # or raise a custom exception\n```\n\n**4. Use list comprehensions and built-ins:**\n```python\n# Instead of manual loops that might have index errors\nresults = [process(item) for item in my_list if condition(item)]\n```\n\n**5. Consider using `get()` method for dictionaries and similar patterns for lists.**\n\nThe key is to write code that's naturally safe by design rather than trying to catch errors after the fact!", - }, - { - "user": "These are great tips! I'm going to bookmark this conversation. Thanks so much for your help!", - "assistant": "You're very welcome! I'm glad I could help you understand list indexing better. Debugging can be frustrating, but you handled it really well by providing the code example - that made all the difference in identifying the issue quickly.\n\nFeel free to come back anytime you have more Python questions. Happy coding, and remember: when in doubt, `print()` statements and the Python debugger are your friends for tracking down tricky issues!", - }, - { - "user": "Actually, one quick follow-up. What's this Python debugger you mentioned?", - "assistant": 'Great question! Python has a built-in debugger called `pdb` (Python Debugger) that\'s incredibly useful for stepping through code and inspecting variables.\n\n**Basic usage:**\n```python\nimport pdb\n\nnumbers = [1, 2, 3]\npdb.set_trace() # Execution will pause here\nfor i in range(5):\n print(numbers[i]) # You can inspect this line-by-line\n```\n\n**Key pdb commands:**\n- `n` (next): Execute next line\n- `s` (step): Step into function calls\n- `c` (continue): Continue execution\n- `l` (list): Show current code\n- `p variable_name`: Print variable value\n- `pp variable_name`: Pretty-print variable\n- `q` (quit): Exit debugger\n\n**Modern alternatives:**\n- `breakpoint()` (Python 3.7+) - cleaner than `pdb.set_trace()`\n- IDE debuggers (PyCharm, VS Code) - visual debugging\n- `ipdb` - enhanced debugger with IPython features\n\n**Quick example:**\n```python\nnumbers = [1, 2, 3]\nfor i in range(5):\n breakpoint() # Pause here each iteration\n print(f"About to access index {i}")\n print(numbers[i])\n```\n\nThis would let you see exactly when `i` becomes problematic!', - }, - { - "user": "Wow, I had no idea Python had a built-in debugger! This would have saved me so much time. How do I exit the debugger if I accidentally trigger it?", - "assistant": "Great question! Getting \"stuck\" in the debugger is a common experience when you're first learning it. Here's how to get out:\n\n**To exit the debugger:**\n1. Type `q` and press Enter - this quits the debugger\n2. If that doesn't work, try `Ctrl+C` (sometimes needed if the debugger is unresponsive)\n3. As a last resort, `Ctrl+Z` (on Unix/Mac) or `Ctrl+Break` (Windows) to force quit\n\n**Pro tips to avoid getting stuck:**\n```python\n# Remove debugger statements when done debugging\n# breakpoint() # Comment out or delete\n\n# Or use conditional breakpoints\nif some_condition:\n breakpoint()\n\n# Environment variable to disable all breakpoints\n# Set PYTHONBREAKPOINT=0 to disable all breakpoint() calls\n```\n\n**If you're in an interactive session:**\n- The debugger will show `(Pdb)` prompt\n- Type `c` to continue execution\n- Type `q` to quit back to normal Python prompt\n\n**In IDEs:**\n- Usually there's a \"Stop\" or \"Terminate\" button\n- Or use the IDE's keyboard shortcut (often Shift+F5)\n\n**Remember:** The debugger is pausing execution, not breaking anything. Your program will resume normally once you continue or quit. Don't panic if you accidentally trigger it - just type `q` and you're back to normal!", - }, - { - "user": "Perfect! This has been incredibly educational. I feel much more confident about debugging Python now.", - "assistant": "That's fantastic to hear! You've made great progress - from fixing a specific IndexError to learning about defensive programming practices and debugging tools. That's exactly the kind of learning journey that will make you a stronger developer.\n\nRemember these key takeaways:\n- Use `enumerate()` and direct iteration when possible\n- Validate inputs and handle edge cases\n- The debugger (`breakpoint()`) is your friend for complex issues\n- Don't be afraid to add `print()` statements for quick debugging\n\nYou've got this! Debugging is a skill that improves with practice, and you're asking all the right questions. Keep experimenting and don't hesitate to reach out when you encounter new challenges. Happy coding! 🐍✨", - }, - { - "user": "One last thing - do you have any recommendations for Python debugging tools beyond the built-in debugger?", - "assistant": "Absolutely! Here are some excellent debugging tools beyond `pdb`:\n\n**Enhanced Debuggers:**\n- **ipdb** - Like pdb but with IPython features (syntax highlighting, tab entry)\n- **pudb** - Full-screen console debugger with a nice UI\n- **web-pdb** - Debug in your browser instead of terminal\n\n**IDE Integration:**\n- **PyCharm** - Excellent visual debugger with breakpoints, variable inspection\n- **VS Code** - Great Python debugging with the Python extension\n- **Thonny** - Beginner-friendly IDE with step-through debugging\n\n**Profiling & Performance:**\n- **cProfile** - Built-in performance profiler\n- **line_profiler** - Line-by-line performance analysis\n- **memory_profiler** - Track memory usage\n\n**Logging & Monitoring:**\n- **loguru** - Much better than standard logging\n- **rich** - Beautiful console output and tracebacks\n- **icecream** - Better than print() for debugging\n\n**Quick example with icecream:**\n```python\nfrom icecream import ic\nnumbers = [1, 2, 3]\nfor i in range(5):\n ic(i, len(numbers)) # Shows: i=3, len(numbers)=3\n if i < len(numbers):\n ic(numbers[i])\n```\n\nStart with your IDE's debugger for visual debugging, and icecream for quick print-style debugging. Happy debugging! 🛠️", - }, - ], - }, - { - "topic": "React performance optimization", - "turns": [ - { - "user": "My React app is getting really slow, especially when I type in forms. Any ideas what might be causing this?", - "assistant": "Slow form typing in React is usually caused by unnecessary re-renders. Here are the most common culprits:\n\n1. **Re-rendering the entire component on each keystroke**\n2. **Heavy computations running on every render**\n3. **Inefficient state management**\n4. **Missing memoization**\n\nCould you share a bit about your form setup? Specifically:\n- Are you using controlled components (value + onChange)?\n- How are you managing form state?\n- Are there any complex calculations or API calls happening during typing?", - }, - { - "user": "I'm using controlled components with useState for each field. I have about 10 fields and some validation that runs on every change.", - "assistant": "Ah, that's likely the issue! Running validation on every keystroke is expensive. Here are some optimizations:\n\n**1. Debounce validation:**\n```jsx\nimport { useMemo, useState } from 'react';\nimport { useDebounce } from 'use-debounce';\n\nfunction MyForm() {\n const [email, setEmail] = useState('');\n const [debouncedEmail] = useDebounce(email, 300); // 300ms delay\n \n const emailError = useMemo(() => {\n return validateEmail(debouncedEmail);\n }, [debouncedEmail]);\n\n return (\n setEmail(e.target.value)}\n />\n );\n}\n```\n\n**2. Use React Hook Form (recommended):**\n```jsx\nimport { useForm } from 'react-hook-form';\n\nfunction OptimizedForm() {\n const { register, handleSubmit, formState: { errors } } = useForm();\n \n return (\n
\n \n {errors.email && Email is required}\n
\n );\n}\n```\n\nReact Hook Form minimizes re-renders by using uncontrolled components under the hood. Would you like me to show you how to migrate your current form?", - }, - { - "user": "That React Hook Form looks interesting! How would I migrate my current validation logic to it?", - "assistant": "Great choice! React Hook Form will significantly improve your performance. Here's how to migrate your validation:\n\n**Current pattern (slow):**\n```jsx\nconst [email, setEmail] = useState('');\nconst [errors, setErrors] = useState({});\n\n// This runs on every keystroke - slow!\nconst validateEmail = (value) => {\n if (!value) return 'Email required';\n if (!/\\S+@\\S+\\.\\S+/.test(value)) return 'Invalid email';\n return '';\n};\n\nconst emailError = validateEmail(email);\n```\n\n**Migrated to React Hook Form:**\n```jsx\nimport { useForm } from 'react-hook-form';\n\nfunction MyForm() {\n const { \n register, \n handleSubmit, \n formState: { errors },\n watch // Optional: for dependent fields\n } = useForm({\n mode: 'onBlur', // Validate on blur, not on change\n });\n\n const onSubmit = (data) => {\n console.log(data); // All form data\n };\n\n return (\n
\n \n {errors.email && {errors.email.message}}\n \n \n {errors.name && {errors.name.message}}\n \n \n \n );\n}\n```\n\n**For custom validation functions:**\n```jsx\nconst validatePassword = (value) => {\n if (value.length < 8) return 'Password must be 8+ characters';\n if (!/(?=.*[0-9])/.test(value)) return 'Must contain a number';\n return true;\n};\n\n\n```\n\nThe key benefits: validation only runs when needed, and the form doesn't re-render on every keystroke!", - }, - { - "user": "This is exactly what I needed! One more question - what about complex validation that depends on multiple fields?", - "assistant": "Excellent question! Cross-field validation is where React Hook Form really shines. Here are the best approaches:\n\n**1. Using `watch` for dependent validation:**\n```jsx\nfunction MyForm() {\n const { register, handleSubmit, watch, formState: { errors } } = useForm();\n \n const password = watch('password'); // Watch password field\n \n return (\n
\n \n \n \n value === password || 'Passwords do not match'\n })}\n type=\"password\"\n />\n {errors.confirmPassword && {errors.confirmPassword.message}}\n \n );\n}\n```\n\n**2. Complex multi-field validation:**\n```jsx\nconst validateAddressFields = (data) => {\n const { country, state, zipCode } = data;\n \n if (country === 'US' && !state) {\n return 'State is required for US addresses';\n }\n \n if (country === 'US' && !/^\\d{5}(-\\d{4})?$/.test(zipCode)) {\n return 'Invalid US ZIP code format';\n }\n \n return true;\n};\n\n validateAddressFields(formValues)\n })}\n/>\n```\n\n**3. Real-time validation with `trigger`:**\n```jsx\nconst { register, trigger, watch } = useForm();\nconst password = watch('password');\n\n// Re-validate confirm password when password changes\nReact.useEffect(() => {\n if (password) {\n trigger('confirmPassword'); // Re-run validation\n }\n}, [password, trigger]);\n```\n\n**4. Schema validation with Yup (for complex cases):**\n```jsx\nimport { yupResolver } from '@hookform/resolvers/yup';\nimport * as yup from 'yup';\n\nconst schema = yup.object({\n email: yup.string().email().required(),\n password: yup.string().min(8).required(),\n confirmPassword: yup.string()\n .oneOf([yup.ref('password')], 'Passwords must match')\n .required()\n});\n\nconst { register } = useForm({\n resolver: yupResolver(schema)\n});\n```\n\nThe beauty is that validation only runs when necessary, not on every keystroke!", - }, - { - "user": "Perfect! This will definitely solve my performance issues. Thanks for the detailed examples!", - "assistant": "You're very welcome! You're going to see a huge performance improvement with these changes. React Hook Form typically reduces re-renders by 80-90% compared to traditional controlled components.\n\n**Quick recap of what you're gaining:**\n- ✅ No re-renders on every keystroke\n- ✅ Validation only when needed (onBlur, onSubmit, or triggered)\n- ✅ Built-in performance optimizations\n- ✅ Cleaner code with less boilerplate\n- ✅ Better user experience\n\n**Pro tip:** Start with `mode: 'onBlur'` for the best balance of performance and UX. You can always switch to `mode: 'onChange'` for specific fields if needed.\n\nFeel free to reach out if you run into any issues during the migration. Good luck with your optimization! 🚀", - }, - { - "user": "Just implemented it and wow, the difference is incredible! One more question - any tips for optimizing the rest of my React app?", - "assistant": "That's awesome to hear! 🎉 Since you've tackled forms, here are the next biggest React performance wins:\n\n**1. Memoization (biggest impact):**\n```jsx\n// Memoize expensive components\nconst ExpensiveComponent = React.memo(({ data }) => {\n return
{processData(data)}
;\n});\n\n// Memoize expensive calculations\nconst processedData = useMemo(() => {\n return expensiveCalculation(rawData);\n}, [rawData]);\n\n// Memoize callbacks\nconst handleClick = useCallback((id) => {\n onItemClick(id);\n}, [onItemClick]);\n```\n\n**2. Virtual scrolling for large lists:**\n```jsx\nimport { FixedSizeList as List } from 'react-window';\n\n// Instead of rendering 1000+ items\n\n {({ index, style }) => (\n
{items[index]}
\n )}\n
\n```\n\n**3. Code splitting & lazy loading:**\n```jsx\nconst LazyComponent = lazy(() => import('./HeavyComponent'));\n\nLoading...}>\n \n\n```\n\n**4. Image optimization:**\n```jsx\n// Use next/image or similar\n{alt}\n```\n\n**5. Avoid inline objects/functions:**\n```jsx\n// Bad - creates new object on every render\n\n\n// Good - stable reference\nconst styles = {margin: 10};\n\n```\n\nStart with memoization - it'll give you the biggest wins! 🚀", - }, - { - "user": "Great tips! I'm particularly interested in the memoization approach. When should I use React.memo vs useMemo vs useCallback?", - "assistant": "Excellent question! These three are often confused. Here's when to use each:\n\n**React.memo - Component-level memoization:**\n```jsx\n// Use when props don't change often\nconst ExpensiveChild = React.memo(({ data, onAction }) => {\n return
{expensiveOperation(data)}
;\n});\n\n// Only re-renders when data or onAction changes\n// Skip if parent re-renders but props stay the same\n```\n\n**useMemo - Value memoization:**\n```jsx\n// Use for expensive calculations\nfunction MyComponent({ items, searchTerm }) {\n const filteredItems = useMemo(() => {\n return items.filter(item => \n item.name.toLowerCase().includes(searchTerm.toLowerCase())\n );\n }, [items, searchTerm]); // Only recalculate when these change\n \n return
{filteredItems.map(...)}
;\n}\n```\n\n**useCallback - Function memoization:**\n```jsx\n// Use when passing functions to memoized children\nfunction Parent({ items }) {\n const handleClick = useCallback((id) => {\n // This function won't be recreated on every render\n setSelectedId(id);\n }, []); // Dependencies array\n \n return (\n
\n {items.map(item => \n \n )}\n
\n );\n}\n```\n\n**Quick decision tree:**\n- **React.memo**: Component renders expensive UI\n- **useMemo**: Expensive calculation/transformation\n- **useCallback**: Function passed to memoized children\n\n**Pro tip:** Don't over-memoize! Only use when you have actual performance issues. React is already pretty fast! 🎯", - }, - { - "user": "This is super helpful! One thing I'm struggling with is bundle size. My app takes forever to load initially. Any suggestions?", - "assistant": 'Bundle size is a huge factor in initial load times! Here are the most effective strategies:\n\n**1. Code Splitting (biggest impact):**\n```jsx\n// Split routes\nconst Home = lazy(() => import(\'./pages/Home\'));\nconst Dashboard = lazy(() => import(\'./pages/Dashboard\'));\nconst Settings = lazy(() => import(\'./pages/Settings\'));\n\n\n }>\n \n \n } />\n\n```\n\n**2. Dynamic imports for heavy libraries:**\n```jsx\n// Load chart library only when needed\nconst loadChart = async () => {\n const { Chart } = await import(\'chart.js\');\n return Chart;\n};\n\n// Use in component\nconst handleShowChart = async () => {\n const Chart = await loadChart();\n // Use Chart here\n};\n```\n\n**3. Bundle analysis:**\n```bash\n# Webpack Bundle Analyzer\nnpm install --save-dev webpack-bundle-analyzer\n\n# Or for Create React App\nnpm install --save-dev source-map-explorer\nnpm run build\nnpm run analyze\n```\n\n**4. Tree shaking (import only what you need):**\n```jsx\n// Bad - imports entire library\nimport _ from \'lodash\';\n\n// Good - imports only what you need\nimport { debounce } from \'lodash\';\n// Or even better\nimport debounce from \'lodash/debounce\';\n```\n\n**5. Optimize dependencies:**\n```jsx\n// Replace heavy libraries with lighter alternatives\n// moment.js (67KB) → date-fns (13KB)\n// lodash (69KB) → native JS methods when possible\n// entire icon library → specific icons only\n```\n\n**6. Preloading critical resources:**\n```html\n\n\n```\n\nStart with bundle analysis to see what\'s actually taking up space - you might be surprised! 📊', - }, - { - "user": "Bundle analysis revealed that Chart.js is taking up 40% of my bundle! But I only use it on one admin page. How can I optimize this?", - "assistant": "Perfect example of where code splitting shines! Chart.js is a beast (300KB+), but you can easily load it only when needed:\n\n**1. Route-level splitting (easiest):**\n```jsx\n// AdminDashboard.jsx - split the entire page\nconst AdminDashboard = lazy(() => import('./pages/AdminDashboard'));\n\n}>\n \n \n} />\n```\n\n**2. Component-level splitting (more granular):**\n```jsx\n// ChartComponent.jsx - split just the chart\nconst ChartComponent = lazy(() => import('./components/ChartComponent'));\n\nfunction AdminPage() {\n const [showChart, setShowChart] = useState(false);\n \n return (\n
\n \n {showChart && (\n Loading chart...
}>\n \n \n )}\n \n );\n}\n```\n\n**3. Dynamic import with loading state:**\n```jsx\nfunction AdminPage() {\n const [Chart, setChart] = useState(null);\n const [loading, setLoading] = useState(false);\n \n const loadChart = async () => {\n setLoading(true);\n try {\n const chartModule = await import('chart.js/auto');\n setChart(() => chartModule.default);\n } finally {\n setLoading(false);\n }\n };\n \n return (\n
\n {!Chart && (\n \n )}\n {Chart && }\n
\n );\n}\n```\n\n**4. Consider lighter alternatives:**\n```jsx\n// Chart.js is feature-rich but heavy\n// Recharts (smaller, React-specific)\nimport { LineChart, Line } from 'recharts';\n\n// Or even lighter solutions\nimport { Sparklines, SparklinesLine } from 'react-sparklines';\n```\n\n**Pro tip:** After splitting Chart.js out, your main bundle should be 40% smaller, and your admin page will load Chart.js in parallel while users navigate to it. Win-win! 🎯\n\nWhat does your bundle analysis show as the next biggest culprit?", - }, - { - "user": "Amazing! After implementing route splitting, my initial load went from 8 seconds to 2 seconds. The next biggest issue seems to be images - they're really slowing things down.", - "assistant": 'Fantastic progress! 🚀 Images are often the silent performance killer. Here\'s how to optimize them:\n\n**1. Image format optimization:**\n```jsx\n// Modern formats with fallbacks\n\n \n \n Description\n\n\n// Or use Next.js Image component (handles this automatically)\nimport Image from \'next/image\';\nDescription\n```\n\n**2. Lazy loading (huge impact):**\n```jsx\n// Native lazy loading\nDescription\n\n// Or React library\nimport { LazyLoadImage } from \'react-lazy-load-image-component\';\n\n```\n\n**3. Responsive images:**\n```jsx\n// Serve different sizes based on viewport\n\n```\n\n**4. Image compression & tools:**\n```bash\n# Squoosh (Google) - online tool\n# ImageOptim - Mac app\n# TinyPNG - online compression\n\n# Or automate with webpack\nnpm install --save-dev imagemin-webpack-plugin\n```\n\n**5. Critical images vs non-critical:**\n```jsx\n// Preload critical above-the-fold images\n\n\n// Lazy load everything else\nDescription\n```\n\n**6. Progressive loading:**\n```jsx\n// Show low-quality placeholder first\nconst [imageLoaded, setImageLoaded] = useState(false);\n\n
\n setImageLoaded(true)}\n style={{ opacity: imageLoaded ? 1 : 0 }}\n />\n
\n```\n\n**Quick wins to try first:**\n1. Add `loading="lazy"` to all below-the-fold images\n2. Convert large images to WebP format\n3. Compress images (aim for <100KB per image)\n4. Use responsive images for different screen sizes\n\nYou should see another significant improvement! What\'s your current image strategy? 📸', - }, - { - "user": "This is incredible - my Lighthouse score went from 45 to 92! One last question: any tips for maintaining good performance as the app grows?", - "assistant": 'That\'s an amazing improvement! 🎉 From 45 to 92 is fantastic. Here\'s how to keep it that way as you scale:\n\n**1. Performance monitoring:**\n```jsx\n// Core Web Vitals tracking\nimport { getCLS, getFID, getFCP, getLCP, getTTFB } from \'web-vitals\';\n\nfunction sendToAnalytics(metric) {\n // Send to your analytics service\n console.log(metric);\n}\n\ngetCLS(sendToAnalytics);\ngetFID(sendToAnalytics);\ngetFCP(sendToAnalytics);\ngetLCP(sendToAnalytics);\ngetTTFB(sendToAnalytics);\n```\n\n**2. Performance budgets & CI:**\n```json\n// In package.json - fail builds if bundle gets too big\n"scripts": {\n "build": "react-scripts build && npm run check-size",\n "check-size": "bundlesize"\n},\n"bundlesize": [\n {\n "path": "./build/static/js/*.js",\n "maxSize": "300kb"\n }\n]\n```\n\n**3. Regular audits:**\n```bash\n# Automate Lighthouse in CI\nnpm install -g @lhci/cli\nlhci autorun\n\n# Bundle analysis on every PR\nnpm run build && npm run analyze\n```\n\n**4. Performance-first development habits:**\n```jsx\n// Always consider performance impact\n// ❌ Adding heavy library for one function\nimport _ from \'lodash\'; // 69KB for one function\n\n// ✅ Write the function yourself or use smaller alternative\nconst debounce = (fn, delay) => { /* implementation */ };\n\n// ❌ Large state objects\nconst [appState, setAppState] = useState(massiveObject);\n\n// ✅ Split state logically\nconst [user, setUser] = useState();\nconst [settings, setSettings] = useState();\n```\n\n**5. Team guidelines:**\n```markdown\n## Performance Checklist\n- [ ] New images are compressed and lazy-loaded\n- [ ] New dependencies are justified (check bundle impact)\n- [ ] New routes are code-split\n- [ ] Heavy computations are memoized\n- [ ] Lighthouse score stays above 90\n```\n\n**6. Automated performance testing:**\n```javascript\n// In your test suite\ntest(\'Homepage loads in under 2 seconds\', async () => {\n const start = Date.now();\n await render();\n expect(Date.now() - start).toBeLessThan(2000);\n});\n```\n\n**Pro tips for sustainable performance:**\n- Make Lighthouse audits part of your PR process\n- Set up bundle size alerts\n- Regular "performance debt" cleanup sessions\n- Celebrate performance wins with the team! 🎯\n\nYou\'ve built something awesome - keep it fast! What\'s your next performance challenge going to be?', - }, - ], - }, -] - - -# Tool Calling Examples -------------------------------------------------------- - -TOOL_CALLING_EXAMPLES = [ - { - "user_query": "What's the weather like in San Francisco today?", - "request": { - "model": "gpt-4", - "messages": [{"role": "user", "content": "What's the weather like in San Francisco today?"}], - "tools": [ - { - "type": "function", - "function": { - "name": "get_weather", - "description": "Get current weather information for a location", - "parameters": { - "type": "object", - "properties": { - "location": {"type": "string", "description": "City name or coordinates"}, - "units": {"type": "string", "enum": ["celsius", "fahrenheit"], "default": "fahrenheit"}, - }, - "required": ["location"], - }, - }, - } - ], - }, - "response": { - "choices": [ - { - "message": { - "role": "assistant", - "content": None, - "tool_calls": [ - { - "id": "call_abc123", - "type": "function", - "function": { - "name": "get_weather", - "arguments": '{"location": "San Francisco", "units": "fahrenheit"}', - }, - } - ], - } - } - ] - }, - "has_error": False, - }, - { - "user_query": "Can you analyze this CSV data and tell me the average sales?", - "request": { - "model": "gpt-4", - "messages": [ - { - "role": "user", - "content": "Can you analyze this CSV data and tell me the average sales?\n\nproduct,sales\nWidget A,1500\nWidget B,2300\nWidget C,1800", - } - ], - "tools": [ - { - "type": "function", - "function": { - "name": "analyze_csv_data", - "description": "Analyze CSV data and compute statistics", - "parameters": { - "type": "object", - "properties": { - "csv_content": {"type": "string", "description": "Raw CSV data"}, - "operations": { - "type": "array", - "items": {"type": "string"}, - "description": "Statistical operations to perform", - }, - }, - "required": ["csv_content", "operations"], - }, - }, - } - ], - }, - "response": { - "choices": [ - { - "message": { - "role": "assistant", - "content": None, - "tool_calls": [ - { - "id": "call_def456", - "type": "function", - "function": { - "name": "analyze_csv_data", - "arguments": '{"csv_content": "product,sales\\nWidget A,1500\\nWidget B,2300\\nWidget C,1800", "operations": ["mean", "sum", "count"]}', - }, - } - ], - } - } - ] - }, - "has_error": False, - }, - { - "user_query": "Send an email to john@example.com about the project update", - "request": { - "model": "gpt-4", - "messages": [{"role": "user", "content": "Send an email to john@example.com about the project update"}], - "tools": [ - { - "type": "function", - "function": { - "name": "send_email", - "description": "Send an email message", - "parameters": { - "type": "object", - "properties": { - "to": {"type": "string", "description": "Recipient email address"}, - "subject": {"type": "string", "description": "Email subject"}, - "body": {"type": "string", "description": "Email body content"}, - }, - "required": ["to", "subject", "body"], - }, - }, - } - ], - }, - "response": { - "choices": [ - { - "message": { - "role": "assistant", - "content": "I'll send an email to john@example.com about the project update.", - "tool_calls": [ - { - "id": "call_ghi789", - "type": "function", - "function": { - "name": "send_email", - "arguments": '{"to": "john@example.com", "subject": "Project Update", "body": "Hi John,\\n\\nI wanted to provide you with an update on our current project status. Please let me know if you have any questions or need additional details.\\n\\nBest regards"}', - }, - } - ], - } - } - ] - }, - "has_error": True, # Missing required information - should ask for specifics - "reviewer_comment": "Model should have asked for specific project details and update content before sending email.", - }, - { - "user_query": "Calculate the compound interest for $1000 at 5% for 10 years", - "request": { - "model": "gpt-4", - "messages": [{"role": "user", "content": "Calculate the compound interest for $1000 at 5% for 10 years"}], - "tools": [ - { - "type": "function", - "function": { - "name": "calculate_compound_interest", - "description": "Calculate compound interest", - "parameters": { - "type": "object", - "properties": { - "principal": {"type": "number", "description": "Initial amount"}, - "rate": {"type": "number", "description": "Annual interest rate (as decimal)"}, - "time": {"type": "number", "description": "Time period in years"}, - "compounds_per_year": { - "type": "number", - "description": "Compounding frequency per year", - "default": 1, - }, - }, - "required": ["principal", "rate", "time"], - }, - }, - } - ], - }, - "response": { - "choices": [ - { - "message": { - "role": "assistant", - "content": None, - "tool_calls": [ - { - "id": "call_jkl012", - "type": "function", - "function": { - "name": "calculate_compound_interest", - "arguments": '{"principal": 1000, "rate": 0.5, "time": 10}', - }, - } - ], - } - } - ] - }, - "has_error": True, # Wrong rate (0.5 instead of 0.05) - "reviewer_comment": "Model used incorrect rate conversion. 5% should be 0.05, not 0.5.", - }, -] - - -# Query Rewriting Examples -------------------------------------------------------- - -QUERY_REWRITING_EXAMPLES = [ - { - "user_query": "I'm trying to figure out how to make my code run faster when I'm doing machine learning stuff with Python and I think there might be some libraries or techniques that could help but I'm not sure which ones are best for my specific use case", - "request": { - "model": "gpt-4", - "messages": [ - { - "role": "system", - "content": "You are a query rewriting assistant. Rewrite the user's complex question into a cleaner, more focused query suitable for retrieval systems. Extract the key concepts and present them in a clear, searchable format.", - }, - { - "role": "user", - "content": "I'm trying to figure out how to make my code run faster when I'm doing machine learning stuff with Python and I think there might be some libraries or techniques that could help but I'm not sure which ones are best for my specific use case", - }, - ], - }, - "response": { - "choices": [ - { - "message": { - "role": "assistant", - "content": "Python machine learning performance optimization libraries and techniques", - } - } - ] - }, - "has_error": False, - }, - { - "user_query": "My manager wants me to create some kind of dashboard or visualization thing that shows our sales data but also needs to be interactive and I'm not really sure what tools to use or how to get started with this kind of project", - "request": { - "model": "gpt-4", - "messages": [ - { - "role": "system", - "content": "You are a query rewriting assistant. Rewrite the user's complex question into a cleaner, more focused query suitable for retrieval systems. Extract the key concepts and present them in a clear, searchable format.", - }, - { - "role": "user", - "content": "My manager wants me to create some kind of dashboard or visualization thing that shows our sales data but also needs to be interactive and I'm not really sure what tools to use or how to get started with this kind of project", - }, - ], - }, - "response": { - "choices": [ - { - "message": { - "role": "assistant", - "content": "Interactive sales dashboard creation tools and getting started guide", - } - } - ] - }, - "has_error": False, - }, - { - "user_query": "So I've been working on this React project and I keep running into issues with state management and I know there are different approaches like Redux and Context API but I'm confused about when to use which one and also how to handle async data fetching in a clean way", - "request": { - "model": "gpt-4", - "messages": [ - { - "role": "system", - "content": "You are a query rewriting assistant. Rewrite the user's complex question into a cleaner, more focused query suitable for retrieval systems. Extract the key concepts and present them in a clear, searchable format.", - }, - { - "role": "user", - "content": "So I've been working on this React project and I keep running into issues with state management and I know there are different approaches like Redux and Context API but I'm confused about when to use which one and also how to handle async data fetching in a clean way", - }, - ], - }, - "response": { - "choices": [ - { - "message": { - "role": "assistant", - "content": "React state management: Redux vs Context API comparison and async data fetching best practices", - } - } - ] - }, - "has_error": False, - }, - { - "user_query": "I need help understanding databases and SQL and all that stuff because I'm working on a web application that needs to store user information and product data and I want to make sure I design the database correctly from the beginning so I don't run into problems later", - "request": { - "model": "gpt-4", - "messages": [ - { - "role": "system", - "content": "You are a query rewriting assistant. Rewrite the user's complex question into a cleaner, more focused query suitable for retrieval systems. Extract the key concepts and present them in a clear, searchable format.", - }, - { - "role": "user", - "content": "I need help understanding databases and SQL and all that stuff because I'm working on a web application that needs to store user information and product data and I want to make sure I design the database correctly from the beginning so I don't run into problems later", - }, - ], - }, - "response": { - "choices": [ - { - "message": { - "role": "assistant", - "content": "Database design best practices for web applications with user and product data", - } - } - ] - }, - "has_error": True, # Too generic, should preserve more specific context - "reviewer_comment": "The rewritten query is too generic. Should preserve context about SQL learning and avoiding future problems.", - }, -] - - -CLEAR_PAGE_SIZE = 100 -# Bounds the list+delete loop so a misbehaving server cannot loop forever. -# At CLEAR_PAGE_SIZE=100 this covers 100k entries before we give up. -MAX_CLEAR_PAGES = 1000 - - -def clear_entries_via_api(api_url: str, timeout: int = 30) -> None: - """Delete every entry in the workspace via the v2 API.""" - logger.info("🧹 Deleting all entries at %s", api_url) - - deleted = 0 - with httpx.Client(timeout=timeout) as client: - for _ in range(MAX_CLEAR_PAGES): - # Always page=1: each iteration deletes the current page, so the next - # "first page" is the next batch of still-existing entries. - list_resp = client.get(api_url, params={"page": 1, "page_size": CLEAR_PAGE_SIZE}) - list_resp.raise_for_status() - entries = list_resp.json().get("data") or [] - if not entries: - break - - for entry in entries: - entry_id = entry["id"] - del_resp = client.delete(f"{api_url.rstrip('/')}/{entry_id}") - del_resp.raise_for_status() - deleted += 1 - else: - raise RuntimeError( - f"clear_entries_via_api exceeded {MAX_CLEAR_PAGES} pages " - f"(~{MAX_CLEAR_PAGES * CLEAR_PAGE_SIZE} entries); " - "deletes may not be taking effect." - ) - - logger.info("✅ Deleted %d entries", deleted) - - -# User Feedback Data -------------------------------------------------------- - -# User feedback includes: thumbs up/down, ratings (1-5), opinions, and user-provided rewrites -USER_FEEDBACK_EXAMPLES = [ - {"type": "thumbs_up", "payload": {"thumb": "up", "opinion": "This was really helpful! Exactly what I needed."}}, - {"type": "thumbs_up", "payload": {"thumb": "up", "opinion": "Great explanation, very clear and detailed."}}, - {"type": "thumbs_up", "payload": {"thumb": "up", "opinion": "Perfect! This solved my problem immediately."}}, - {"type": "thumbs_down", "payload": {"thumb": "down", "opinion": "This doesn't really answer my question."}}, - {"type": "thumbs_down", "payload": {"thumb": "down", "opinion": "Too complicated, I need a simpler explanation."}}, - { - "type": "rewrite", - "payload": { - "thumb": "down", - "opinion": "Close but not quite right.", - "rewrite": "Here's a better version: The main issue is that you need to check the array bounds before accessing elements. Use `if (index < array.length)` to prevent errors.", - }, - }, - { - "type": "rewrite", - "payload": { - "thumb": "down", - "opinion": "Good start but missing important details.", - "rewrite": "Your explanation is correct, but you should also mention that React Hook Form handles form validation more efficiently by reducing re-renders, which is the main performance benefit.", - }, - }, - {"type": "rating", "payload": {"rating": 4.5, "opinion": "Very helpful response, just missing a small detail."}}, - {"type": "rating", "payload": {"rating": 5.0, "opinion": "Excellent! This is exactly what I was looking for."}}, - {"type": "rating", "payload": {"rating": 2.0, "opinion": "Somewhat helpful but not comprehensive enough."}}, -] - - -def create_user_feedback_event_data() -> Dict[str, Any]: - """Create realistic user feedback event data for API.""" - feedback = random.choice(USER_FEEDBACK_EXAMPLES) - - # Create event data matching UserFeedbackEvent schema - event_data: Dict[str, Any] = {"event_type": "user_feedback", "created_at": datetime.now(timezone.utc).isoformat()} - - # Add feedback fields based on the type - payload = feedback["payload"] - if isinstance(payload, dict): - if payload.get("thumb"): - event_data["thumb"] = str(payload["thumb"]) - if payload.get("rating"): - event_data["rating"] = float(payload["rating"]) - if payload.get("opinion"): - event_data["opinion"] = str(payload["opinion"]) - if payload.get("rewrite"): - event_data["rewrite"] = str(payload["rewrite"]) - - # Return EventsCreate format with events array - return {"events": [event_data]} - - -def add_user_feedback_via_api(api_url: str, entry_id: str, timeout: int = 30) -> bool: - """Add user feedback event via API call.""" - events_url = f"{api_url.rstrip('/')}/{entry_id}/events" - - try: - feedback_data = create_user_feedback_event_data() - - with httpx.Client(timeout=timeout) as client: - response = client.post(events_url, json=feedback_data) - response.raise_for_status() # Let it error on 404 or other failures - - logger.info("✅ Added user feedback for entry %s", entry_id) - return True - - except httpx.HTTPStatusError as e: - if e.response.status_code == 404: - logger.error("❌ Events endpoint not found for entry %s. URL: %s", entry_id, events_url) - else: - logger.error("❌ Failed to add user feedback for %s: HTTP %s", entry_id, e.response.status_code) - return False - except Exception as e: - logger.error("❌ Failed to add user feedback for %s: %s", entry_id, e) - return False - - -def create_corrected_response_override(original_example: Dict[str, Any]) -> Optional[Dict[str, Any]]: - """Create a corrected response_override for examples with errors.""" - if not original_example.get("has_error", False): - return None - - user_query = original_example.get("user_query", "") - - # Email example - should ask for specifics instead of sending - if "john@example.com" in user_query and "project update" in user_query: - return { - "choices": [ - { - "message": { - "role": "assistant", - "content": "I'd be happy to help you send an email to john@example.com about a project update. However, I need more specific information to create a meaningful email:\n\n1. What specific project are you referring to?\n2. What key updates or milestones should be included?\n3. What's the current status or recent progress?\n4. Are there any specific actions needed from John?\n5. What tone would be appropriate (formal, casual, urgent)?\n\nOnce you provide these details, I can craft a comprehensive and relevant project update email.", - "tool_calls": None, - } - } - ] - } - - # Compound interest example - fix the rate conversion - elif "compound interest" in user_query and "$1000" in user_query: - return { - "choices": [ - { - "message": { - "role": "assistant", - "content": None, - "tool_calls": [ - { - "id": "call_jkl012", - "type": "function", - "function": { - "name": "calculate_compound_interest", - "arguments": '{"principal": 1000, "rate": 0.05, "time": 10}', - }, - } - ], - } - } - ] - } - - # Query rewriting example - preserve more context - elif "database" in user_query and "SQL" in user_query: - return { - "choices": [ - { - "message": { - "role": "assistant", - "content": "SQL database design fundamentals for web applications: avoiding common mistakes when storing user and product data", - } - } - ] - } - - return None - - -def add_reviewer_annotation_via_api( - api_url: str, - entry_id: str, - original_example: Optional[Dict[str, Any]] = None, - reviewer_comment: Optional[str] = None, - timeout: int = 30, -) -> bool: - """Add reviewer annotation event via API call.""" - events_url = f"{api_url.rstrip('/')}/{entry_id}/events" - - try: - # Create reviewer annotation data - review_payload: Dict[str, Any] = { - "thumb": "down" if reviewer_comment else "up", - "opinion": reviewer_comment or "Good response, accurate and helpful.", - } - - # Note: rewrite field is for end-user suggestions, not reviewer corrections - # Reviewers use response_override for corrected responses and opinion for comments - - # Add response_override with corrected response if available - if original_example: - corrected_response = create_corrected_response_override(original_example) - if corrected_response: - review_payload["response_override"] = corrected_response - - event_data = {"event_type": "reviewer_annotation", **review_payload} - - # Wrap in EventsCreate format - annotation_data = {"events": [event_data]} - - with httpx.Client(timeout=timeout) as client: - response = client.post(events_url, json=annotation_data) - response.raise_for_status() # Let it error on 404 or other failures - - logger.info("✅ Added reviewer annotation for entry %s", entry_id) - return True - - except httpx.HTTPStatusError as e: - if e.response.status_code == 404: - logger.error("❌ Events endpoint not found for entry %s. URL: %s", entry_id, events_url) - else: - logger.error("❌ Failed to add reviewer annotation for %s: HTTP %s", entry_id, e.response.status_code) - return False - except Exception as e: - logger.error("❌ Failed to add reviewer annotation for %s: %s", entry_id, e) - return False - - -# Data Generation Functions -------------------------------------------------------- - - -def _build_context(workspace: str, app_id: str, task_id: str, thread_id: str) -> Dict[str, Any]: - return { - "app": f"{workspace}/{app_id}", - "task": task_id, - "thread_id": thread_id, - } - - -def create_conversational_entry_data( - workspace: str, app_id: str, task_id: str, thread_id: str, turn_number: int, conversation_data: Dict[str, Any] -) -> Dict[str, Any]: - """Create entry data for progressive conversation turns. - - The request carries prior turns plus the current user message; the response - carries the current assistant reply. This matches the request→response shape - used by generate_test_dataset.create_conversation_threads. - """ - messages: List[Dict[str, str]] = [] - for i in range(turn_number - 1): - prior = conversation_data["turns"][i] - messages.append({"role": "user", "content": prior["user"]}) - messages.append({"role": "assistant", "content": prior["assistant"]}) - - current_turn = conversation_data["turns"][turn_number - 1] - messages.append({"role": "user", "content": current_turn["user"]}) - - return { - "data": { - "request": { - "model": "gpt-4", - "messages": messages, - }, - "response": { - "choices": [ - { - "message": { - "role": "assistant", - "content": current_turn["assistant"], - } - } - ] - }, - }, - "context": _build_context(workspace, app_id, task_id, thread_id), - } - - -def create_tool_calling_entry_data( - workspace: str, app_id: str, task_id: str, thread_id: str, tool_example: Dict[str, Any] -) -> Dict[str, Any]: - """Create entry data for tool calling examples.""" - return { - "data": { - "request": tool_example["request"], - "response": tool_example["response"], - }, - "context": _build_context(workspace, app_id, task_id, thread_id), - } - - -def create_query_rewriting_entry_data( - workspace: str, app_id: str, task_id: str, thread_id: str, rewriting_example: Dict[str, Any] -) -> Dict[str, Any]: - """Create entry data for query rewriting examples.""" - return { - "data": { - "request": rewriting_example["request"], - "response": rewriting_example["response"], - }, - "context": _build_context(workspace, app_id, task_id, thread_id), - } - - -def seed_api_diverse( - api_url: str, - workspace: str, - num_conversations: int = 5, - num_tool_examples: int = 15, - num_query_examples: int = 10, - timeout: int = 30, -) -> None: - """Seed the API with diverse, realistic entry records (API mode).""" - logger.info( - "🌱 Seeding API with diverse entries: %d conversations, %d tool examples, %d query examples to %s", - num_conversations, - num_tool_examples, - num_query_examples, - api_url, - ) - - successful_requests = 0 - failed_requests = 0 - - with httpx.Client(timeout=timeout) as client: - # 1. Create conversational support entries (10 turns each) - for conv_idx in range(num_conversations): - conversation = random.choice(CONVERSATION_TOPICS) - thread_id = f"conv_thread_{conv_idx + 1}" - app_id = f"support_client_{(conv_idx % 3) + 1}" - - for turn in range(1, 11): # 10 turns - entry_data = create_conversational_entry_data( - workspace=workspace, - app_id=app_id, - task_id="conversational-support", - thread_id=thread_id, - turn_number=turn, - conversation_data=conversation, - ) - - try: - response = client.post(api_url, json=entry_data) - response.raise_for_status() - - response_data = response.json() - entry_id = response_data.get("id", f"conv_{conv_idx}_{turn}") - - logger.info("Created conversation entry %s (turn %d/10)", entry_id, turn) - successful_requests += 1 - - # Add user feedback on turns 3, 5, and 8 - if turn in [3, 5, 8]: - add_user_feedback_via_api(api_url, entry_id, timeout) - - except Exception as e: - logger.error("Error creating conversation entry %d-%d: %s", conv_idx, turn, e) - failed_requests += 1 - - # 2. Create tool calling entries - for tool_idx in range(num_tool_examples): - tool_example = random.choice(TOOL_CALLING_EXAMPLES) - thread_id = f"tool_thread_{tool_idx + 1}" - app_id = f"tool_client_{(tool_idx % 2) + 1}" - - entry_data = create_tool_calling_entry_data( - workspace=workspace, - app_id=app_id, - task_id="tool-calling-examples", - thread_id=thread_id, - tool_example=tool_example, - ) - - try: - response = client.post(api_url, json=entry_data) - response.raise_for_status() - - response_data = response.json() - entry_id = response_data.get("id", f"tool_{tool_idx}") - - logger.info("Created tool calling entry %s", entry_id) - successful_requests += 1 - - # Add user feedback for some tool examples (30% chance) - if random.random() < 0.3: - add_user_feedback_via_api(api_url, entry_id, timeout) - - # Add reviewer annotation if example has errors - add_reviewer = bool(tool_example.get("has_error", False)) - reviewer_comment = ( - str(tool_example.get("reviewer_comment")) - if add_reviewer and tool_example.get("reviewer_comment") - else None - ) - if add_reviewer: - add_reviewer_annotation_via_api(api_url, entry_id, tool_example, reviewer_comment, timeout) - - except Exception as e: - logger.error("Error creating tool calling entry %d: %s", tool_idx, e) - failed_requests += 1 - - # 3. Create query rewriting entries - for query_idx in range(num_query_examples): - query_example = random.choice(QUERY_REWRITING_EXAMPLES) - thread_id = f"query_thread_{query_idx + 1}" - app_id = f"retrieval_client_{(query_idx % 2) + 1}" - - entry_data = create_query_rewriting_entry_data( - workspace=workspace, - app_id=app_id, - task_id="query-rewriting", - thread_id=thread_id, - rewriting_example=query_example, - ) - - try: - response = client.post(api_url, json=entry_data) - response.raise_for_status() - - response_data = response.json() - entry_id = response_data.get("id", f"query_{query_idx}") - - logger.info("Created query rewriting entry %s", entry_id) - successful_requests += 1 - - # Add user feedback for some query examples (25% chance) - if random.random() < 0.25: - add_user_feedback_via_api(api_url, entry_id, timeout) - - # Add reviewer annotation if example has errors - add_reviewer = bool(query_example.get("has_error", False)) - reviewer_comment = ( - str(query_example.get("reviewer_comment")) - if add_reviewer and query_example.get("reviewer_comment") - else None - ) - if add_reviewer: - add_reviewer_annotation_via_api(api_url, entry_id, query_example, reviewer_comment, timeout) - - except Exception as e: - logger.error("Error creating query rewriting entry %d: %s", query_idx, e) - failed_requests += 1 - - logger.info("✅ Successfully created %d diverse entries via API", successful_requests) - if failed_requests > 0: - logger.warning("⚠️ Failed to create %d entries", failed_requests) - - -def main(): - """Main entry point with command line argument parsing.""" - parser = argparse.ArgumentParser(description="Seed intake service with diverse, realistic entry records") - parser.add_argument( - "--api-url", - default="http://localhost:8080", - help="Base URL of the NeMo Platform gateway (default: http://localhost:8080)", - ) - parser.add_argument( - "--workspace", - default="default", - help=( - "Target workspace. Used both as the route segment " - "(.../workspaces//entries) and in each entry's context.app " - "(default: default)" - ), - ) - parser.add_argument( - "--conversations", type=int, default=3, help="Number of conversation threads to create (default: 3)" - ) - parser.add_argument( - "--tool-examples", type=int, default=10, help="Number of tool calling examples to create (default: 10)" - ) - parser.add_argument( - "--query-examples", type=int, default=8, help="Number of query rewriting examples to create (default: 8)" - ) - parser.add_argument("--timeout", type=int, default=30, help="HTTP timeout in seconds (default: 30)") - parser.add_argument( - "--clear", action="store_true", help="Delete all existing entries in the workspace before seeding" - ) - parser.add_argument("--verbose", action="store_true", help="Enable verbose logging") - - args = parser.parse_args() - - log_level = logging.DEBUG if args.verbose else logging.INFO - logging.basicConfig(level=log_level, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s") - - try: - api_url = entries_url(args.api_url, args.workspace) - - if args.clear: - clear_entries_via_api(api_url, args.timeout) - - seed_api_diverse( - api_url, - args.workspace, - args.conversations, - args.tool_examples, - args.query_examples, - args.timeout, - ) - except KeyboardInterrupt: - logger.info("🛑 Seeding interrupted by user") - except Exception as e: - logger.exception("❌ Seeding failed: %s", str(e)) - exit(1) - - -if __name__ == "__main__": - main() diff --git a/services/intake/src/nmp/intake/api/v2/apps/__init__.py b/services/intake/src/nmp/intake/api/v2/apps/__init__.py deleted file mode 100644 index b5d8b6d0a2..0000000000 --- a/services/intake/src/nmp/intake/api/v2/apps/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. -# SPDX-License-Identifier: Apache-2.0 - -"""Apps API resource.""" diff --git a/services/intake/src/nmp/intake/api/v2/apps/endpoints.py b/services/intake/src/nmp/intake/api/v2/apps/endpoints.py deleted file mode 100644 index 808466f8ca..0000000000 --- a/services/intake/src/nmp/intake/api/v2/apps/endpoints.py +++ /dev/null @@ -1,174 +0,0 @@ -# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. -# SPDX-License-Identifier: Apache-2.0 - -"""API endpoints for Apps using EntityClient pattern.""" - -from fastapi import APIRouter, Depends, HTTPException, Query, status -from nmp.common.api.common import Page -from nmp.common.api.parsed_filter import ParsedFilter, make_filter_dep -from nmp.common.api.utils import generate_openapi_extra_params -from nmp.common.entities import ( - EntityClient, - EntityConflictError, - EntityNotFoundError, -) -from nmp.common.service.dependencies import get_entity_client -from nmp.intake.entities import App as AppEntity - -from .schemas import App, AppFilter, AppInput, AppSortField, AppUpdate - -router = APIRouter() - -API_TAG = "Apps" - - -@router.get( - "/v2/workspaces/{workspace}/apps", - response_model=Page[App], - tags=[API_TAG], - response_model_exclude_none=True, - openapi_extra=generate_openapi_extra_params( - filter_schema=AppFilter, - filter_description="Filter apps by name, description, project, created_at, and updated_at.", - ), -) -async def list_apps( - workspace: str, - entities_client: EntityClient = Depends(get_entity_client), - page: int = Query(default=1, description="Page number."), - page_size: int = Query(default=10, description="Page size."), - sort: AppSortField = Query( - default="created_at", - description="""The field to sort by. To sort in decreasing order, use `-` in front of the field name.""", - ), - parsed: ParsedFilter = Depends(make_filter_dep(AppFilter)), -) -> Page[App]: - """List all apps with filtering capabilities.""" - res = await entities_client.list( - AppEntity, - page=page, - page_size=page_size, - sort=sort, - workspace=workspace, - filter_operation=parsed.operation, - ) - - data_dicts = [item.model_dump(by_alias=True, mode="json") for item in res.data] - - return Page[App]( - data=data_dicts, - pagination=res.pagination.model_dump(), - sort=sort, - filter=None, - ) - - -@router.post( - "/v2/workspaces/{workspace}/apps", - responses={ - 200: {"description": "Successful Response"}, - 409: {"description": "App already exists"}, - 422: {"description": "Validation Error"}, - }, - response_model=App, - tags=[API_TAG], - status_code=status.HTTP_201_CREATED, -) -async def create_app( - workspace: str, - app_input: AppInput, - entities_client: EntityClient = Depends(get_entity_client), -) -> App: - """Create a new app.""" - # Convert input to entity - app_entity = AppEntity( - name=app_input.name, - workspace=workspace, - description=app_input.description, - locked=app_input.locked if hasattr(app_input, "locked") else False, - ) - - try: - created = await entities_client.create(app_entity) - except EntityConflictError: - raise HTTPException( - status_code=status.HTTP_409_CONFLICT, - detail=f"App {app_entity.workspace}/{app_entity.name} already exists", - ) - - return App(**created.model_dump(by_alias=True, mode="json")) - - -@router.get( - "/v2/workspaces/{workspace}/apps/{name}", - response_model=App, - tags=[API_TAG], -) -async def get_app( - workspace: str, - name: str, - entities_client: EntityClient = Depends(get_entity_client), -) -> App: - """Get a specific app by workspace and name.""" - try: - app_entity = await entities_client.get(AppEntity, name=name, workspace=workspace) - except EntityNotFoundError: - raise HTTPException( - status_code=status.HTTP_404_NOT_FOUND, - detail=f"App {workspace}/{name} not found", - ) - - return App(**app_entity.model_dump(by_alias=True, mode="json")) - - -@router.patch( - "/v2/workspaces/{workspace}/apps/{name}", - response_model=App, - tags=[API_TAG], -) -async def update_app( - workspace: str, - name: str, - app_update: AppUpdate, - entities_client: EntityClient = Depends(get_entity_client), -) -> App: - """Update an existing app.""" - try: - app_entity = await entities_client.get(AppEntity, name=name, workspace=workspace) - except EntityNotFoundError: - raise HTTPException( - status_code=status.HTTP_404_NOT_FOUND, - detail=f"App {workspace}/{name} not found", - ) - - # Apply updates (only update fields that were provided) - update_data = app_update.model_dump(exclude_unset=True) - for field, value in update_data.items(): - setattr(app_entity, field, value) - - # Save updated entity - updated = await entities_client.update(app_entity) - - return App(**updated.model_dump(by_alias=True, mode="json")) - - -@router.delete( - "/v2/workspaces/{workspace}/apps/{name}", - status_code=status.HTTP_204_NO_CONTENT, - tags=[API_TAG], -) -async def delete_app( - workspace: str, - name: str, - entities_client: EntityClient = Depends(get_entity_client), -) -> None: - """Delete an app.""" - try: - await entities_client.get(AppEntity, name=name, workspace=workspace) - except EntityNotFoundError: - raise HTTPException( - status_code=status.HTTP_404_NOT_FOUND, - detail=f"App {workspace}/{name} not found", - ) - - await entities_client.delete(AppEntity, name, workspace=workspace) diff --git a/services/intake/src/nmp/intake/api/v2/apps/schemas.py b/services/intake/src/nmp/intake/api/v2/apps/schemas.py deleted file mode 100644 index 81be309308..0000000000 --- a/services/intake/src/nmp/intake/api/v2/apps/schemas.py +++ /dev/null @@ -1,81 +0,0 @@ -# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. -# SPDX-License-Identifier: Apache-2.0 - -"""API schemas for App endpoints.""" - -from datetime import datetime -from enum import StrEnum -from typing import Optional - -from nmp.common.entities.values import DatetimeFilter, Filter -from pydantic import BaseModel, Field - -# --------------------------------------------------------------------------- -# App Request/Response schemas -# --------------------------------------------------------------------------- - - -class AppInput(BaseModel): - """Schema for creating a new App.""" - - name: str = Field(..., description="App name (unique within workspace)") - # Note: workspace comes from workspace in the URL path, not the body - description: str | None = Field(default=None, description="App description") - project: str | None = Field(default=None, description="The name of the project associated with this app") - locked: bool = Field( - default=False, - description="If true, this record cannot be automatically updated when entries are ingested.", - ) - - -class AppUpdate(BaseModel): - """Schema for updating an existing App.""" - - description: str | None = Field(default=None, description="App description") - project: str | None = Field(default=None, description="The name of the project associated with this app") - locked: bool | None = Field(default=None, description="Lock status") - - -class App(BaseModel): - """Schema for App responses.""" - - id: str = Field(..., description="Unique identifier") - name: str = Field(..., description="App name") - workspace: str = Field(..., description="Workspace identifier") - description: str | None = Field(default=None, description="App description") - project: str | None = Field(default=None, description="The name of the project associated with this app") - locked: bool = Field(default=False, description="Lock status") - created_at: datetime | None = Field(default=None, description="Creation timestamp") - updated_at: datetime | None = Field(default=None, description="Last update timestamp") - - -# --------------------------------------------------------------------------- -# Sort enums -# --------------------------------------------------------------------------- - - -class AppSortField(StrEnum): - """Sort fields for Apps.""" - - CREATED_AT_ASC = "created_at" - CREATED_AT_DESC = "-created_at" - NAME_ASC = "name" - NAME_DESC = "-name" - UPDATED_AT_ASC = "updated_at" - UPDATED_AT_DESC = "-updated_at" - - -# --------------------------------------------------------------------------- -# Filter schemas -# --------------------------------------------------------------------------- - - -class AppFilter(Filter): - """Filter for Apps.""" - - workspace: Optional[str] = Field(None, description="Filter by workspace id.") - name: Optional[str] = Field(None, description="Filter by app name.") - project: Optional[str] = Field(None, description="Filter by project name.") - description: Optional[str] = Field(None, description="Filter by app description.") - created_at: Optional[DatetimeFilter] = Field(None, description="Filter entities based on creation date.") - updated_at: Optional[DatetimeFilter] = Field(None, description="Filter entities based on update date.") diff --git a/services/intake/src/nmp/intake/api/v2/entries/__init__.py b/services/intake/src/nmp/intake/api/v2/entries/__init__.py deleted file mode 100644 index 4789637fc6..0000000000 --- a/services/intake/src/nmp/intake/api/v2/entries/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. -# SPDX-License-Identifier: Apache-2.0 - -"""Entries API resource.""" diff --git a/services/intake/src/nmp/intake/api/v2/entries/endpoints.py b/services/intake/src/nmp/intake/api/v2/entries/endpoints.py deleted file mode 100644 index badad08d2e..0000000000 --- a/services/intake/src/nmp/intake/api/v2/entries/endpoints.py +++ /dev/null @@ -1,512 +0,0 @@ -# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. -# SPDX-License-Identifier: Apache-2.0 - -"""API endpoints for Entries using EntityClient pattern.""" - -from typing import Optional - -from fastapi import APIRouter, Depends, HTTPException, Query, status -from nemo_platform_plugin.filter_ops import FilterOperation -from nmp.common.api.common import Page, PaginationData -from nmp.common.api.parsed_filter import ParsedFilter, make_filter_dep -from nmp.common.api.utils import generate_openapi_extra_params -from nmp.common.entities import EntityClient, EntityConflictError, EntityNotFoundError -from nmp.common.service.dependencies import get_entity_client -from nmp.intake.entities import App as AppEntity -from nmp.intake.entities import Entry as EntryEntity -from nmp.intake.entities import Task as TaskEntity -from nmp.intake.entities import Usage, UserRating - -from .schemas import Entry, EntryFilter, EntryInput, EntrySortField, EntryUpdate, EventsCreateRequest - -router = APIRouter() - -API_TAG = "Entries" - -# Core entities `GET .../entities/{entity_type}` caps `page_size` at 1000 (Query le=1000). -_ENTITIES_LIST_MAX_PAGE_SIZE = 1000 - - -# --------------------------------------------------------------------------- -# Helper Functions -# --------------------------------------------------------------------------- - - -async def _fetch_all_entry_entities( - entities_client: EntityClient, - *, - workspace: str, - sort: EntrySortField, - filter_operation: FilterOperation | None, -) -> list: - """Load all matching intake entries by paging entity-store list calls. - - ``longest_per_thread`` needs every row in the workspace filter to group by - ``thread_id``, but the entities API rejects ``page_size`` > 1000 with HTTP 422. - """ - page_size = _ENTITIES_LIST_MAX_PAGE_SIZE - first = await entities_client.list( - EntryEntity, - page=1, - page_size=page_size, - sort=sort, - workspace=workspace, - filter_operation=filter_operation, - ) - all_rows = list(first.data) - total_pages = first.pagination.total_pages - for page_num in range(2, total_pages + 1): - page_res = await entities_client.list( - EntryEntity, - page=page_num, - page_size=page_size, - sort=sort, - workspace=workspace, - filter_operation=filter_operation, - ) - all_rows.extend(page_res.data) - return all_rows - - -def _parse_entry_id(entry_id: str) -> tuple[str, bool]: - """Parse entry_id to check if it's an external_id reference. - - Returns: - tuple: (actual_id, is_external) where is_external is True if using external: prefix - """ - if entry_id.startswith("external:"): - return entry_id[9:], True # Remove "external:" prefix - return entry_id, False - - -async def _get_entry_by_id_or_external( - entities_client: EntityClient, - entry_id: str, - workspace: str, -) -> Optional[EntryEntity]: - """Get entry by ID or external_id depending on prefix.""" - actual_id, is_external = _parse_entry_id(entry_id) - - try: - if is_external: - # Search by external_id - return await entities_client.get_by_field(EntryEntity, workspace=workspace, external_id=actual_id) - else: - # Get by primary key (entity ID) - return await entities_client.get_by_id(EntryEntity, actual_id) - except EntityNotFoundError: - return None - - -# --------------------------------------------------------------------------- -# Entry Endpoints -# --------------------------------------------------------------------------- - - -@router.get( - "/v2/workspaces/{workspace}/entries", - response_model=Page[Entry], - tags=[API_TAG], - openapi_extra=generate_openapi_extra_params( - filter_schema=EntryFilter, - filter_description=( - "Filter entries by id, project, external_id, created_at, updated_at, " - "usage fields (model), context fields, and user_rating fields." - ), - ), -) -async def list_entries( - workspace: str, - entities_client: EntityClient = Depends(get_entity_client), - page: int = Query(default=1, description="Page number."), - page_size: int = Query(default=10, description="Page size."), - sort: EntrySortField = Query( - default="created_at", - description="""The field to sort by. To sort in decreasing order, use `-` in front of the field name.""", - ), - parsed: ParsedFilter = Depends(make_filter_dep(EntryFilter)), -) -> Page[Entry]: - """List all entries with filtering capabilities. - - When longest_per_thread=true is set in filters, returns only the longest entry - (by message count) for each unique thread_id. - """ - # Extract longest_per_thread before forwarding to entity store - longest_per_thread_val = parsed.remove("longest_per_thread") - longest_per_thread = longest_per_thread_val in (True, "true") - - # Workspace from the path takes precedence over any filter value. - parsed.remove("workspace") - - if longest_per_thread: - # Need full filtered corpus to pick longest-per-thread; entities API max page_size is 1000. - entry_rows = await _fetch_all_entry_entities( - entities_client, - workspace=workspace, - sort=sort, - filter_operation=parsed.operation, - ) - - # Group entries by thread_id and keep only the longest per thread - from collections import defaultdict - - threads = defaultdict(list) - - for entry in entry_rows: - if entry.context and entry.context.thread_id: - threads[entry.context.thread_id].append(entry) - - # For each thread, keep only the entry with most messages - filtered_entries = [] - for thread_entries in threads.values(): - - def get_message_count(entry): - """Count messages in an entry.""" - try: - if entry.data and hasattr(entry.data, "request") and hasattr(entry.data.request, "messages"): - return len(entry.data.request.messages) - elif entry.data and isinstance(entry.data, dict): - request = entry.data.get("request", {}) - if isinstance(request, dict): - messages = request.get("messages", []) - return len(messages) if isinstance(messages, list) else 0 - except Exception: - pass - return 0 - - # Get the entry with the most messages - longest_entry = max(thread_entries, key=get_message_count, default=None) - if longest_entry: - filtered_entries.append(longest_entry) - - # Apply pagination to filtered results - total_results = len(filtered_entries) - start_idx = (page - 1) * page_size - end_idx = start_idx + page_size - paginated_entries = filtered_entries[start_idx:end_idx] - - data_dicts = [entry.model_dump(by_alias=True, mode="json") for entry in paginated_entries] - - return Page[Entry]( - data=data_dicts, - pagination=PaginationData( - page=page, - page_size=page_size, - current_page_size=len(paginated_entries), - total_results=total_results, - total_pages=(total_results + page_size - 1) // page_size, - ), - sort=sort, - filter=None, - ) - else: - # Normal listing without longest_per_thread - res = await entities_client.list( - EntryEntity, - page=page, - page_size=page_size, - sort=sort, - workspace=workspace, - filter_operation=parsed.operation, - ) - - data_dicts = [entry.model_dump(by_alias=True, mode="json") for entry in res.data] - - return Page[Entry]( - data=data_dicts, - pagination=res.pagination.model_dump(), - sort=sort, - filter=None, - ) - - -def _auto_register_app_and_task_async( - entities_client: EntityClient, - app_ref: str, - task_name: Optional[str], - workspace: str, -) -> None: - """Auto-register app and task if they don't exist - fire and forget.""" - import asyncio - - # Parse app reference (format: "workspace/app_name") - if "/" in app_ref: - app_workspace, app_name = app_ref.split("/", 1) - else: - app_workspace = workspace - app_name = app_ref - - # Build full app reference for task association - full_app_ref = f"{app_workspace}/{app_name}" - - # Fire off inserts without waiting - async def try_insert_app(): - try: - app = AppEntity( - name=app_name, - workspace=app_workspace, - description="Auto-registered from entry", - ) - await entities_client.create(app) - except EntityConflictError: - pass # Already exists - - async def try_insert_task(): - if task_name: - try: - task = TaskEntity( - name=task_name, - workspace=workspace, - app=full_app_ref, - description="Auto-registered from entry", - ) - await entities_client.create(task) - except EntityConflictError: - pass # Already exists - - # Launch background tasks without waiting - asyncio.create_task(try_insert_app()) - if task_name: - asyncio.create_task(try_insert_task()) - - -@router.post( - "/v2/workspaces/{workspace}/entries", - response_model=Entry, - tags=[API_TAG], - status_code=status.HTTP_201_CREATED, -) -async def create_entry( - workspace: str, - entry_input: EntryInput, - entities_client: EntityClient = Depends(get_entity_client), -) -> Entry: - """Create a new entry. - - Apps and tasks referenced in the entry context will be auto-created if they don't exist. - """ - # Generate a unique name for the entry - from nmp.core.entities.utils.identifiers import generate_random_suffix - - entry_name = f"entry-{generate_random_suffix()}" - - entry_entity = EntryEntity( - name=entry_name, - workspace=workspace, - external_id=entry_input.external_id if hasattr(entry_input, "external_id") else None, - data=entry_input.data, - usage=entry_input.usage, - context=entry_input.context, - user_rating=entry_input.user_rating if hasattr(entry_input, "user_rating") else None, - events=entry_input.events if hasattr(entry_input, "events") else [], - custom_fields=entry_input.custom_fields if hasattr(entry_input, "custom_fields") else None, - ) - - # Auto-register app and task from context (fire-and-forget, don't await) - if entry_entity.context and entry_entity.context.app: - _auto_register_app_and_task_async( - entities_client, - entry_entity.context.app, - entry_entity.context.task, - entry_entity.workspace, - ) - - try: - created = await entities_client.create(entry_entity) - except EntityConflictError: - raise HTTPException( - status_code=status.HTTP_409_CONFLICT, - detail=f"Entry with external_id={entry_entity.external_id} already exists", - ) - - return Entry.model_validate(created.model_dump(by_alias=True, mode="json")) - - -@router.get( - "/v2/workspaces/{workspace}/entries/{name}", - response_model=Entry, - tags=[API_TAG], -) -async def get_entry( - workspace: str, - name: str, - entities_client: EntityClient = Depends(get_entity_client), -) -> Entry: - """Get a specific entry by ID or external_id. - - Use `external:{external_id}` to get by external_id. - Example: `/v2/workspaces/{workspace}/entries/external:chatcmpl-abc123` - """ - entry_entity = await _get_entry_by_id_or_external(entities_client, name, workspace) - - if entry_entity is None: - raise HTTPException( - status_code=status.HTTP_404_NOT_FOUND, - detail=f"Entry {name} not found", - ) - - return Entry.model_validate(entry_entity.model_dump(by_alias=True, mode="json")) - - -@router.patch( - "/v2/workspaces/{workspace}/entries/{name}", - response_model=Entry, - tags=[API_TAG], -) -async def update_entry( - workspace: str, - name: str, - entry_update: EntryUpdate, - entities_client: EntityClient = Depends(get_entity_client), -) -> Entry: - """Update an existing entry by ID or external_id. - - Use `external:{external_id}` to update by external_id. - Example: `/v2/workspaces/{workspace}/entries/external:chatcmpl-abc123` - """ - entry_entity = await _get_entry_by_id_or_external(entities_client, name, workspace) - - if entry_entity is None: - raise HTTPException( - status_code=status.HTTP_404_NOT_FOUND, - detail=f"Entry {name} not found", - ) - - # Apply updates, coercing nested models to proper types - update_data = entry_update.model_dump(exclude_unset=True) - for field, value in update_data.items(): - # Coerce user_rating dict to UserRating to avoid Pydantic serialization warnings - if field == "user_rating" and isinstance(value, dict): - value = UserRating(**value) - elif field == "usage" and isinstance(value, dict): - value = Usage(**value) - setattr(entry_entity, field, value) - - updated = await entities_client.update(entry_entity) - - return Entry.model_validate(updated.model_dump(by_alias=True, mode="json")) - - -@router.delete( - "/v2/workspaces/{workspace}/entries/{name}", - status_code=status.HTTP_204_NO_CONTENT, - tags=[API_TAG], -) -async def delete_entry( - workspace: str, - name: str, - entities_client: EntityClient = Depends(get_entity_client), -) -> None: - """Delete an entry by ID or external_id. - - Use `external:{external_id}` to delete by external_id. - Example: `/v2/workspaces/{workspace}/entries/external:chatcmpl-abc123` - """ - entry_entity = await _get_entry_by_id_or_external(entities_client, name, workspace) - - if entry_entity is None: - raise HTTPException( - status_code=status.HTTP_404_NOT_FOUND, - detail=f"Entry {name} not found", - ) - - await entities_client.delete(EntryEntity, entry_entity.name, workspace=entry_entity.workspace) - - -# --------------------------------------------------------------------------- -# Events Sub-resource -# --------------------------------------------------------------------------- - - -@router.post( - "/v2/workspaces/{workspace}/entries/{name}/events", - response_model=Entry, - tags=[API_TAG], -) -async def add_events( - workspace: str, - name: str, - request: EventsCreateRequest, - entities_client: EntityClient = Depends(get_entity_client), -) -> Entry: - """Add events to an entry by ID or external_id. - - Use `external:{external_id}` to add events by external_id. - Example: `/v2/workspaces/{workspace}/entries/external:chatcmpl-abc123/events` - """ - entry_entity = await _get_entry_by_id_or_external(entities_client, name, workspace) - - if entry_entity is None: - raise HTTPException( - status_code=status.HTTP_404_NOT_FOUND, - detail=f"Entry {name} not found", - ) - - # Add new events - entry_entity.events.extend(request.events) - - # Update user_rating if any UserFeedbackEvent is present - for event in request.events: - if hasattr(event, "event_type") and event.event_type == "user_feedback": - # Update user_rating fields from the feedback event - if not entry_entity.user_rating: - entry_entity.user_rating = UserRating() - - if hasattr(event, "thumb") and event.thumb: - entry_entity.user_rating.thumb = event.thumb - if hasattr(event, "rating") and event.rating is not None: - entry_entity.user_rating.rating = event.rating - if hasattr(event, "opinion") and event.opinion: - entry_entity.user_rating.opinion = event.opinion - if hasattr(event, "rewrite") and event.rewrite: - entry_entity.user_rating.rewrite = event.rewrite - if hasattr(event, "chosen_index") and event.chosen_index is not None: - entry_entity.user_rating.chosen_index = event.chosen_index - if hasattr(event, "categories") and event.categories: - entry_entity.user_rating.categories = event.categories - - updated = await entities_client.update(entry_entity) - - return Entry.model_validate(updated.model_dump(by_alias=True, mode="json")) - - -@router.delete( - "/v2/workspaces/{workspace}/entries/{entry}/events/{name}", - response_model=Entry, - tags=[API_TAG], -) -async def delete_event( - workspace: str, - entry: str, - name: str, - entities_client: EntityClient = Depends(get_entity_client), -) -> Entry: - """Delete a specific event from an entry. - - Entry can be referenced by ID or external_id using `external:{external_id}` prefix. - """ - entry_entity = await _get_entry_by_id_or_external(entities_client, entry, workspace) - - if entry_entity is None: - raise HTTPException( - status_code=status.HTTP_404_NOT_FOUND, - detail=f"Entry {entry} not found", - ) - - # Find and remove the event - event_found = False - for i, event in enumerate(entry_entity.events): - if hasattr(event, "id") and event.id == name: - entry_entity.events.pop(i) - event_found = True - break - - if not event_found: - raise HTTPException( - status_code=status.HTTP_404_NOT_FOUND, - detail=f"Event {name} not found in entry {entry}", - ) - - updated = await entities_client.update(entry_entity) - - return Entry.model_validate(updated.model_dump(by_alias=True, mode="json")) diff --git a/services/intake/src/nmp/intake/api/v2/entries/schemas.py b/services/intake/src/nmp/intake/api/v2/entries/schemas.py deleted file mode 100644 index c631d9a2c2..0000000000 --- a/services/intake/src/nmp/intake/api/v2/entries/schemas.py +++ /dev/null @@ -1,181 +0,0 @@ -# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. -# SPDX-License-Identifier: Apache-2.0 - -"""API schemas for Entry endpoints.""" - -from datetime import datetime -from enum import StrEnum -from typing import Annotated, Any, Dict, List, Optional, Union - -from nmp.common.entities.values import DatetimeFilter, Filter, map_entity_field -from nmp.intake.entities import EntryContext, EntryData, EntryEvent, ThumbDirection, Usage, UserRating -from pydantic import BaseModel, Field, model_validator - -# --------------------------------------------------------------------------- -# Entry Request/Response schemas -# --------------------------------------------------------------------------- - - -class EntryInput(BaseModel): - """Schema for creating a new Entry.""" - - # Note: workspace comes from workspace in the URL path, not the body - external_id: str | None = Field( - default=None, - description="Optional client-provided identifier (e.g., completion_id from an LLM provider)", - ) - project: str | None = Field(default=None, description="The name of the project associated with this entry") - data: EntryData = Field(..., description="Entry data containing request and response") - usage: Usage | None = Field( - default=None, - description="Structured usage metrics (model served, latency, cost, token counts).", - ) - context: EntryContext = Field(..., description="Metadata describing producer, task, trace") - user_rating: UserRating | None = Field(default=None, description="User's rating/evaluation of the AI response") - events: List[EntryEvent] = Field(default_factory=list, description="Events associated with this entry") - custom_fields: Dict[str, Any] | None = Field( - default=None, - description="Free-form metadata bag for client-defined fields (e.g., external experiment metadata).", - ) - - -class EntryUpdate(BaseModel): - """Schema for updating an existing Entry.""" - - data: EntryData | None = Field(default=None, description="Entry data containing request and response") - usage: Usage | None = Field( - default=None, - description="Structured usage metrics (model served, latency, cost, token counts).", - ) - context: EntryContext | None = Field(default=None, description="Metadata describing producer, task, trace") - user_rating: UserRating | None = Field(default=None, description="User's rating/evaluation of the AI response") - events: List[EntryEvent] | None = Field(default=None, description="Events associated with this entry") - custom_fields: Dict[str, Any] | None = Field( - default=None, - description="Free-form metadata bag for client-defined fields (replaces existing value when provided).", - ) - - -class Entry(BaseModel): - """Schema for Entry responses.""" - - id: str = Field(..., description="Unique identifier") - name: str = Field(..., description="Entry name (auto-generated)") - workspace: str = Field(..., description="Workspace identifier") - external_id: str | None = Field(default=None, description="Client-provided identifier") - project: str | None = Field(default=None, description="The name of the project associated with this entry") - data: EntryData = Field(..., description="Entry data containing request and response") - usage: Usage | None = Field( - default=None, - description="Structured usage metrics (model served, latency, cost, token counts).", - ) - context: EntryContext = Field(..., description="Metadata describing producer, task, trace") - user_rating: UserRating | None = Field(default=None, description="User's rating/evaluation") - events: List[EntryEvent] = Field(default_factory=list, description="Events associated with this entry") - custom_fields: Dict[str, Any] | None = Field( - default=None, - description="Free-form metadata bag for client-defined fields.", - ) - created_at: datetime | None = Field(default=None, description="Creation timestamp") - updated_at: datetime | None = Field(default=None, description="Last update timestamp") - - @model_validator(mode="before") - @classmethod - def _coerce_nested_models(cls, data: Any) -> Any: - """Coerce nested dict fields to their proper Pydantic model types. - - When entities are loaded from the Entity Store, nested Pydantic models - come back as raw dicts. This validator ensures they are properly coerced - to avoid Pydantic serialization warnings. - """ - if isinstance(data, dict) and isinstance(data.get("user_rating"), dict): - data["user_rating"] = UserRating(**data["user_rating"]) - return data - - -# --------------------------------------------------------------------------- -# Events Sub-resource schemas -# --------------------------------------------------------------------------- - - -class EventsCreateRequest(BaseModel): - """Request to add events to an entry.""" - - events: List[EntryEvent] = Field(..., min_length=1, description="List of events to add to the entry.") - - -# --------------------------------------------------------------------------- -# Sort enums -# --------------------------------------------------------------------------- - - -class EntrySortField(StrEnum): - """Sort fields for Entries.""" - - CREATED_AT_ASC = "created_at" - CREATED_AT_DESC = "-created_at" - UPDATED_AT_ASC = "updated_at" - UPDATED_AT_DESC = "-updated_at" - - -# --------------------------------------------------------------------------- -# Filter schemas -# --------------------------------------------------------------------------- - - -class EntryContextFilter(BaseModel): - """Filter for entry context fields.""" - - app: Optional[str] = Field(None, description="Filter by app reference (workspace/name).") - task: Optional[str] = Field(None, description="Filter by task reference.") - thread_id: Optional[str] = Field(None, description="Filter by thread ID.") - user_id: Optional[str] = Field(None, description="Filter by user ID.") - session_id: Optional[str] = Field(None, description="Filter by session ID.") - - -class EntryUserRatingFilter(BaseModel): - """Filter for entry user rating fields.""" - - thumb: Optional[ThumbDirection] = Field(None, description="Filter by thumb direction.") - - -class EntryFilter(Filter): - """Filter for Entries.""" - - id: Optional[Union[str, Dict[str, Any]]] = Field( - None, - description="Filter by entry ID. Supports operators like {'in': ['entry-ABC', 'entry-XYZ']} for multiple IDs.", - ) - workspace: Optional[str] = Field(None, description="Filter by workspace id.") - project: Optional[str] = Field(None, description="Filter by project name.") - external_id: Optional[Union[str, Dict[str, Any]]] = Field( - None, - description="Filter by external ID. Supports operators like {'in': ['id1', 'id2']} for multiple IDs.", - ) - - # Nested context filters — persisted under the entity row JSON `data` blob (`data.context.*`). - context: Optional[EntryContextFilter] = Field(None, description="Filter by context fields.") - - user_rating: Optional[EntryUserRatingFilter] = Field(None, description="Filter by user rating fields.") - - # Only model is currently supported in usage -- numeric filter support coming in FP-59 - model: Annotated[Optional[str], map_entity_field("data.usage.model")] = Field( - None, - description="Filter by the served model recorded in usage (e.g., 'gpt-4o', 'meta/llama-3.1-70b-instruct').", - ) - - # Feedback filters (non-nested convenience filters) - has_thumb: Optional[bool] = Field(None, description="Filter by presence of thumb feedback.") - has_rating: Optional[bool] = Field(None, description="Filter by presence of rating.") - has_opinion: Optional[bool] = Field(None, description="Filter by presence of opinion.") - has_rewrite: Optional[bool] = Field(None, description="Filter by presence of rewrite.") - has_events: Optional[bool] = Field(None, description="Filter by presence of any events.") - - # Thread aggregation filter - longest_per_thread: Optional[bool] = Field( - None, description="If true, return only the longest entry per thread (based on message count)." - ) - - # Timestamp filters - created_at: Optional[DatetimeFilter] = Field(None, description="Filter entities based on creation date.") - updated_at: Optional[DatetimeFilter] = Field(None, description="Filter entities based on update date.") diff --git a/services/intake/src/nmp/intake/api/v2/exports/__init__.py b/services/intake/src/nmp/intake/api/v2/exports/__init__.py deleted file mode 100644 index 474f1fbc21..0000000000 --- a/services/intake/src/nmp/intake/api/v2/exports/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. -# SPDX-License-Identifier: Apache-2.0 - -"""Exports API resource.""" diff --git a/services/intake/src/nmp/intake/api/v2/exports/endpoints.py b/services/intake/src/nmp/intake/api/v2/exports/endpoints.py deleted file mode 100644 index 16bcd95b57..0000000000 --- a/services/intake/src/nmp/intake/api/v2/exports/endpoints.py +++ /dev/null @@ -1,243 +0,0 @@ -# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. -# SPDX-License-Identifier: Apache-2.0 - -"""API endpoints for managing data exports using EntityClient pattern.""" - -import logging - -from fastapi import APIRouter, Depends, HTTPException, Query, Response, status -from nmp.common.api.common import Page -from nmp.common.api.parsed_filter import ParsedFilter, make_filter_dep -from nmp.common.api.utils import generate_openapi_extra_params -from nmp.common.entities.client import EntityClient, EntityNotFoundError -from nmp.common.service.dependencies import get_entity_client -from nmp.intake.app.exporter import DataExporter -from nmp.intake.app.utils.exports import extract_datastore_path, extract_nds_path, is_local_file_uri -from nmp.intake.app.utils.exports import is_datastore_uri as _is_datastore_uri -from nmp.intake.entities import ExportConfig, ExportStatusDetails, JobStatus -from nmp.intake.entities import ExportJob as ExportJobEntity - -from .schemas import ( - ExportJobFilter, - ExportJobInput, - ExportJobSortField, - ExportPreviewRequest, - ExportPreviewResponse, -) - -logger = logging.getLogger(__name__) - -router = APIRouter() - -API_TAG = "Exports" - - -def _with_path_workspace(config: ExportConfig, workspace: str) -> ExportConfig: - """Return an export config whose entry filter is scoped to the route workspace.""" - filters = dict(config.filters or {}) - filters["workspace"] = workspace - return config.model_copy(update={"filters": filters}) - - -@router.get( - "/v2/workspaces/{workspace}/export/jobs", - response_model=Page[ExportJobEntity], - tags=[API_TAG], - response_model_exclude_none=True, - openapi_extra=generate_openapi_extra_params( - filter_schema=ExportJobFilter, - filter_description="Filter export jobs by name, status, output_file_url, created_at, and updated_at.", - ), -) -async def list_export_jobs( - workspace: str, - entities_client: EntityClient = Depends(get_entity_client), - page: int = Query(default=1, description="Page number."), - page_size: int = Query(default=10, description="Page size."), - sort: ExportJobSortField = Query( - default="created_at", - description="""The field to sort by. To sort in decreasing order, use `-` in front of the field name.""", - ), - parsed: ParsedFilter = Depends(make_filter_dep(ExportJobFilter)), -) -> Page[ExportJobEntity]: - """List all export jobs with filtering capabilities. - - Use `workspace=-` for cross-workspace listing. - """ - # Workspace from the path takes precedence over any filter value. - parsed.remove("workspace") - - res = await entities_client.list( - ExportJobEntity, - page=page, - page_size=page_size, - sort=sort, - workspace=workspace, - filter_operation=parsed.operation, - ) - - data_dicts = [item.model_dump(by_alias=True, mode="json") for item in res.data] - - return Page[ExportJobEntity]( - data=data_dicts, - pagination=res.pagination.model_dump(), - sort=sort, - filter=None, - ) - - -@router.post("/v2/workspaces/{workspace}/export/jobs", response_model=ExportJobEntity, tags=[API_TAG]) -async def create_export_job( - workspace: str, - export_request: ExportJobInput, - response: Response, - entities_client: EntityClient = Depends(get_entity_client), -) -> ExportJobEntity: - """Export entries to an external file. - - Use the `longest_per_thread` filter to export only the longest entry per thread, - which is useful for thread-based exports. - - Supported output file URLs: - - - NeMo Datastore: nds://workspace/dataset_name - - HuggingFace Dataset: hf://datasets/org/name/path/to/file - - Local filesystem: file:///path/to/export (for development) - """ - # Note: Currently runs synchronously. Async/Celery support to be added. - # Convert AnyUrl to string for validation - output_url_str = str(export_request.output_file_url) - is_remote_url = _is_datastore_uri(output_url_str) - is_local_url = is_local_file_uri(output_url_str) - - if not (is_local_url or is_remote_url): - raise HTTPException( - status_code=status.HTTP_400_BAD_REQUEST, - detail="Only file://, hf://, and nds:// URLs are supported for output_file_url", - ) - - if is_remote_url: - try: - # Validate the URL format - from urllib.parse import urlparse - - parsed = urlparse(output_url_str) - if parsed.scheme == "hf": - extract_datastore_path(output_url_str) - elif parsed.scheme == "nds": - extract_nds_path(output_url_str) - except ValueError as e: - raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=f"Invalid output file URL: {str(e)}") - - try: - # Convert input config to ExportConfig - config_obj = _with_path_workspace(ExportConfig(**export_request.config.model_dump()), workspace) - - # Create job entity (name will be auto-generated by the entity API) - job_entity = ExportJobEntity( - workspace=workspace, - status=JobStatus.PENDING, - output_file_url=export_request.output_file_url, - config=config_obj, - status_details=ExportStatusDetails(entries_count=0), - ) - job_entity = await entities_client.create(job_entity) - - logger.info( - "Creating export job %s with output file URL %s", - job_entity.id, - export_request.output_file_url, - ) - - # Run export synchronously (TODO: Add async/Celery support) - try: - # Update job status - job_entity.status = JobStatus.RUNNING - job_entity = await entities_client.update(job_entity) - - # Perform export - exporter = DataExporter(entities_client) - export_data = await exporter.export_entries(config_obj) - - # Write to destination - if is_local_url: - from nmp.intake.app.utils.exports import extract_local_path - - file_path = extract_local_path(output_url_str) - records_count = await exporter.write_to_file(export_data, str(file_path)) - else: - # Handle remote exports (HuggingFace or NeMo Datastore) - from urllib.parse import urlparse - - parsed = urlparse(output_url_str) - if parsed.scheme == "hf": - records_count = await exporter.write_to_hf_dataset(export_data, output_url_str) - elif parsed.scheme == "nds": - records_count = await exporter.write_to_nds_dataset(export_data, output_url_str) - else: - raise HTTPException( - status_code=status.HTTP_400_BAD_REQUEST, - detail=f"Unsupported URI scheme: {parsed.scheme}", - ) - - # Update job as completed - job_entity.status = JobStatus.COMPLETED - if job_entity.status_details: - job_entity.status_details.entries_count = records_count - else: - job_entity.status_details = ExportStatusDetails(entries_count=records_count) - job_entity = await entities_client.update(job_entity) - - except Exception as e: - # Update job as failed - job_entity.status = JobStatus.FAILED - if job_entity.status_details: - job_entity.status_details.error_message = str(e) - else: - job_entity.status_details = ExportStatusDetails(error_message=str(e)) - await entities_client.update(job_entity) - raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Export failed: {str(e)}") - - # Return job entity - return job_entity - - except HTTPException: - raise - except Exception as e: - logger.exception("Failed to create export job: %s", str(e)) - raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e)) - - -@router.post("/v2/workspaces/{workspace}/export/preview", response_model=ExportPreviewResponse, tags=[API_TAG]) -async def preview_export( - workspace: str, - export_request: ExportPreviewRequest, - entities_client: EntityClient = Depends(get_entity_client), -) -> ExportPreviewResponse: - """Preview export data without writing to a file (max 100 records).""" - try: - # Convert input config to ExportConfig - config_obj = _with_path_workspace(ExportConfig(**export_request.config.model_dump()), workspace) - - exporter = DataExporter(entities_client) - preview_data = await exporter.preview_export(config_obj) - - return ExportPreviewResponse(data=preview_data, count=len(preview_data), config=export_request.config) - except Exception as e: - logger.exception("Failed to preview export: %s", str(e)) - raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e)) - - -@router.get("/v2/workspaces/{workspace}/export/jobs/{name}", response_model=ExportJobEntity, tags=[API_TAG]) -async def get_export_job_status( - workspace: str, - name: str, - entities_client: EntityClient = Depends(get_entity_client), -) -> ExportJobEntity: - """Check the status of an export job.""" - try: - job_entity = await entities_client.get(ExportJobEntity, name, workspace=workspace) - except EntityNotFoundError: - raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Export job not found") - - return job_entity diff --git a/services/intake/src/nmp/intake/api/v2/exports/schemas.py b/services/intake/src/nmp/intake/api/v2/exports/schemas.py deleted file mode 100644 index 2e5b846e92..0000000000 --- a/services/intake/src/nmp/intake/api/v2/exports/schemas.py +++ /dev/null @@ -1,102 +0,0 @@ -# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. -# SPDX-License-Identifier: Apache-2.0 - -"""Export schemas for Intake API.""" - -from enum import StrEnum -from typing import Any, Dict, List, Optional - -from nmp.common.entities.values import DatetimeFilter, Filter -from nmp.intake.entities import JobStatus -from pydantic import AnyUrl, BaseModel, Field - -# --------------------------------------------------------------------------- -# Sort enums -# --------------------------------------------------------------------------- - - -class ExportJobSortField(StrEnum): - """Sort fields for ExportJobs.""" - - CREATED_AT_ASC = "created_at" - CREATED_AT_DESC = "-created_at" - UPDATED_AT_ASC = "updated_at" - UPDATED_AT_DESC = "-updated_at" - STATUS_ASC = "status" - STATUS_DESC = "-status" - - -# --------------------------------------------------------------------------- -# Filter schemas -# --------------------------------------------------------------------------- - - -class ExportJobFilter(Filter): - """Filter for ExportJobs.""" - - id: Optional[str] = Field(None, description="Filter by export job ID.") - workspace: Optional[str] = Field(None, description="Filter by workspace id.") - name: Optional[str] = Field(None, description="Filter by export job name.") - status: Optional[JobStatus] = Field(None, description="Filter by job status.") - output_file_url: Optional[str] = Field(None, description="Filter by output file URL.") - created_at: Optional[DatetimeFilter] = Field(None, description="Filter entities based on creation date.") - updated_at: Optional[DatetimeFilter] = Field(None, description="Filter entities based on update date.") - - -# --------------------------------------------------------------------------- -# Request/Response schemas -# --------------------------------------------------------------------------- - - -class ExportConfigInput(BaseModel): - """Input schema for export configuration. - - Defines what entries to export and how to format them. - """ - - filters: Optional[Dict[str, Any]] = Field( - default=None, - description=( - "Filter criteria for selecting entries (workspace, app, task, thread_id, external_id, " - "has_thumb, has_rating, longest_per_thread, model, etc.)" - ), - ) - - search: Optional[Dict[str, Any]] = Field( - default=None, - description="Search criteria for finding entries", - ) - - limit: Optional[int] = Field( - default=1000, - description="Maximum number of entries to export. None means no limit.", - ) - - format_options: Optional[Dict[str, Any]] = Field( - default=None, - description="Format options for the export (e.g., row_transformation)", - ) - - -class ExportJobInput(BaseModel): - """Request payload for creating an export job.""" - - config: ExportConfigInput = Field(..., description="Export configuration") - output_file_url: AnyUrl = Field( - ..., - description="The place where the exported file should be written (file://, hf://, nds://, etc.)", - ) - - -class ExportPreviewRequest(BaseModel): - """Request payload for previewing export data without writing to a file.""" - - config: ExportConfigInput = Field(..., description="Export configuration for preview") - - -class ExportPreviewResponse(BaseModel): - """Response containing preview data from the export configuration.""" - - data: List[Dict[str, Any]] = Field(..., description="Preview data (max 100 records)") - count: int = Field(..., description="Number of records returned") - config: ExportConfigInput = Field(..., description="The configuration used for this preview") diff --git a/services/intake/src/nmp/intake/api/v2/health/endpoints.py b/services/intake/src/nmp/intake/api/v2/health/endpoints.py index da7a85a87c..574d271b0e 100644 --- a/services/intake/src/nmp/intake/api/v2/health/endpoints.py +++ b/services/intake/src/nmp/intake/api/v2/health/endpoints.py @@ -3,10 +3,7 @@ """Health check endpoints.""" -from fastapi import APIRouter, Depends, status -from nmp.common.entities.client import EntityClient -from nmp.common.service.dependencies import get_entity_client -from nmp.intake.entities import App +from fastapi import APIRouter, status router = APIRouter() @@ -44,19 +41,11 @@ async def health_live() -> dict: status_code=status.HTTP_200_OK, summary="Perform a readiness check to verify the server is able/ready to serve requests.", ) -async def health_ready( - entities_client: EntityClient = Depends(get_entity_client), -) -> dict: +async def health_ready() -> dict: """ - Health check endpoint to verify the status of the application's dependencies. - Checks entity store connectivity. + Health check endpoint to verify the status of the application. """ - try: - # Try a simple query to verify entity store connectivity - await entities_client.list(App, page=1, page_size=1) - return {"status": "ready", "entity_store": "connected"} - except Exception: - return {"status": "not_ready", "entity_store": "disconnected"} + return {"status": "ready"} @router.get( diff --git a/services/intake/src/nmp/intake/api/v2/tasks/__init__.py b/services/intake/src/nmp/intake/api/v2/tasks/__init__.py deleted file mode 100644 index 82db3d3a37..0000000000 --- a/services/intake/src/nmp/intake/api/v2/tasks/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. -# SPDX-License-Identifier: Apache-2.0 - -"""Tasks API resource.""" diff --git a/services/intake/src/nmp/intake/api/v2/tasks/endpoints.py b/services/intake/src/nmp/intake/api/v2/tasks/endpoints.py deleted file mode 100644 index 2a5b045256..0000000000 --- a/services/intake/src/nmp/intake/api/v2/tasks/endpoints.py +++ /dev/null @@ -1,190 +0,0 @@ -# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. -# SPDX-License-Identifier: Apache-2.0 - -"""API endpoints for Tasks using EntityClient pattern.""" - -from fastapi import APIRouter, Depends, HTTPException, Query, status -from nmp.common.api.common import Page -from nmp.common.api.filter import ComparisonOperation, FilterOperator, LogicalOperation -from nmp.common.api.parsed_filter import ParsedFilter, make_filter_dep -from nmp.common.api.utils import generate_openapi_extra_params -from nmp.common.entities.client import EntityClient, EntityConflictError, EntityNotFoundError -from nmp.common.service.dependencies import get_entity_client -from nmp.intake.entities import Task as TaskEntity - -from .schemas import Task, TaskFilter, TaskInput, TaskSortField, TaskUpdate - -router = APIRouter() - -API_TAG = "Tasks" - - -@router.get( - "/v2/workspaces/{workspace}/apps/{name}/tasks", - response_model=Page[Task], - tags=[API_TAG], - response_model_exclude_none=True, - openapi_extra=generate_openapi_extra_params( - filter_schema=TaskFilter, - filter_description="Filter tasks by name, app, description, project, created_at, and updated_at.", - ), -) -async def list_tasks( - workspace: str, - name: str, - entities_client: EntityClient = Depends(get_entity_client), - page: int = Query(default=1, description="Page number."), - page_size: int = Query(default=10, description="Page size."), - sort: TaskSortField = Query( - default="created_at", - description="""The field to sort by. To sort in decreasing order, use `-` in front of the field name.""", - ), - parsed: ParsedFilter = Depends(make_filter_dep(TaskFilter)), -) -> Page[Task]: - """List all tasks for a specific app.""" - # Inject app filter from URL path params - app_ref = f"{workspace}/{name}" - app_field = parsed._resolve_field("app") - app_op = ComparisonOperation(field=app_field, operator=FilterOperator.EQ, value=app_ref) - if parsed.operation is None: - parsed.operation = app_op - elif isinstance(parsed.operation, LogicalOperation) and parsed.operation.operator == FilterOperator.AND: - parsed.operation.operations.append(app_op) - else: - parsed.operation = LogicalOperation(operator=FilterOperator.AND, operations=[parsed.operation, app_op]) - - res = await entities_client.list( - TaskEntity, - workspace=workspace, - page=page, - page_size=page_size, - sort=sort, - filter_operation=parsed.operation, - ) - - data_dicts = [item.model_dump(by_alias=True, mode="json") for item in res.data] - - return Page[Task]( - data=data_dicts, - pagination=res.pagination.model_dump(), - sort=sort, - filter=None, - ) - - -@router.post( - "/v2/workspaces/{workspace}/apps/{name}/tasks", - response_model=Task, - tags=[API_TAG], - status_code=status.HTTP_201_CREATED, -) -async def create_task( - workspace: str, - name: str, - task_input: TaskInput, - entities_client: EntityClient = Depends(get_entity_client), -) -> Task: - """Create a new task.""" - # Build app reference (name is the app name from the URL path) - app_ref = f"{workspace}/{name}" - - task_entity = TaskEntity( - name=task_input.name, - workspace=workspace, - description=task_input.description, - app=app_ref, - locked=task_input.locked if hasattr(task_input, "locked") else False, - ) - - try: - created = await entities_client.create(task_entity) - except EntityConflictError: - raise HTTPException( - status_code=status.HTTP_409_CONFLICT, - detail=f"Task {workspace}/{name}/{task_entity.name} already exists", - ) - - return Task(**created.model_dump(by_alias=True, mode="json")) - - -@router.get( - "/v2/workspaces/{workspace}/apps/{app}/tasks/{name}", - response_model=Task, - tags=[API_TAG], -) -async def get_task( - workspace: str, - app: str, - name: str, - entities_client: EntityClient = Depends(get_entity_client), -) -> Task: - """Get a specific task.""" - app_ref = f"{workspace}/{app}" - - try: - task_entity = await entities_client.get_by_field(TaskEntity, workspace=workspace, name=name, app=app_ref) - except EntityNotFoundError: - raise HTTPException( - status_code=status.HTTP_404_NOT_FOUND, - detail=f"Task {workspace}/{app}/{name} not found", - ) - - return Task(**task_entity.model_dump(by_alias=True, mode="json")) - - -@router.patch( - "/v2/workspaces/{workspace}/apps/{app}/tasks/{name}", - response_model=Task, - tags=[API_TAG], -) -async def update_task( - workspace: str, - app: str, - name: str, - task_update: TaskUpdate, - entities_client: EntityClient = Depends(get_entity_client), -) -> Task: - """Update an existing task.""" - app_ref = f"{workspace}/{app}" - - try: - task_entity = await entities_client.get_by_field(TaskEntity, workspace=workspace, name=name, app=app_ref) - except EntityNotFoundError: - raise HTTPException( - status_code=status.HTTP_404_NOT_FOUND, - detail=f"Task {workspace}/{app}/{name} not found", - ) - - # Apply updates - update_data = task_update.model_dump(exclude_unset=True) - for field, value in update_data.items(): - setattr(task_entity, field, value) - - updated = await entities_client.update(task_entity) - - return Task(**updated.model_dump(by_alias=True, mode="json")) - - -@router.delete( - "/v2/workspaces/{workspace}/apps/{app}/tasks/{name}", - status_code=status.HTTP_204_NO_CONTENT, - tags=[API_TAG], -) -async def delete_task( - workspace: str, - app: str, - name: str, - entities_client: EntityClient = Depends(get_entity_client), -) -> None: - """Delete a task.""" - app_ref = f"{workspace}/{app}" - - try: - await entities_client.get_by_field(TaskEntity, workspace=workspace, name=name, app=app_ref) - except EntityNotFoundError: - raise HTTPException( - status_code=status.HTTP_404_NOT_FOUND, - detail=f"Task {workspace}/{app}/{name} not found", - ) - - await entities_client.delete(TaskEntity, name, workspace=workspace) diff --git a/services/intake/src/nmp/intake/api/v2/tasks/schemas.py b/services/intake/src/nmp/intake/api/v2/tasks/schemas.py deleted file mode 100644 index f21758f14f..0000000000 --- a/services/intake/src/nmp/intake/api/v2/tasks/schemas.py +++ /dev/null @@ -1,85 +0,0 @@ -# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. -# SPDX-License-Identifier: Apache-2.0 - -"""API schemas for Task endpoints.""" - -from datetime import datetime -from enum import StrEnum -from typing import Optional - -from nmp.common.entities.values import DatetimeFilter, Filter -from pydantic import BaseModel, Field - -# --------------------------------------------------------------------------- -# Task Request/Response schemas -# --------------------------------------------------------------------------- - - -class TaskInput(BaseModel): - """Schema for creating a new Task. - - Note: workspace and app are automatically set from the URL path. - """ - - name: str = Field(..., description="Task name") - description: str | None = Field(default=None, description="Task description") - project: str | None = Field(default=None, description="The name of the project associated with this task") - locked: bool = Field( - default=False, - description="If true, this record cannot be automatically updated when entries are ingested.", - ) - - -class TaskUpdate(BaseModel): - """Schema for updating an existing Task.""" - - description: str | None = Field(default=None, description="Task description") - project: str | None = Field(default=None, description="The name of the project associated with this task") - locked: bool | None = Field(default=None, description="Lock status") - - -class Task(BaseModel): - """Schema for Task responses.""" - - id: str = Field(..., description="Unique identifier") - name: str = Field(..., description="Task name") - workspace: str = Field(..., validation_alias="workspace", description="Workspace identifier") - app: str = Field(..., description="Parent app reference (workspace/name)") - description: str | None = Field(default=None, description="Task description") - project: str | None = Field(default=None, description="The name of the project associated with this task") - locked: bool = Field(default=False, description="Lock status") - created_at: datetime | None = Field(default=None, description="Creation timestamp") - updated_at: datetime | None = Field(default=None, description="Last update timestamp") - - -# --------------------------------------------------------------------------- -# Sort enums -# --------------------------------------------------------------------------- - - -class TaskSortField(StrEnum): - """Sort fields for Tasks.""" - - CREATED_AT_ASC = "created_at" - CREATED_AT_DESC = "-created_at" - NAME_ASC = "name" - NAME_DESC = "-name" - UPDATED_AT_ASC = "updated_at" - UPDATED_AT_DESC = "-updated_at" - - -# --------------------------------------------------------------------------- -# Filter schemas -# --------------------------------------------------------------------------- - - -class TaskFilter(Filter): - """Filter for Tasks.""" - - workspace: Optional[str] = Field(None, description="Filter by workspace id.") - name: Optional[str] = Field(None, description="Filter by task name.") - app: Optional[str] = Field(None, description="Filter by app reference (workspace/name).") - project: Optional[str] = Field(None, description="Filter by project name.") - description: Optional[str] = Field(None, description="Filter by task description.") - created_at: Optional[DatetimeFilter] = Field(None, description="Filter entities based on creation date.") - updated_at: Optional[DatetimeFilter] = Field(None, description="Filter entities based on update date.") diff --git a/services/intake/src/nmp/intake/app/__init__.py b/services/intake/src/nmp/intake/app/__init__.py deleted file mode 100644 index b1528115b6..0000000000 --- a/services/intake/src/nmp/intake/app/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. -# SPDX-License-Identifier: Apache-2.0 - -"""Intake business logic.""" diff --git a/services/intake/src/nmp/intake/app/exporter.py b/services/intake/src/nmp/intake/app/exporter.py deleted file mode 100644 index 43dc400f6b..0000000000 --- a/services/intake/src/nmp/intake/app/exporter.py +++ /dev/null @@ -1,285 +0,0 @@ -# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. -# SPDX-License-Identifier: Apache-2.0 - -"""Data exporter for Intake entries using EntityClient pattern.""" - -import asyncio -import json -import logging -import os -import tempfile -from pathlib import Path -from typing import Any, Dict, List, Optional - -from nmp.common.entities.client import EntityClient -from nmp.intake.app.utils.datastore import DataStoreClient -from nmp.intake.app.utils.exports import extract_datastore_path, extract_nds_path -from nmp.intake.entities import Entry, ExportConfig - -logger = logging.getLogger(__name__) - - -class DataExporter: - """Export entries to various formats and destinations.""" - - def __init__(self, entities_client: EntityClient): - """Initialize exporter with EntityClient.""" - self.entities_client = entities_client - self.datastore_client = DataStoreClient() - - async def export_entries( - self, - config: ExportConfig, - ) -> List[Dict[str, Any]]: - """Export entries matching config. - - Args: - config: Export configuration with filters, search, and format options - - Returns: - List of entry dictionaries ready for export - """ - # Extract filter and search from config, merge them for EntityClient - filter_obj = dict(config.filters) if config.filters else {} - search_obj = config.search or {} - combined_filter = {**filter_obj, **search_obj} - - # Extract workspace from filter since it needs to be passed as a direct parameter - workspace_filter = combined_filter.pop("workspace", None) if combined_filter else None - - # Query entries using EntityClient - page_size = config.limit if config.limit else 10000 - result = await self.entities_client.list( - Entry, - page=1, - page_size=page_size, - workspace=workspace_filter, - filter_obj=combined_filter if combined_filter else None, - ) - - entries = result.data - - # Apply limit if specified - if config.limit: - entries = entries[: config.limit] - - # Transform entries to export format - export_data = [] - for entry in entries: - export_entry = self._transform_entry(entry, config.format_options) - export_data.append(export_entry) - - return export_data - - def _transform_entry(self, entry: Entry, format_options: Optional[Dict[str, Any]] = None) -> Dict[str, Any]: - """Transform entry to export format. - - Args: - entry: Entry entity to transform - format_options: Optional transformation options - - Returns: - Dictionary ready for export - """ - entry_dict = entry.model_dump(mode="json") - - # Add messages array for OpenAI/Customizer compatibility - try: - # Get data from the serialized dict to handle both Pydantic and dict types - data_dict = entry_dict.get("data", {}) - if isinstance(data_dict, dict): - request = data_dict.get("request", {}) - response = data_dict.get("response", {}) - - if isinstance(request, dict) and isinstance(response, dict): - request_messages = request.get("messages", []) - choices = response.get("choices", []) - - if request_messages and choices and isinstance(choices, list) and len(choices) > 0: - response_message = choices[0].get("message") - if response_message: - # Add top-level messages array for compatibility - entry_dict["messages"] = [*request_messages, response_message] - - # Add tools if present in request - if isinstance(request, dict) and request.get("tools"): - entry_dict["tools"] = request["tools"] - except Exception as e: - logger.warning(f"Failed to add messages/tools to entry {entry.external_id}: {e}") - - return entry_dict - - async def preview_export( - self, - config: ExportConfig, - ) -> List[Dict[str, Any]]: - """Preview export data without writing (max 100 records). - - Args: - config: Export configuration - - Returns: - List of up to 100 entry dictionaries - """ - # Limit preview to 100 records - preview_config = config.model_copy() - preview_config.limit = min(config.limit or 100, 100) - - return await self.export_entries(preview_config) - - # TODO(v2): FILES - async def write_to_file( - self, - entries: List[Dict[str, Any]], - file_path: str, - ) -> int: - """Write entries to a local JSONL file. - - Args: - entries: List of entry dictionaries - file_path: Path to output file - - Returns: - Number of records written - """ - output_path = Path(file_path) - output_path.parent.mkdir(parents=True, exist_ok=True) - - with output_path.open("w") as f: - for entry in entries: - f.write(json.dumps(entry) + "\n") - - logger.info(f"Wrote {len(entries)} entries to {file_path}") - return len(entries) - - # TODO(v2): FILES - # TODO(v2): SERVICE-CALL - async def write_to_hf_dataset( - self, - entries: List[Dict[str, Any]], - uri: str, - ) -> int: - """Write entries to a HuggingFace dataset. - - Args: - entries: List of entry dictionaries - uri: HuggingFace dataset URI in format hf://datasets/namespace/name/path/to/file.jsonl - - Returns: - Number of records written - - Raises: - ValueError: If no entries provided or URI is invalid - """ - if len(entries) == 0: - raise ValueError("No entries found. Cannot create empty file.") - - dataset_id, path_in_repo = extract_datastore_path(uri) - logger.info("Attempting to export %d entries to HuggingFace dataset %s", len(entries), dataset_id) - - # Check if dataset exists (run in executor since HfApi is synchronous) - loop = asyncio.get_event_loop() - is_existing_dataset = await loop.run_in_executor(None, self.datastore_client.dataset_exists, dataset_id) - - # Create dataset if it doesn't exist - if not is_existing_dataset: - logger.info("Creating new dataset with ID: %s", dataset_id) - try: - # Create dataset in HuggingFace - await loop.run_in_executor(None, self.datastore_client.create_dataset, dataset_id) - - # TODO(v2): Re-enable dataset registration once datasets service is up - # namespace, name = dataset_id.split("/") - # await self.entity_store_client.register_dataset( - # namespace=namespace, name=name, files_url=f"hf://datasets/{dataset_id}" - # ) - except Exception as e: - logger.error("Failed to create dataset %s: %s", dataset_id, str(e)) - # Clean up HuggingFace repo if creation fails - await loop.run_in_executor(None, self.datastore_client.delete_dataset, dataset_id) - raise Exception(f"Failed to create dataset: {str(e)}") - - # Create temporary file with JSONL content - with tempfile.NamedTemporaryFile(mode="w", suffix=".jsonl", delete=False) as tmp: - for entry in entries: - json_str = json.dumps(entry) - tmp.write(json_str + "\n") - tmp_path = tmp.name - - try: - # Upload file to dataset (run in executor since HfApi is synchronous) - await loop.run_in_executor( - None, - self.datastore_client.upload_file, - tmp_path, - dataset_id, - path_in_repo, - f"Export {len(entries)} entries", - ) - logger.info("Successfully exported %d entries to HuggingFace dataset %s", len(entries), dataset_id) - finally: - # Clean up temporary file - os.unlink(tmp_path) - - return len(entries) - - # TODO(v2): FILES - # TODO(v2): SERVICE-CALL - async def write_to_nds_dataset( - self, - entries: List[Dict[str, Any]], - uri: str, - ) -> int: - """Write entries to a NeMo Datastore dataset. - - Args: - entries: List of entry dictionaries - uri: NeMo Datastore URI in format nds://workspace/dataset_name - - Returns: - Number of records written - - Raises: - ValueError: If no entries provided or URI is invalid - """ - if len(entries) == 0: - raise ValueError("No entries found matching the export criteria. Please verify your filters and try again.") - - workspace, dataset_name = extract_nds_path(uri) - dataset_id = f"{workspace}/{dataset_name}" - - logger.info("Attempting to export %d entries to NDS dataset %s", len(entries), dataset_id) - - # Create temporary file with JSONL content - with tempfile.NamedTemporaryFile(mode="w", suffix=".jsonl", delete=False) as tmp_file: - for entry in entries: - json_str = json.dumps(entry) - tmp_file.write(json_str + "\n") - tmp_path = tmp_file.name - - try: - # Run synchronous operations in executor - loop = asyncio.get_event_loop() - - # Create the dataset if it doesn't exist - await loop.run_in_executor(None, self.datastore_client.create_dataset, dataset_id) - - # Upload the file to the dataset - await loop.run_in_executor( - None, - self.datastore_client.upload_file, - tmp_path, - dataset_id, - "data.jsonl", - f"Export {len(entries)} entries from Intake", - ) - - logger.info("Successfully exported %d entries to NDS dataset %s", len(entries), dataset_id) - return len(entries) - - except Exception as e: - logger.error("Failed to export to NDS dataset %s: %s", dataset_id, str(e)) - raise Exception(f"Failed to export to NDS dataset: {str(e)}") - finally: - # Clean up temporary file - os.unlink(tmp_path) diff --git a/services/intake/src/nmp/intake/app/utils/__init__.py b/services/intake/src/nmp/intake/app/utils/__init__.py deleted file mode 100644 index e3be422b21..0000000000 --- a/services/intake/src/nmp/intake/app/utils/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. -# SPDX-License-Identifier: Apache-2.0 - -"""Utility functions.""" diff --git a/services/intake/src/nmp/intake/app/utils/datastore.py b/services/intake/src/nmp/intake/app/utils/datastore.py deleted file mode 100644 index 091cfdc779..0000000000 --- a/services/intake/src/nmp/intake/app/utils/datastore.py +++ /dev/null @@ -1,117 +0,0 @@ -# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. -# SPDX-License-Identifier: Apache-2.0 - -"""Client for interacting with the NeMo Datastore service. - -DEPRECATED: This module is deprecated and will be removed in a future release. -Use the Files API instead for file storage and retrieval operations. -""" - -from __future__ import annotations - -import logging -import warnings -from pathlib import Path -from typing import BinaryIO - -from huggingface_hub import HfApi -from huggingface_hub.errors import HfHubHTTPError - -# TODO(v2): CONFIG -from nmp.intake.config import config - -logger = logging.getLogger(__name__) - -# Emit deprecation warning when module is imported -warnings.warn( - "nmp.intake.app.utils.datastore is deprecated and will be removed in a future release. Use the Files API instead.", - DeprecationWarning, - stacklevel=2, -) - - -class DataStoreClient: - """Client for interacting with the NeMo Datastore service. - - .. deprecated:: - This class is deprecated. Use the Files API instead for file storage - and retrieval operations. - """ - - def __init__(self, base_url: str | None = None, token: str | None = None): - """Initialize the client. - - Args: - base_url: Optional base URL for the DataStore service. If not provided, uses the URL from config. - token: Optional authentication token. If not provided, uses the token from config. - """ - self.base_url = base_url or config.datastore_url - # HF-compatible Files endpoints require service principal auth when platform auth is enabled. - self.token = token or "service:intake" - self.api = HfApi(endpoint=f"{self.base_url}/v1/hf", token=self.token) - - def dataset_exists(self, dataset_id: str) -> bool: - """Check if a dataset exists. - - Args: - dataset_id: The dataset ID in format `workspace/name` - - Returns: - bool: True if the dataset exists, False otherwise - - Raises: - HfHubHTTPError: If the API request fails for reasons other than 404 - """ - try: - return self.api.repo_exists(repo_id=dataset_id, repo_type="dataset") - except HfHubHTTPError as e: - if e.response.status_code == 404: - return False - raise - - def create_dataset(self, dataset_id: str) -> None: - """Create a new dataset. - - Args: - dataset_id: The dataset ID in format 'workspace/name' - - Raises: - HfHubHTTPError: If dataset creation fails - """ - logger.debug("Creating dataset: %s", dataset_id) - self.api.create_repo(repo_id=dataset_id, repo_type="dataset") - - def delete_dataset(self, dataset_id: str) -> None: - """Delete a dataset. - - Args: - dataset_id: The dataset ID in format 'workspace/name' - - Raises: - HfHubHTTPError: If dataset deletion fails - """ - logger.debug("Deleting dataset: %s", dataset_id) - self.api.delete_repo(repo_id=dataset_id, repo_type="dataset", missing_ok=True) - - def upload_file( - self, file_path: str | Path | BinaryIO, dataset_id: str, path_in_repo: str, commit_message: str | None = None - ) -> None: - """Upload a file to a dataset. - - Args: - file_path: Path to the file to upload, or a file-like object - dataset_id: The dataset ID in format `workspace/name` - path_in_repo: Path where the file should be stored in the dataset - commit_message: Optional commit message for the upload - - Raises: - HfHubHTTPError: If file upload fails - """ - logger.debug("Uploading file to dataset %s at path %s", dataset_id, path_in_repo) - self.api.upload_file( - path_or_fileobj=file_path, - path_in_repo=path_in_repo, - repo_id=dataset_id, - repo_type="dataset", - commit_message=commit_message, - ) diff --git a/services/intake/src/nmp/intake/app/utils/exports.py b/services/intake/src/nmp/intake/app/utils/exports.py deleted file mode 100644 index d68b5688a2..0000000000 --- a/services/intake/src/nmp/intake/app/utils/exports.py +++ /dev/null @@ -1,113 +0,0 @@ -# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. -# SPDX-License-Identifier: Apache-2.0 - -import logging -from pathlib import Path -from urllib.parse import urlparse - -# Configure logger with a specific name -logger = logging.getLogger("nmp.intake.app.utils.exports") - -# Supported file extensions for exports -SUPPORTED_FILE_EXTENSIONS: list[str] = [".jsonl"] - - -def is_local_file_uri(uri: str) -> bool: - """Check if URI is local filesystem (file://).""" - parsed = urlparse(uri) - logger.debug(f"Checking if {uri} is a local file URI. Scheme: {parsed.scheme}") - return parsed.scheme == "file" - - -def is_datastore_uri(uri: str) -> bool: - """Check if URI is HuggingFace dataset (hf://) or NeMo Datastore (nds://).""" - parsed = urlparse(uri) - logger.debug(f"Checking if {uri} is a datastore URI. Scheme: {parsed.scheme}") - return parsed.scheme in ("hf", "nds") - - -def extract_local_path(uri: str) -> Path: - """Extract filesystem path from file:// URI.""" - logger.debug(f"Extracting local path from URI: {uri}") - parsed = urlparse(uri) - if parsed.scheme != "file": - logger.error(f"Invalid URI scheme: {parsed.scheme}. Expected 'file'") - raise ValueError(f"Not a file:// URI: {uri}") - - path = Path(parsed.path) - logger.debug(f"Extracted path: {path}") - logger.debug(f"Absolute path: {path.absolute()}") - return path - - -def extract_datastore_path(uri: str) -> tuple[str, str]: - """Extract dataset ID and file path from hf:// URI. - - Args: - uri: The HuggingFace URI in format hf://datasets/namespace/name/path/to/file - - Returns: - tuple[str, str]: (dataset_id, path_in_repo) - """ - logger.debug(f"Extracting datastore path from URI: {uri}") - uri_components = urlparse(uri) - if uri_components.scheme != "hf": - logger.error(f"Invalid URI scheme: {uri_components.scheme}. Expected 'hf'") - raise ValueError(f"Not a hf:// URI: {uri}") - - # Remove leading slash from path - dataset_path = uri_components.path.lstrip("/") - path_parts = dataset_path.split("/") - logger.debug(f"Path parts: {path_parts}") - - # Ensure path contains at least two parts (namespace/name) - if len(path_parts) < 2: - logger.error(f"Invalid path format. Got {len(path_parts)} parts, expected at least 2") - raise ValueError("Invalid URI format. Expected format: hf://datasets/namespace/dataset_name/file_name") - - dataset_id = f"{path_parts[0]}/{path_parts[1]}" - logger.info(f"dataset_id: {dataset_id}") - - # Extract file path from remaining parts of URI - path_in_repo = "/".join(path_parts[2:]) if len(path_parts) > 2 else "" - logger.info(f"path_in_repo: {path_in_repo}") - - if not path_in_repo or not path_in_repo.endswith(tuple(SUPPORTED_FILE_EXTENSIONS)): - logger.error(f"Invalid file path: {path_in_repo}. Supported extensions: {SUPPORTED_FILE_EXTENSIONS}") - raise ValueError("Invalid file path. Supported file extensions: " + ", ".join(SUPPORTED_FILE_EXTENSIONS)) - - return dataset_id, path_in_repo - - -def extract_nds_path(uri: str) -> tuple[str, str]: - """Extract workspace and dataset name from nds:// URI. - - Args: - uri: The NeMo Datastore URI in format nds://workspace/dataset_name - - Returns: - tuple[str, str]: (workspace, dataset_name) - """ - logger.debug(f"Extracting NDS path from URI: {uri}") - uri_components = urlparse(uri) - if uri_components.scheme != "nds": - logger.error(f"Invalid URI scheme: {uri_components.scheme}. Expected 'nds'") - raise ValueError(f"Not a nds:// URI: {uri}") - - # For nds://workspace/dataset_name: - # - workspace is in netloc - # - dataset_name is in path (without leading slash) - workspace = uri_components.netloc - dataset_name = uri_components.path.lstrip("/") - - logger.debug(f"Parsed workspace: {workspace}, dataset_name: {dataset_name}") - - # Validate that both parts exist - if not workspace or not dataset_name: - logger.error(f"Invalid URI format. workspace='{workspace}', dataset_name='{dataset_name}'") - raise ValueError("Invalid URI format. Expected format: nds://workspace/dataset_name") - - logger.info(f"workspace: {workspace}") - logger.info(f"dataset_name: {dataset_name}") - - return workspace, dataset_name diff --git a/services/intake/src/nmp/intake/config.py b/services/intake/src/nmp/intake/config.py index 00395291c9..d8b8565153 100644 --- a/services/intake/src/nmp/intake/config.py +++ b/services/intake/src/nmp/intake/config.py @@ -43,11 +43,6 @@ class IntakeConfig(_BaseIntakeConfig): Environment variables use the NMP_INTAKE_ prefix. """ - # TODO(v2): CONFIG - deprecated, used by DataStoreClient - datastore_url: str = Field( - default="http://nemo-data-store:8000", - description="URL for the DataStore service", - ) clickhouse_config: ClickHouseConfig = Field( default_factory=ClickHouseConfig, description="ClickHouse connection settings for Intake spans storage.", @@ -57,7 +52,3 @@ class IntakeConfig(_BaseIntakeConfig): ge=1024, description="Maximum accepted body size for OTLP ingest requests, in bytes.", ) - - -# Backward-compatible module-level config for legacy imports. -config = IntakeConfig() diff --git a/services/intake/src/nmp/intake/entities/__init__.py b/services/intake/src/nmp/intake/entities/__init__.py deleted file mode 100644 index 10e50ba54e..0000000000 --- a/services/intake/src/nmp/intake/entities/__init__.py +++ /dev/null @@ -1,74 +0,0 @@ -# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. -# SPDX-License-Identifier: Apache-2.0 - -"""Intake entities package. - -This package contains all entity definitions, value types, and enums for the Intake service. -""" - -# Enums -# Entity classes -from .entities import ( - App, - Entry, - ExportJob, - Task, -) -from .enums import ( - EntryEventType, - ExportMode, - ExportStatus, - JobStatus, - MessageRole, - RowTransformation, - ThumbDirection, -) - -# Value types -from .values import ( - EntryContext, - EntryData, - EntryEvent, - EvaluatorResultEvent, - ExportConfig, - ExportStatusDetails, - FlexibleEntryRequest, - FlexibleEntryResponse, - FlexibleMessage, - ReviewerAnnotationEvent, - Usage, - UserActionEvent, - UserFeedbackEvent, - UserRating, -) - -__all__ = [ - # Enums - "EntryEventType", - "ExportMode", - "ExportStatus", - "JobStatus", - "MessageRole", - "RowTransformation", - "ThumbDirection", - # Value types - "EntryContext", - "EntryData", - "EntryEvent", - "EvaluatorResultEvent", - "ExportConfig", - "ExportStatusDetails", - "FlexibleEntryRequest", - "FlexibleEntryResponse", - "FlexibleMessage", - "ReviewerAnnotationEvent", - "Usage", - "UserActionEvent", - "UserFeedbackEvent", - "UserRating", - # Entity classes - "App", - "Entry", - "ExportJob", - "Task", -] diff --git a/services/intake/src/nmp/intake/entities/entities.py b/services/intake/src/nmp/intake/entities/entities.py deleted file mode 100644 index 4be73ee4a7..0000000000 --- a/services/intake/src/nmp/intake/entities/entities.py +++ /dev/null @@ -1,153 +0,0 @@ -# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. -# SPDX-License-Identifier: Apache-2.0 - -"""Entity definitions for the Intake service using EntityBase. - -These entities use the new EntityClient pattern with EntityBase for -compatibility with the v2 Entity Store. -""" - -from typing import Any, Dict, List, Optional - -from nmp.common.entities.client import EntityBase -from pydantic import AnyUrl, Field, model_validator - -from .enums import JobStatus -from .values import ( - EntryContext, - EntryData, - EntryEvent, - ExportConfig, - ExportStatusDetails, - Usage, - UserRating, -) - - -class App(EntityBase): - """Application that sends data to the Intake service. - - Apps are scoped by workspace, identified by workspace/name. - """ - - __entity_type__ = "intake_app" - - description: str | None = Field(default=None, description="App description") - locked: bool = Field( - default=False, - description=( - "If true, this record cannot be automatically updated when entries are ingested. " - "When an entry is created, the system normally auto-updates the app's metadata (name, description). " - "Set locked=true to prevent these automatic updates and preserve manually curated information. " - "The record can still be modified via explicit PATCH requests." - ), - ) - - -class Task(EntityBase): - """Logical task within an application. - - Tasks are scoped by workspace, but also sub-entities of Apps. - Task names must be unique within their parent app. - """ - - __entity_type__ = "intake_task" - - description: str | None = Field(default=None, description="Task description") - app: str = Field(..., description="The app this task belongs to, in the form `workspace/name`.") - locked: bool = Field( - default=False, - description=( - "If true, this record cannot be automatically updated when entries are ingested. " - "When an entry is created, the system normally auto-updates the task's metadata (name, description). " - "Set locked=true to prevent these automatic updates and preserve manually curated information. " - "The record can still be modified via explicit PATCH requests." - ), - ) - - -class Entry(EntityBase): - """LLM completion entry stored in the Intake service. - - Entries have auto-generated names and can optionally have an external_id - for referencing by client-provided identifiers. - """ - - __entity_type__ = "intake_entry" - - # External identifier (e.g., completion_id from LLM provider) - external_id: Optional[str] = Field( - default=None, - description=( - "Optional client-provided identifier (e.g., completion_id from an LLM provider like OpenAI or NIM). " - "Must be globally unique if provided—attempting to create an entry with a duplicate external_id will fail with a 409 error. " - "If your service provides unique IDs (like 'chatcmpl-abc123'), you should use them here for easier lookups. " - "Entries can be retrieved using external_id via the prefix syntax: GET /entries/external:chatcmpl-abc123" - ), - ) - - # Primary artifacts - data: EntryData = Field(..., description="Entry data containing request and response.") - - # Usage metrics - usage: Optional[Usage] = Field( - default=None, - description="Structured usage metrics (model served, latency, cost, token counts).", - ) - - # Contextual metadata - context: EntryContext = Field(..., description="Metadata describing producer, task, trace.") - - # User feedback and events - user_rating: Optional[UserRating] = Field(default=None, description="User's rating/evaluation of the AI response.") - events: List[EntryEvent] = Field(default_factory=list, description="All events associated with this entry.") - - # Free-form metadata bag for client-defined fields (e.g., experiment metadata - # from external eval frameworks). Stored as a JSON column on the entity store. - custom_fields: Optional[Dict[str, Any]] = Field( - default=None, - description=( - "Free-form metadata bag for client-defined fields. " - "Use this to attach structured client metadata (e.g., experiment provenance, " - "external job IDs) that doesn't fit elsewhere on the entry." - ), - ) - - @model_validator(mode="before") - @classmethod - def _coerce_nested_models(cls, data: Any) -> Any: - """Coerce nested dict fields to their proper Pydantic model types. - - When entities are loaded from the Entity Store, nested Pydantic models - come back as raw dicts. This validator ensures they are properly coerced - to avoid Pydantic serialization warnings. - """ - if isinstance(data, dict) and isinstance(data.get("user_rating"), dict): - data["user_rating"] = UserRating(**data["user_rating"]) - return data - - -class ExportJob(EntityBase): - """Export job tracking entry exports to external datastores. - - Export jobs track the status of background export tasks. - """ - - __entity_type__ = "intake_export_job" - - status: JobStatus = Field(default=JobStatus.PENDING, description="Job status") - - config: ExportConfig = Field( - ..., - description="The export configuration defining filters, search criteria, and format options.", - ) - - output_file_url: Optional[AnyUrl] = Field( - default=None, - description="The place where the exported file should be written (file://, hf://, nds://, etc.)", - ) - - status_details: Optional[ExportStatusDetails] = Field( - default_factory=ExportStatusDetails, - description="Details about the status of the export job.", - ) diff --git a/services/intake/src/nmp/intake/entities/enums.py b/services/intake/src/nmp/intake/entities/enums.py deleted file mode 100644 index ecca7f9c36..0000000000 --- a/services/intake/src/nmp/intake/entities/enums.py +++ /dev/null @@ -1,106 +0,0 @@ -# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. -# SPDX-License-Identifier: Apache-2.0 - -"""Enums for the Intake service.""" - -from enum import Enum - - -class EntryEventType(str, Enum): - """High-level categorization of events linked to an entry. - - ### Values - - #### user_feedback - User-supplied opinion about the generated entry. Examples: - - - **Thumbs-up / thumbs-down** – simple binary rating. - - **Chosen response** – the user selected one of several responses. - - **Rewrite / edit** – the user provided an improved version. - - #### user_action - Arbitrary client-defined action taken after the entry is shown. Examples: - - - **share_clicked** – user clicked a *Share* button. - - **code_copied** – user copied a code snippet to the clipboard. - - **purchase_made** – user purchased an item recommended by the assistant. - - #### reviewer_annotation - Reviewer-supplied opinion about the existing entry. Examples: - - **Thumbs-up / thumbs-down** – simple binary rating. - - **Chosen response** – the reviewer selected one of several responses. - - **Rewrite / edit** – the reviewer provided an improved version. - - #### evaluator_result - Score produced by an automated evaluator (verifier, judge, auditor, - external eval framework, etc.). Examples: - - - **Verifier reward** – numeric reward from an eval framework's verifier. - - **LLM-judge score** – numeric or categorical judgement. - - **Auditor probe result** – pass/fail or graded score from a security probe. - """ - - user_feedback = "user_feedback" - user_action = "user_action" - reviewer_annotation = "reviewer_annotation" - evaluator_result = "evaluator_result" - - -class MessageRole(str, Enum): - """Valid role values for entry request messages.""" - - user = "user" - system = "system" - assistant = "assistant" - developer = "developer" - tool = "tool" - function = "function" - - -class ThumbDirection(str, Enum): - """Possible thumb feedback choices.""" - - up = "up" - down = "down" - - -class ExportMode(str, Enum): - """Export and search modes for retrieving entry data. - - Currently only 'entries' mode is supported. Future modes may be added. - """ - - entries = "entries" - - -class ExportStatus(str, Enum): - """Status values for export jobs.""" - - pending = "pending" - in_progress = "in_progress" - completed = "completed" - failed = "failed" - - -class RowTransformation(str, Enum): - """Supported transformations for exported rows. - - ### Values - - #### use_annotation_rewrite - Use reviewer annotation response_override to replace the original response when available. - Note: The 'rewrite' field contains end-user suggestions and does not modify exports. - Reviewers should use 'response_override' for corrections. - """ - - use_annotation_rewrite = "use_annotation_rewrite" - - -class JobStatus(str, Enum): - """Job status enum.""" - - PENDING = "pending" - RUNNING = "running" - COMPLETED = "completed" - FAILED = "failed" - CANCELLED = "cancelled" diff --git a/services/intake/src/nmp/intake/entities/values.py b/services/intake/src/nmp/intake/entities/values.py deleted file mode 100644 index 1928501b0e..0000000000 --- a/services/intake/src/nmp/intake/entities/values.py +++ /dev/null @@ -1,614 +0,0 @@ -# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. -# SPDX-License-Identifier: Apache-2.0 - -"""Value objects for the Intake service.""" - -from datetime import datetime, timezone -from typing import Annotated, Any, Dict, List, Literal, Optional, Self, TypeAlias, Union - -from nmp.common.entities.values import Value -from pydantic import BaseModel, ConfigDict, Field, field_validator, model_validator - -from .enums import MessageRole, ThumbDirection - -# --------------------------------------------------------------------------- -# Type aliases -# --------------------------------------------------------------------------- - -# Constrained string type alias for ≤256-char values (schema adds maxLength) -ShortStr: TypeAlias = Annotated[str, Field(max_length=256)] - -# Allowed value types for user-action metadata entries -UserActionValue = Union[ShortStr, List[ShortStr], bool, int, float] - - -# --------------------------------------------------------------------------- -# Flexible Messages and Requests -# --------------------------------------------------------------------------- - - -class FlexibleMessage(BaseModel): - """A flexible message model that requires a valid role field but allows any other fields. - - This flexibility enables the Intake service to store messages from various LLM providers - and future model types without requiring schema updates. Additional fields like `content`, - `name`, `tool_calls`, `tool_call_id`, etc. are all accepted. - - Examples of additional fields: - - `content`: The message text or content - - `name`: Name of the user or function - - `tool_calls`: Tool/function calls in the message - - `tool_call_id`: ID of the tool call being responded to - """ - - model_config = ConfigDict(extra="allow") - - role: MessageRole = Field( - ..., - description=( - "The role of the message sender. Must be one of: user, system, assistant, developer, tool, or function." - ), - ) - - -class FlexibleEntryRequest(BaseModel): - """Flexible entry request that accepts any object shape. - - This flexibility enables the Intake service to store requests from various LLM providers - (OpenAI, Anthropic, NIM, etc.) and future model types (embeddings, multimodal, etc.) - without requiring schema updates. - - Required fields: `messages` and `model` - Common optional fields: `temperature`, `max_tokens`, `top_p`, `tools`, `tool_choice`, - `stream`, `response_format`, etc. - """ - - model_config = ConfigDict(extra="allow") - - messages: List[FlexibleMessage] = Field( - ..., - description=( - "A list of messages comprising the conversation. Each message must have a valid role. " - "Additional fields like `content`, `tool_calls`, etc. are provider-specific." - ), - ) - model: str = Field( - ..., - description="The model identifier used for this request (e.g., 'gpt-4', 'llama-3-70b', 'claude-3-opus').", - ) - - -class FlexibleEntryResponse(BaseModel): - """Flexible entry response that accepts any object shape. - - This flexibility enables the Intake service to store responses from various LLM providers - and future model types without requiring schema updates. - - Required: either `choices` (successful response) or `error` (failed call). - Common optional fields: `id`, `created`, `model`, `usage`, `system_fingerprint`, etc. - """ - - model_config = ConfigDict( - extra="allow", - json_schema_extra={ - "oneOf": [ - {"required": ["choices"]}, - {"required": ["error"]}, - ], - }, - ) - - choices: Optional[List[Dict[str, Any]]] = Field( - default=None, - description=( - "A list of response choices generated by the model. Each choice typically contains " - "a message object with role, content, and optional tool_calls. " - "The structure varies by provider but commonly includes fields like `index`, `message`, and `finish_reason`." - ), - ) - error: Optional[Dict[str, Any]] = Field( - default=None, - description=( - "Error envelope returned by the provider when the call failed (e.g., OpenAI's " - "`{message, type, code, param}` shape). Mutually exclusive with `choices`: a response " - "carries one or the other." - ), - ) - - @model_validator(mode="after") - def _require_choices_or_error(self) -> Self: - # XOR by presence: choices=[] still counts as "present" — the entries endpoint - # historically accepts callers that supply an empty list, so we treat omission - # (None) as the only "missing" state. - if (self.choices is None) == (self.error is None): - if self.choices is None: - raise ValueError("response must include either `choices` or `error`") - raise ValueError("response cannot include both `choices` and `error`") - return self - - -# --------------------------------------------------------------------------- -# Core Value Objects -# --------------------------------------------------------------------------- - - -class Usage(Value): - """Structured usage metrics captured at log time. - - Every field is optional so producers can populate whatever they have without - schema breakage. Stored as the entry-level ``usage`` field so filters can - reach it via ``data.usage.`` entity-store paths. - """ - - model: Optional[str] = Field( - default=None, - description="The actual model that served the request (after any routing). May differ from the model in the request body.", - ) - started_at: Optional[datetime] = Field( - default=None, - description="UTC timestamp when the upstream LLM call started.", - json_schema_extra={"format": "date-time"}, - ) - ended_at: Optional[datetime] = Field( - default=None, - description="UTC timestamp when the upstream LLM call ended.", - json_schema_extra={"format": "date-time"}, - ) - latency_ms: Optional[int] = Field( - default=None, - ge=0, - description="Wall-clock latency of the upstream LLM call, in milliseconds.", - json_schema_extra={"minimum": 0}, - ) - cost_usd: Optional[float] = Field( - default=None, - ge=0, - description="Total estimated cost of this call, in USD.", - json_schema_extra={"minimum": 0}, - ) - cost_input_usd: Optional[float] = Field( - default=None, - ge=0, - description="Estimated cost attributed to input tokens, in USD.", - json_schema_extra={"minimum": 0}, - ) - cost_output_usd: Optional[float] = Field( - default=None, - ge=0, - description="Estimated cost attributed to output tokens, in USD.", - json_schema_extra={"minimum": 0}, - ) - input_tokens: Optional[int] = Field( - default=None, - ge=0, - description="Number of input tokens consumed.", - json_schema_extra={"minimum": 0}, - ) - output_tokens: Optional[int] = Field( - default=None, - ge=0, - description="Number of output tokens produced.", - json_schema_extra={"minimum": 0}, - ) - cached_tokens: Optional[int] = Field( - default=None, - ge=0, - description="Number of input tokens served from a prompt cache (subset of input_tokens).", - json_schema_extra={"minimum": 0}, - ) - - @field_validator("started_at", "ended_at") - @classmethod - def _ensure_utc_timestamp(cls, value: Optional[datetime]) -> Optional[datetime]: - if value is None: - return value - if value.tzinfo is None or value.utcoffset() is None: - return value.replace(tzinfo=timezone.utc) - return value.astimezone(timezone.utc) - - @model_validator(mode="after") - def _validate_timestamp_order(self) -> Self: - if self.started_at is not None and self.ended_at is not None and self.ended_at < self.started_at: - raise ValueError("ended_at must be greater than or equal to started_at") - return self - - @model_validator(mode="after") - def _validate_cached_tokens(self) -> Self: - if self.cached_tokens is not None and self.input_tokens is not None and self.cached_tokens > self.input_tokens: - raise ValueError("cached_tokens must be less than or equal to input_tokens") - return self - - -class EntryData(Value): - """Entry data containing the request and response for an LLM interaction.""" - - request: FlexibleEntryRequest = Field(..., description="Raw request payload recorded from the client.") - response: FlexibleEntryResponse = Field(..., description="Raw response payload generated by the model.") - - -class EntryContext(Value): - """Contextual metadata attached to every entry record. - - Keeping these grouped in a dedicated object avoids polluting the top-level - entity schema and makes it trivial to extend without breaking compatibility. - """ - - # Required fields ------------------------------------------------------ - - app: str = Field( - ..., - description=( - "Reference to the app that produced this entry, in the form `workspace/name`. " - "If the app doesn't exist, it will be automatically created when the entry is ingested." - ), - ) - task: Optional[str] = Field( - ..., - description=( - "Name of the task within the app (e.g., 'chat', 'completion', 'tool-call'). " - "If the task doesn't exist, it will be automatically created when the entry is ingested." - ), - ) - - thread_id: Optional[str] = Field( - default=None, - description=( - "Logical thread identifier that groups related entries in a multi-turn conversation. " - "If provided, entries with the same thread_id are treated as part of the same conversation. " - "If omitted, the entry is treated as a single-turn interaction (e.g., a standalone tool call or completion)." - ), - ) - - user_id: Optional[str] = Field( - default=None, - description=( - "Identifier of the application's end-user who triggered this LLM interaction. " - "This represents the person using your application (e.g., 'customer_123', 'employee@company.com'), " - "NOT the service account that created the entry record (see ownership.created_by for that). " - "Use this to track which of your users a conversation belongs to, filter entries by user, " - "and enable per-user analytics. Format is application-defined." - ), - ) - - # Optional tracing fields ----------------------------------------------- - trace_id: Optional[str] = Field( - default=None, - description=( - "Distributed trace identifier (e.g., W3C traceparent). Intake stores it " - "verbatim and does not use it at ingestion time; helps with later cross-system joins." - ), - ) - - session_id: Optional[str] = Field( - default=None, - description=( - "Long-lived session identifier (e.g., user account or browser session). " - "Stored for post-processing analytics; not used by the Intake service at runtime." - ), - ) - - # Timestamps - created_at: datetime = Field( - default_factory=lambda: datetime.now(timezone.utc), - description="UTC timestamp when the entry was created.", - ) - - -class UserRating(Value): - """User's rating/evaluation of an AI response. - - This captures various forms of end-user feedback about a model's response, including - binary thumbs up/down ratings, numeric scores, free-text opinions, suggested rewrites, - and structured category ratings. - - Either `thumb` or `rating` should be provided (they are mutually exclusive), but all - fields are optional to accommodate different feedback collection patterns. - """ - - thumb: Optional[ThumbDirection] = Field( - default=None, - description=( - 'Binary feedback: "up" for 👍 or "down" for 👎. ' - "Mutually exclusive with `rating`. Use this for simple thumbs up/down UI elements." - ), - ) - rating: Optional[float] = Field( - default=None, - ge=0, - description=( - "Numeric rating (e.g., 1-5 stars) provided by the end user. " - "Mutually exclusive with `thumb`. Use this for star ratings or numeric scales." - ), - ) - opinion: Optional[str] = Field( - default=None, - min_length=1, - max_length=2_000, - description="Free-text comment from the end user describing their opinion of the response.", - ) - rewrite: Optional[str] = Field( - default=None, - min_length=1, - max_length=10_000, - description=( - "End-user's suggested text replacement for the generated response. " - "This is the user's idea of what the response should have been." - ), - ) - chosen_index: Optional[int] = Field( - default=None, - ge=0, - description=( - "Zero-based index of the response option the user selected when multiple responses were returned. " - "Use this when showing users multiple completion choices and tracking which one they picked." - ), - ) - categories: Optional[Dict[str, Union[float, str]]] = Field( - default=None, - description=( - "Application-specific category ratings as key-value pairs. " - "Use this for custom rating dimensions (e.g., {'helpfulness': 4, 'accuracy': 5, 'tone': 'professional'}). " - "Useful for radio buttons, dropdowns, or multi-dimensional rating systems." - ), - ) - - @field_validator("rewrite") - @classmethod - def _strip_whitespace(cls, v: Optional[str]): # noqa: D401, N805 - return v.strip() if isinstance(v, str) else v - - -# --------------------------------------------------------------------------- -# Events Base Class -# --------------------------------------------------------------------------- - - -class _BaseEvent(Value): # noqa: D101 (internal base) - """Base class for all entry events.""" - - id: Optional[str] = Field( - default=None, - description="Unique identifier for the event. Populated when retrieved from database.", - ) - created_at: datetime = Field( - default_factory=lambda: datetime.now(timezone.utc), - description="UTC timestamp when the record was created.", - ) - created_by: Optional[Dict[str, str]] = Field( - default=None, - description="Identifier of the user or system that generated the record. Can be set of key-value pairs.", - ) - - -# --------------------------------------------------------------------------- -# Event Types -# --------------------------------------------------------------------------- - - -class UserFeedbackEvent(_BaseEvent): - """Structured feedback supplied by an end-user. - - This event captures various forms of end-user feedback about a model's response, - including binary thumbs up/down ratings, numeric scores, free-text opinions, - suggested rewrites, and structured category ratings. - - Either `thumb` or `rating` should be provided (they are mutually exclusive), but all - feedback fields are optional to accommodate different feedback collection patterns. - """ - - model_config = ConfigDict(extra="forbid") - - event_type: Literal["user_feedback"] = "user_feedback" # noqa: A003 - - # Feedback specifics ---------------------------------------------------- - thumb: Optional[ThumbDirection] = Field( - default=None, - description=( - 'Binary feedback: "up" for 👍 or "down" for 👎. ' - "Mutually exclusive with `rating`. Use this for simple thumbs up/down UI elements." - ), - ) - rating: Optional[float] = Field( - default=None, - ge=0, - description=( - "Numeric rating (e.g., 1-5 stars) provided by the end user. " - "Mutually exclusive with `thumb`. Use this for star ratings or numeric scales." - ), - ) - opinion: Optional[str] = Field( - default=None, - min_length=1, - max_length=2_000, - description="Free-text comment from the end user describing their opinion of the response.", - ) - rewrite: Optional[str] = Field( - default=None, - min_length=1, - max_length=10_000, - description=( - "End-user's suggested text replacement for the generated response. " - "This is the user's idea of what the response should have been." - ), - ) - chosen_index: Optional[int] = Field( - default=None, - ge=0, - description=( - "Zero-based index of the response option the user selected when multiple responses were returned. " - "Use this when showing users multiple completion choices and tracking which one they picked." - ), - ) - categories: Optional[Dict[str, Union[float, str]]] = Field( - default=None, - description=( - "Application-specific category ratings as key-value pairs. " - "Use this for custom rating dimensions (e.g., {'helpfulness': 4, 'accuracy': 5, 'tone': 'professional'}). " - "Useful for radio buttons, dropdowns, or multi-dimensional rating systems." - ), - ) - - @field_validator("rewrite") - @classmethod - def _strip_whitespace(cls, v: Optional[str]): # noqa: D401, N805 - return v.strip() if isinstance(v, str) else v - - -class UserActionEvent(_BaseEvent): - """Free-form user action captured by the client application. - - Use this to track arbitrary user interactions with AI responses, such as copying code, - clicking share buttons, making purchases, or any other measurable action. - - The action identifier must be ≤256 characters and should use snake-case or kebab-case - for consistency (e.g., ``share_clicked``, ``code_copied``, ``purchase_made``). - """ - - model_config = ConfigDict(extra="forbid") - - event_type: Literal["user_action"] = "user_action" # noqa: A003 - - action: str = Field( - ..., - max_length=256, - description=( - "Descriptive name for the action taken by the user (e.g., 'share_clicked', 'code_copied', 'link_followed'). " - "Use snake-case or kebab-case. This is a label, not a unique identifier—multiple events can have the same action name." - ), - ) - metadata: Dict[ShortStr, UserActionValue] = Field( - default_factory=dict, - description=( - "Optional key-value pairs with additional context about the action (max 16 entries). " - "Use this for details like user IDs, item IDs, timestamps, A/B test variants, or any other " - "information useful for downstream training or evaluation pipelines. " - "Example: {'user_id': '12345', 'experiment': 'variant_b', 'item_purchased': 'product_456'}." - ), - max_length=16, - ) - - -class ReviewerAnnotationEvent(UserFeedbackEvent): - """Structured annotation supplied by a reviewer or expert evaluator. - - A reviewer annotation is similar to user feedback but includes an additional capability - to provide a complete replacement response. This is useful when human experts need to - correct not just the text but also structured elements like tool calls, function outputs, - or other response metadata. - - Inherits all feedback fields from UserFeedbackEvent (thumb, rating, opinion, rewrite, - chosen_index, categories) and adds response_override for full response replacement. - """ - - event_type: Literal["reviewer_annotation"] = "reviewer_annotation" # type: ignore[assignment] - - response_override: Optional[Dict[str, Any]] = Field( - default=None, - description=( - "Complete JSON object that replaces the original model response when exporting data. " - "Unlike the `rewrite` field (which is just text), this can include tool calls, function outputs, " - "and all other response metadata. When an entry with response_override is exported, you can choose " - "to use this corrected response instead of the original. " - "Example: {'choices': [{'message': {'role': 'assistant', 'content': 'Corrected text', " - "'tool_calls': [...]}}]}" - ), - ) - - -class EvaluatorResultEvent(_BaseEvent): - """Result produced by an automated evaluator. - - Use this for any non-human scorer that emits a judgement about an entry: - eval-framework verifier rewards, LLM-judge ratings, auditor probe results, etc. - Distinct from UserFeedbackEvent (human end-user feedback) and ReviewerAnnotationEvent - (human expert annotation). - """ - - model_config = ConfigDict(extra="forbid") - - event_type: Literal["evaluator_result"] = "evaluator_result" # noqa: A003 - - name: str = Field( - ..., - max_length=256, - description=( - "Identifier of the evaluator that produced this result " - "(e.g., 'harbor.verifier', 'evaluator.llm_judge', 'auditor.pii_probe')." - ), - ) - score: Optional[Union[int, float, str]] = Field( - default=None, - description=( - "The result value: a number (e.g., reward, rating, probability) or a string " - "(e.g., 'pass', 'fail', a category label). Semantics are defined by the evaluator." - ), - ) - metadata: Optional[Dict[str, Any]] = Field( - default=None, - description=( - "Free-form additional context about this result " - "(e.g., supporting metrics, trial_name, rubric_version, evaluator config snapshot)." - ), - ) - - -# Discriminated union -------------------------------------------------------- - -EntryEvent = Annotated[ - Union[UserFeedbackEvent, UserActionEvent, ReviewerAnnotationEvent, EvaluatorResultEvent], - Field(discriminator="event_type"), -] - - -# --------------------------------------------------------------------------- -# Export Job Configuration and Status -# --------------------------------------------------------------------------- - - -class ExportConfig(Value): - """Configuration for an export job. - - Defines what entries to export and how to format them. - """ - - filters: Optional[Dict[str, Any]] = Field( - default=None, - description=( - "Filter criteria for selecting entries (workspace, app, task, thread_id, external_id, " - "has_thumb, has_rating, longest_per_thread, model, etc.)" - ), - ) - - search: Optional[Dict[str, Any]] = Field( - default=None, - description="Search criteria for finding entries", - ) - - limit: Optional[int] = Field( - default=1000, - description="Maximum number of entries to export. None means no limit.", - ) - - format_options: Optional[Dict[str, Any]] = Field( - default=None, - description="Format options for the export (e.g., row_transformation)", - ) - - -class ExportStatusDetails(Value): - """Detailed status information for an export job.""" - - entries_count: int = Field( - default=0, - description="Number of entries exported", - ) - - progress: Optional[float] = Field( - default=None, - description="Progress percentage (0-100)", - ) - - error_message: Optional[str] = Field( - default=None, - description="Error message if the job failed", - ) diff --git a/services/intake/src/nmp/intake/service.py b/services/intake/src/nmp/intake/service.py index 0802423054..d5752174c5 100644 --- a/services/intake/src/nmp/intake/service.py +++ b/services/intake/src/nmp/intake/service.py @@ -7,10 +7,6 @@ from typing import ClassVar, List from nmp.common.service import RouterConfig, Service -from nmp.intake.api.v2.apps import endpoints as apps -from nmp.intake.api.v2.entries import endpoints as entries -from nmp.intake.api.v2.exports import endpoints as exports -from nmp.intake.api.v2.tasks import endpoints as tasks from nmp.intake.config import IntakeConfig from nmp.intake.spans.api import annotations, evaluator_results, spans, traces from nmp.intake.spans.clickhouse_client import ClickHouseSettings, ClickHouseSpanClient @@ -37,15 +33,11 @@ def title(self) -> str: @property def description(self) -> str: - return "Intake service for storing LLM entries and feedback" + return "Intake service for ingesting and reading spans, traces, annotations, and evaluator results" def get_routers(self) -> List[RouterConfig]: """Return routers for the intake service.""" return [ - RouterConfig(apps.router, tag="Apps", description="App management endpoints"), - RouterConfig(tasks.router, tag="Tasks", description="Task management endpoints"), - RouterConfig(entries.router, tag="Entries", description="Entry management endpoints"), - RouterConfig(exports.router, tag="Exports", description="Export endpoints"), RouterConfig(spans.router, tag="Spans", description="ClickHouse-backed span read endpoints"), RouterConfig(traces.router, tag="Traces", description="ClickHouse-backed trace summary read endpoints"), RouterConfig( diff --git a/services/intake/src/nmp/intake/spans/ingest/chat_completions.py b/services/intake/src/nmp/intake/spans/ingest/chat_completions.py index aa78ee3fa8..02b5592cd1 100644 --- a/services/intake/src/nmp/intake/spans/ingest/chat_completions.py +++ b/services/intake/src/nmp/intake/spans/ingest/chat_completions.py @@ -5,25 +5,24 @@ Producers POST a captured chat-completion request + response. Intake stores one IntakeSpan per response (one model invocation), preserving the raw -request and response payloads verbatim so downstream dataset export does not -need format conversion. +request and response payloads verbatim for downstream telemetry consumers. """ from __future__ import annotations import math from datetime import datetime, timezone -from typing import Any +from enum import Enum +from typing import Any, Self from fastapi import APIRouter, Depends, status -from nmp.intake.entities.values import FlexibleEntryRequest, FlexibleEntryResponse from nmp.intake.spans.api.dependencies import SpansServiceDep, require_workspace_access from nmp.intake.spans.domain import IntakeSpan, SpanKind, SpanStatus, TraceBatch from nmp.intake.spans.ingest.evaluation_context import EvaluationContext from nmp.intake.spans.span_attribute_bags import SpanAttributeBags from nmp.intake.spans.span_semantic_attributes import SpanSemanticAttributes from nmp.intake.spans.storage import json_dumps_preserve, stable_id, utc_now -from pydantic import BaseModel, ConfigDict, Field +from pydantic import BaseModel, ConfigDict, Field, model_validator router = APIRouter(dependencies=[Depends(require_workspace_access)]) API_TAG = "Ingest" @@ -31,11 +30,70 @@ SOURCE_FORMAT = "chat_completions" +class ChatMessageRole(str, Enum): + """Valid role values for captured chat-completions messages.""" + + user = "user" + system = "system" + assistant = "assistant" + developer = "developer" + tool = "tool" + function = "function" + + +class CapturedChatMessage(BaseModel): + """A flexible message model that requires a valid role field but allows provider-specific fields.""" + + model_config = ConfigDict(extra="allow") + + role: ChatMessageRole = Field( + description="The role of the message sender.", + ) + + +class CapturedChatCompletionsRequest(BaseModel): + """Flexible captured chat-completions request.""" + + model_config = ConfigDict(extra="allow") + + messages: list[CapturedChatMessage] = Field( + description="Messages comprising the conversation.", + ) + model: str = Field( + description="The model identifier used for this request.", + ) + + +class CapturedChatCompletionsResponse(BaseModel): + """Flexible captured chat-completions response.""" + + model_config = ConfigDict( + extra="allow", + json_schema_extra={ + "oneOf": [ + {"required": ["choices"]}, + {"required": ["error"]}, + ], + }, + ) + + choices: list[dict[str, Any]] | None = Field(default=None) + error: dict[str, Any] | None = Field(default=None) + + @model_validator(mode="after") + def _require_choices_or_error(self) -> Self: + if (self.choices is None) == (self.error is None): + if self.choices is None: + raise ValueError("response must include either `choices` or `error`") + raise ValueError("response cannot include both `choices` and `error`") + return self + + class ChatCompletionsIngestRequest(BaseModel): model_config = ConfigDict(extra="forbid") - request: FlexibleEntryRequest - response: FlexibleEntryResponse + request: CapturedChatCompletionsRequest + response: CapturedChatCompletionsResponse session_id: str | None = Field( default=None, diff --git a/services/intake/tests/data/action_heavy.json b/services/intake/tests/data/action_heavy.json deleted file mode 100644 index 2beaaddaec..0000000000 --- a/services/intake/tests/data/action_heavy.json +++ /dev/null @@ -1,695 +0,0 @@ -[ - { - "id": "comp_a6d33218d24d", - "request": { - "prompt": "Generate a SQL query to find active users", - "messages": [], - "tools": [], - "model": "llama-3.1-8b-instruct" - }, - "response": { - "text": "```sql\nSELECT user_id, email, last_login\nFROM users \nWHERE last_login >= DATE_SUB(NOW(), INTERVAL 30 DAY)\nAND status = 'active'\nORDER BY last_login DESC;\n```", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 30, - "entry_tokens": 120, - "total_tokens": 150 - } - }, - "context": { - "app_id": "initech", - "task_id": "code-generation", - "thread_id": null, - "created_at": "2025-05-30T00:52:51.313893+00:00" - }, - "events": [ - { - "event_type": "user_action", - "action": "copy_clicked", - "metadata": { - "copied_content": "sql_query" - } - }, - { - "event_type": "user_action", - "action": "share_clicked", - "metadata": { - "share_platform": "slack", - "team_share": true - } - } - ] - }, - { - "id": "comp_b6d2559e4858", - "request": { - "prompt": "Show me how to deploy a React app to production", - "messages": [], - "tools": [], - "model": "llama-3.1-8b-instruct" - }, - "response": { - "text": "Here's a step-by-step guide to deploy your React app:\n\n1. Build the production version:\n```bash\nnpm run build\n```\n\n2. Choose a hosting platform (Netlify, Vercel, or AWS S3)\n\n3. For Netlify:\n- Connect your GitHub repo\n- Set build command: `npm run build`\n- Set publish directory: `build`\n\n4. Configure environment variables if needed\n\nYour app will be live with automatic SSL and CDN!", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 30, - "entry_tokens": 120, - "total_tokens": 150 - } - }, - "context": { - "app_id": "acme-corp", - "task_id": "code-generation", - "thread_id": null, - "created_at": "2025-05-14T15:39:51.313899+00:00" - }, - "events": [ - { - "event_type": "user_action", - "action": "copy_clicked", - "metadata": { - "copied_content": "bash_command" - } - }, - { - "event_type": "user_action", - "action": "link_clicked", - "metadata": { - "external_link": "netlify.com" - } - } - ] - }, - { - "id": "comp_cef60b2c28fe", - "request": { - "prompt": "Sample prompt for data-analysis task", - "messages": [], - "tools": [], - "model": "llama-3.1-8b-instruct" - }, - "response": { - "text": "Response with actionable content for data-analysis", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 30, - "entry_tokens": 80, - "total_tokens": 110 - } - }, - "context": { - "app_id": "hooli", - "task_id": "data-analysis", - "thread_id": null, - "created_at": "2025-05-26T03:49:51.313907+00:00" - }, - "events": [ - { - "event_type": "user_action", - "action": "bookmark_clicked", - "metadata": { - "action_context": "data-analysis" - } - }, - { - "event_type": "user_action", - "action": "link_clicked", - "metadata": { - "secondary_action": true - } - } - ] - }, - { - "id": "comp_16c554e19579", - "request": { - "prompt": "Sample prompt for education task", - "messages": [], - "tools": [], - "model": "llama-3.1-8b-instruct" - }, - "response": { - "text": "Response with actionable content for education", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 30, - "entry_tokens": 80, - "total_tokens": 110 - } - }, - "context": { - "app_id": "hooli", - "task_id": "education", - "thread_id": null, - "created_at": "2025-05-16T19:01:51.313915+00:00" - }, - "events": [ - { - "event_type": "user_action", - "action": "share_clicked", - "metadata": { - "action_context": "education" - } - } - ] - }, - { - "id": "comp_11c3174525f1", - "request": { - "prompt": "Sample prompt for education task", - "messages": [], - "tools": [], - "model": "llama-3.1-8b-instruct" - }, - "response": { - "text": "Response with actionable content for education", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 30, - "entry_tokens": 80, - "total_tokens": 110 - } - }, - "context": { - "app_id": "globodyne", - "task_id": "education", - "thread_id": null, - "created_at": "2025-05-03T16:34:51.313922+00:00" - }, - "events": [ - { - "event_type": "user_action", - "action": "copy_clicked", - "metadata": { - "action_context": "education" - } - } - ] - }, - { - "id": "comp_c72433085781", - "request": { - "prompt": "Sample prompt for legal-research task", - "messages": [], - "tools": [], - "model": "llama-3.1-8b-instruct" - }, - "response": { - "text": "Response with actionable content for legal-research", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 30, - "entry_tokens": 80, - "total_tokens": 110 - } - }, - "context": { - "app_id": "hooli", - "task_id": "legal-research", - "thread_id": null, - "created_at": "2025-05-14T07:13:51.313928+00:00" - }, - "events": null - }, - { - "id": "comp_bb2a81e83f95", - "request": { - "prompt": "Sample prompt for education task", - "messages": [], - "tools": [], - "model": "llama-3.1-8b-instruct" - }, - "response": { - "text": "Response with actionable content for education", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 30, - "entry_tokens": 80, - "total_tokens": 110 - } - }, - "context": { - "app_id": "initech", - "task_id": "education", - "thread_id": null, - "created_at": "2025-05-06T00:13:51.313934+00:00" - }, - "events": [ - { - "event_type": "user_action", - "action": "share_clicked", - "metadata": { - "secondary_action": true - } - } - ] - }, - { - "id": "comp_14d42ed41bcd", - "request": { - "prompt": "Sample prompt for data-analysis task", - "messages": [], - "tools": [], - "model": "llama-3.1-8b-instruct" - }, - "response": { - "text": "Response with actionable content for data-analysis", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 30, - "entry_tokens": 80, - "total_tokens": 110 - } - }, - "context": { - "app_id": "initech", - "task_id": "data-analysis", - "thread_id": null, - "created_at": "2025-05-26T06:27:51.313944+00:00" - }, - "events": [ - { - "event_type": "user_action", - "action": "bookmark_clicked", - "metadata": { - "action_context": "data-analysis" - } - }, - { - "event_type": "user_action", - "action": "share_clicked", - "metadata": { - "secondary_action": true - } - } - ] - }, - { - "id": "comp_a012898a02d7", - "request": { - "prompt": "Sample prompt for education task", - "messages": [], - "tools": [], - "model": "llama-3.1-8b-instruct" - }, - "response": { - "text": "Response with actionable content for education", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 30, - "entry_tokens": 80, - "total_tokens": 110 - } - }, - "context": { - "app_id": "acme-corp", - "task_id": "education", - "thread_id": null, - "created_at": "2025-05-27T02:49:51.313950+00:00" - }, - "events": [ - { - "event_type": "user_action", - "action": "export_clicked", - "metadata": { - "action_context": "education" - } - } - ] - }, - { - "id": "comp_5c20f87d68bd", - "request": { - "prompt": "Sample prompt for data-analysis task", - "messages": [], - "tools": [], - "model": "llama-3.1-8b-instruct" - }, - "response": { - "text": "Response with actionable content for data-analysis", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 30, - "entry_tokens": 80, - "total_tokens": 110 - } - }, - "context": { - "app_id": "globodyne", - "task_id": "data-analysis", - "thread_id": null, - "created_at": "2025-05-04T18:45:51.313956+00:00" - }, - "events": [ - { - "event_type": "user_action", - "action": "link_clicked", - "metadata": { - "action_context": "data-analysis" - } - } - ] - }, - { - "id": "comp_090e988b72a8", - "request": { - "prompt": "Sample prompt for education task", - "messages": [], - "tools": [], - "model": "llama-3.1-8b-instruct" - }, - "response": { - "text": "Response with actionable content for education", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 30, - "entry_tokens": 80, - "total_tokens": 110 - } - }, - "context": { - "app_id": "pied-piper", - "task_id": "education", - "thread_id": null, - "created_at": "2025-05-08T13:11:51.313963+00:00" - }, - "events": [ - { - "event_type": "user_action", - "action": "copy_clicked", - "metadata": { - "action_context": "education" - } - }, - { - "event_type": "user_action", - "action": "bookmark_clicked", - "metadata": { - "secondary_action": true - } - } - ] - }, - { - "id": "comp_53850b304b04", - "request": { - "prompt": "Sample prompt for translation task", - "messages": [], - "tools": [], - "model": "llama-3.1-8b-instruct" - }, - "response": { - "text": "Response with actionable content for translation", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 30, - "entry_tokens": 80, - "total_tokens": 110 - } - }, - "context": { - "app_id": "hooli", - "task_id": "translation", - "thread_id": null, - "created_at": "2025-05-04T21:06:51.313970+00:00" - }, - "events": [ - { - "event_type": "user_action", - "action": "bookmark_clicked", - "metadata": { - "action_context": "translation" - } - }, - { - "event_type": "user_action", - "action": "export_clicked", - "metadata": { - "secondary_action": true - } - } - ] - }, - { - "id": "comp_4527ff3f527f", - "request": { - "prompt": "Sample prompt for education task", - "messages": [], - "tools": [], - "model": "llama-3.1-8b-instruct" - }, - "response": { - "text": "Response with actionable content for education", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 30, - "entry_tokens": 80, - "total_tokens": 110 - } - }, - "context": { - "app_id": "pied-piper", - "task_id": "education", - "thread_id": null, - "created_at": "2025-05-06T09:26:51.313976+00:00" - }, - "events": [ - { - "event_type": "user_action", - "action": "copy_clicked", - "metadata": { - "secondary_action": true - } - } - ] - }, - { - "id": "comp_bcf7aff6755a", - "request": { - "prompt": "Sample prompt for legal-research task", - "messages": [], - "tools": [], - "model": "llama-3.1-8b-instruct" - }, - "response": { - "text": "Response with actionable content for legal-research", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 30, - "entry_tokens": 80, - "total_tokens": 110 - } - }, - "context": { - "app_id": "globodyne", - "task_id": "legal-research", - "thread_id": null, - "created_at": "2025-05-19T23:12:51.313984+00:00" - }, - "events": [ - { - "event_type": "user_action", - "action": "link_clicked", - "metadata": { - "action_context": "legal-research" - } - } - ] - }, - { - "id": "comp_1ffb1feabfce", - "request": { - "prompt": "Sample prompt for legal-research task", - "messages": [], - "tools": [], - "model": "llama-3.1-8b-instruct" - }, - "response": { - "text": "Response with actionable content for legal-research", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 30, - "entry_tokens": 80, - "total_tokens": 110 - } - }, - "context": { - "app_id": "hooli", - "task_id": "legal-research", - "thread_id": null, - "created_at": "2025-05-15T08:44:51.313990+00:00" - }, - "events": [ - { - "event_type": "user_action", - "action": "link_clicked", - "metadata": { - "action_context": "legal-research" - } - }, - { - "event_type": "user_action", - "action": "export_clicked", - "metadata": { - "secondary_action": true - } - } - ] - }, - { - "id": "comp_4ced1fcc5313", - "request": { - "prompt": "Sample prompt for translation task", - "messages": [], - "tools": [], - "model": "llama-3.1-8b-instruct" - }, - "response": { - "text": "Response with actionable content for translation", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 30, - "entry_tokens": 80, - "total_tokens": 110 - } - }, - "context": { - "app_id": "acme-corp", - "task_id": "translation", - "thread_id": null, - "created_at": "2025-05-03T06:34:51.313996+00:00" - }, - "events": [ - { - "event_type": "user_action", - "action": "link_clicked", - "metadata": { - "action_context": "translation" - } - }, - { - "event_type": "user_action", - "action": "link_clicked", - "metadata": { - "secondary_action": true - } - } - ] - }, - { - "id": "comp_44ce33badea3", - "request": { - "prompt": "Sample prompt for education task", - "messages": [], - "tools": [], - "model": "llama-3.1-8b-instruct" - }, - "response": { - "text": "Response with actionable content for education", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 30, - "entry_tokens": 80, - "total_tokens": 110 - } - }, - "context": { - "app_id": "globodyne", - "task_id": "education", - "thread_id": null, - "created_at": "2025-05-12T23:33:51.314002+00:00" - }, - "events": [ - { - "event_type": "user_action", - "action": "share_clicked", - "metadata": { - "action_context": "education" - } - } - ] - }, - { - "id": "comp_513645f965fd", - "request": { - "prompt": "Sample prompt for legal-research task", - "messages": [], - "tools": [], - "model": "llama-3.1-8b-instruct" - }, - "response": { - "text": "Response with actionable content for legal-research", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 30, - "entry_tokens": 80, - "total_tokens": 110 - } - }, - "context": { - "app_id": "initech", - "task_id": "legal-research", - "thread_id": null, - "created_at": "2025-05-19T21:58:51.314009+00:00" - }, - "events": [ - { - "event_type": "user_action", - "action": "copy_clicked", - "metadata": { - "action_context": "legal-research" - } - } - ] - }, - { - "id": "comp_0e28cdc687a1", - "request": { - "prompt": "Sample prompt for legal-research task", - "messages": [], - "tools": [], - "model": "llama-3.1-8b-instruct" - }, - "response": { - "text": "Response with actionable content for legal-research", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 30, - "entry_tokens": 80, - "total_tokens": 110 - } - }, - "context": { - "app_id": "hooli", - "task_id": "legal-research", - "thread_id": null, - "created_at": "2025-05-04T10:59:51.314016+00:00" - }, - "events": null - }, - { - "id": "comp_47bf70c41b4e", - "request": { - "prompt": "Sample prompt for code-generation task", - "messages": [], - "tools": [], - "model": "llama-3.1-8b-instruct" - }, - "response": { - "text": "Response with actionable content for code-generation", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 30, - "entry_tokens": 80, - "total_tokens": 110 - } - }, - "context": { - "app_id": "globodyne", - "task_id": "code-generation", - "thread_id": null, - "created_at": "2025-05-20T10:27:51.314022+00:00" - }, - "events": null - } -] \ No newline at end of file diff --git a/services/intake/tests/data/all_entries.json b/services/intake/tests/data/all_entries.json deleted file mode 100644 index 4a8a7dcf00..0000000000 --- a/services/intake/tests/data/all_entries.json +++ /dev/null @@ -1,3303 +0,0 @@ -[ - { - "id": "comp_8287dcd5d647", - "request": { - "prompt": "Explain photosynthesis to a middle school student", - "messages": [], - "tools": [], - "model": "llama-3.1-8b-instruct" - }, - "response": { - "text": "Photosynthesis is like a plant's way of making food using sunlight! Plants take in carbon dioxide from the air, water from their roots, and use sunlight energy to create glucose (sugar) and release oxygen.", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 89, - "entry_tokens": 99, - "total_tokens": 285 - } - }, - "context": { - "app_id": "acme-corp", - "task_id": "education", - "thread_id": null, - "created_at": "2025-05-03T07:18:51.311380+00:00" - } - }, - { - "id": "comp_5f49942666db", - "request": { - "prompt": "Research trademark infringement laws", - "messages": [], - "tools": [], - "model": "llama-3.1-8b-instruct" - }, - "response": { - "text": "Trademark infringement occurs when a party uses a mark that is identical or confusingly similar to a registered trademark. Key factors include: likelihood of confusion, strength of the mark, and similarity of goods/services.", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 64, - "entry_tokens": 55, - "total_tokens": 223 - } - }, - "context": { - "app_id": "acme-corp", - "task_id": "legal-research", - "thread_id": null, - "created_at": "2025-05-16T11:27:51.313114+00:00" - } - }, - { - "id": "comp_6f8389973692", - "request": { - "prompt": "Explain photosynthesis to a middle school student", - "messages": [], - "tools": [], - "model": "llama-3.1-8b-instruct" - }, - "response": { - "text": "Photosynthesis is like a plant's way of making food using sunlight! Plants take in carbon dioxide from the air, water from their roots, and use sunlight energy to create glucose (sugar) and release oxygen.", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 41, - "entry_tokens": 193, - "total_tokens": 156 - } - }, - "context": { - "app_id": "initech", - "task_id": "education", - "thread_id": null, - "created_at": "2025-05-20T10:58:51.313132+00:00" - } - }, - { - "id": "comp_4142018bfa4e", - "request": { - "prompt": "Research trademark infringement laws", - "messages": [], - "tools": [], - "model": "llama-3.1-8b-instruct" - }, - "response": { - "text": "Trademark infringement occurs when a party uses a mark that is identical or confusingly similar to a registered trademark. Key factors include: likelihood of confusion, strength of the mark, and similarity of goods/services.", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 66, - "entry_tokens": 182, - "total_tokens": 123 - } - }, - "context": { - "app_id": "hooli", - "task_id": "legal-research", - "thread_id": null, - "created_at": "2025-05-15T03:50:51.313146+00:00" - } - }, - { - "id": "comp_a6674fc471f2", - "request": { - "prompt": "Explain photosynthesis to a middle school student", - "messages": [], - "tools": [], - "model": "llama-3.1-8b-instruct" - }, - "response": { - "text": "Photosynthesis is like a plant's way of making food using sunlight! Plants take in carbon dioxide from the air, water from their roots, and use sunlight energy to create glucose (sugar) and release oxygen.", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 51, - "entry_tokens": 67, - "total_tokens": 289 - } - }, - "context": { - "app_id": "pied-piper", - "task_id": "education", - "thread_id": null, - "created_at": "2025-05-29T13:29:51.313155+00:00" - } - }, - { - "id": "comp_ad120c5aff99", - "request": { - "prompt": "Write a Python function to validate email addresses", - "messages": [], - "tools": [], - "model": "llama-3.1-8b-instruct" - }, - "response": { - "text": "```python\nimport re\n\ndef validate_email(email):\n pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$'\n return re.match(pattern, email) is not None\n```", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 53, - "entry_tokens": 173, - "total_tokens": 233 - } - }, - "context": { - "app_id": "acme-corp", - "task_id": "code-generation", - "thread_id": null, - "created_at": "2025-05-28T11:58:51.313163+00:00" - } - }, - { - "id": "comp_8ea1df8a755e", - "request": { - "prompt": "Analyze customer churn patterns in our dataset", - "messages": [], - "tools": [], - "model": "llama-3.1-8b-instruct" - }, - "response": { - "text": "Based on the analysis, customer churn is highest in months 3-6 after signup (28% churn rate). Key factors include lack of feature usage (45% correlation) and poor onboarding entry (62% of churned users didn't complete setup).", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 99, - "entry_tokens": 127, - "total_tokens": 185 - } - }, - "context": { - "app_id": "acme-corp", - "task_id": "data-analysis", - "thread_id": null, - "created_at": "2025-05-11T14:15:51.313194+00:00" - } - }, - { - "id": "comp_747c7533695d", - "request": { - "prompt": "Research trademark infringement laws", - "messages": [], - "tools": [], - "model": "llama-3.1-8b-instruct" - }, - "response": { - "text": "Trademark infringement occurs when a party uses a mark that is identical or confusingly similar to a registered trademark. Key factors include: likelihood of confusion, strength of the mark, and similarity of goods/services.", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 54, - "entry_tokens": 155, - "total_tokens": 128 - } - }, - "context": { - "app_id": "hooli", - "task_id": "legal-research", - "thread_id": null, - "created_at": "2025-05-05T08:47:51.313214+00:00" - } - }, - { - "id": "comp_dcf1e2221530", - "request": { - "prompt": "Write a blog post about sustainable living", - "messages": [], - "tools": [], - "model": "llama-3.1-8b-instruct" - }, - "response": { - "text": "# Sustainable Living: Small Changes, Big Impact\n\nIn today's world, making environmentally conscious choices has never been more important. Here are 10 simple ways to reduce your carbon footprint and contribute to a healthier planet...", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 81, - "entry_tokens": 174, - "total_tokens": 162 - } - }, - "context": { - "app_id": "initech", - "task_id": "content-writing", - "thread_id": null, - "created_at": "2025-04-30T02:06:51.313223+00:00" - } - }, - { - "id": "comp_608af96131a8", - "request": { - "prompt": "Translate 'Hello, how are you?' to Spanish", - "messages": [], - "tools": [], - "model": "llama-3.1-8b-instruct" - }, - "response": { - "text": "The translation of 'Hello, how are you?' to Spanish is: 'Hola, \u00bfc\u00f3mo est\u00e1s?' (informal) or 'Hola, \u00bfc\u00f3mo est\u00e1 usted?' (formal).", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 55, - "entry_tokens": 149, - "total_tokens": 104 - } - }, - "context": { - "app_id": "pied-piper", - "task_id": "translation", - "thread_id": null, - "created_at": "2025-05-05T19:30:51.313232+00:00" - } - }, - { - "id": "comp_0a15e509eaed", - "request": { - "prompt": "Research trademark infringement laws", - "messages": [], - "tools": [], - "model": "llama-3.1-8b-instruct" - }, - "response": { - "text": "Trademark infringement occurs when a party uses a mark that is identical or confusingly similar to a registered trademark. Key factors include: likelihood of confusion, strength of the mark, and similarity of goods/services.", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 79, - "entry_tokens": 84, - "total_tokens": 271 - } - }, - "context": { - "app_id": "acme-corp", - "task_id": "legal-research", - "thread_id": null, - "created_at": "2025-05-27T07:13:51.313239+00:00" - } - }, - { - "id": "comp_541d2e65ddb5", - "request": { - "prompt": "Explain the symptoms of diabetes", - "messages": [], - "tools": [], - "model": "llama-3.1-8b-instruct" - }, - "response": { - "text": "Common diabetes symptoms include frequent urination, excessive thirst, unexplained weight loss, fatigue, and blurred vision. If you experience these symptoms, consult a healthcare provider for proper evaluation and testing.", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 88, - "entry_tokens": 89, - "total_tokens": 182 - } - }, - "context": { - "app_id": "acme-corp", - "task_id": "medical-qa", - "thread_id": null, - "created_at": "2025-05-06T02:08:51.313247+00:00" - } - }, - { - "id": "comp_45972e45c9c9", - "request": { - "prompt": "Analyze customer churn patterns in our dataset", - "messages": [], - "tools": [], - "model": "llama-3.1-8b-instruct" - }, - "response": { - "text": "Based on the analysis, customer churn is highest in months 3-6 after signup (28% churn rate). Key factors include lack of feature usage (45% correlation) and poor onboarding entry (62% of churned users didn't complete setup).", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 29, - "entry_tokens": 50, - "total_tokens": 112 - } - }, - "context": { - "app_id": "initech", - "task_id": "data-analysis", - "thread_id": null, - "created_at": "2025-05-29T08:07:51.313254+00:00" - } - }, - { - "id": "comp_49894dc762e2", - "request": { - "prompt": "Create a REST API endpoint for user authentication", - "messages": [], - "tools": [], - "model": "llama-3.1-8b-instruct" - }, - "response": { - "text": "```python\nfrom flask import Flask, request, jsonify\nfrom werkzeug.security import check_password_hash\n\n@app.route('/auth', methods=['POST'])\ndef authenticate():\n data = request.get_json()\n # Authentication logic here\n return jsonify({'token': 'jwt_token_here'})\n```", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 26, - "entry_tokens": 107, - "total_tokens": 112 - } - }, - "context": { - "app_id": "pied-piper", - "task_id": "code-generation", - "thread_id": null, - "created_at": "2025-05-14T19:39:51.313262+00:00" - } - }, - { - "id": "comp_462a340a51d2", - "request": { - "prompt": "How do I reset my password?", - "messages": [], - "tools": [], - "model": "llama-3.1-8b-instruct" - }, - "response": { - "text": "I can help you reset your password. Please visit the account settings page and click 'Forgot Password'. You'll receive a reset link via email within 5 minutes.", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 33, - "entry_tokens": 148, - "total_tokens": 114 - } - }, - "context": { - "app_id": "pied-piper", - "task_id": "customer-support", - "thread_id": null, - "created_at": "2025-05-09T19:15:51.313269+00:00" - } - }, - { - "id": "comp_1746e72f5d42", - "request": { - "prompt": "Explain photosynthesis to a middle school student", - "messages": [], - "tools": [], - "model": "llama-3.1-8b-instruct" - }, - "response": { - "text": "Photosynthesis is like a plant's way of making food using sunlight! Plants take in carbon dioxide from the air, water from their roots, and use sunlight energy to create glucose (sugar) and release oxygen.", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 20, - "entry_tokens": 131, - "total_tokens": 162 - } - }, - "context": { - "app_id": "hooli", - "task_id": "education", - "thread_id": null, - "created_at": "2025-05-23T20:46:51.313276+00:00" - } - }, - { - "id": "comp_2e917c61ece8", - "request": { - "prompt": "How do I reset my password?", - "messages": [], - "tools": [], - "model": "llama-3.1-8b-instruct" - }, - "response": { - "text": "I can help you reset your password. Please visit the account settings page and click 'Forgot Password'. You'll receive a reset link via email within 5 minutes.", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 37, - "entry_tokens": 157, - "total_tokens": 107 - } - }, - "context": { - "app_id": "hooli", - "task_id": "customer-support", - "thread_id": null, - "created_at": "2025-05-01T19:56:51.313283+00:00" - } - }, - { - "id": "comp_ca3b5b4f5ee7", - "request": { - "prompt": "Explain the symptoms of diabetes", - "messages": [], - "tools": [], - "model": "llama-3.1-8b-instruct" - }, - "response": { - "text": "Common diabetes symptoms include frequent urination, excessive thirst, unexplained weight loss, fatigue, and blurred vision. If you experience these symptoms, consult a healthcare provider for proper evaluation and testing.", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 52, - "entry_tokens": 91, - "total_tokens": 179 - } - }, - "context": { - "app_id": "hooli", - "task_id": "medical-qa", - "thread_id": null, - "created_at": "2025-05-06T05:57:51.313293+00:00" - } - }, - { - "id": "comp_febeab1a79b2", - "request": { - "prompt": "Translate 'Hello, how are you?' to Spanish", - "messages": [], - "tools": [], - "model": "llama-3.1-8b-instruct" - }, - "response": { - "text": "The translation of 'Hello, how are you?' to Spanish is: 'Hola, \u00bfc\u00f3mo est\u00e1s?' (informal) or 'Hola, \u00bfc\u00f3mo est\u00e1 usted?' (formal).", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 51, - "entry_tokens": 111, - "total_tokens": 118 - } - }, - "context": { - "app_id": "acme-corp", - "task_id": "translation", - "thread_id": null, - "created_at": "2025-05-21T19:07:51.313300+00:00" - } - }, - { - "id": "comp_32df0cbe8ad8", - "request": { - "prompt": "Write a blog post about sustainable living", - "messages": [], - "tools": [], - "model": "llama-3.1-8b-instruct" - }, - "response": { - "text": "# Sustainable Living: Small Changes, Big Impact\n\nIn today's world, making environmentally conscious choices has never been more important. Here are 10 simple ways to reduce your carbon footprint and contribute to a healthier planet...", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 77, - "entry_tokens": 130, - "total_tokens": 100 - } - }, - "context": { - "app_id": "pied-piper", - "task_id": "content-writing", - "thread_id": null, - "created_at": "2025-05-09T04:50:51.313307+00:00" - } - }, - { - "id": "comp_33fbe54f5f35", - "request": { - "prompt": "Explain the symptoms of diabetes", - "messages": [], - "tools": [], - "model": "llama-3.1-8b-instruct" - }, - "response": { - "text": "Common diabetes symptoms include frequent urination, excessive thirst, unexplained weight loss, fatigue, and blurred vision. If you experience these symptoms, consult a healthcare provider for proper evaluation and testing.", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 69, - "entry_tokens": 173, - "total_tokens": 168 - } - }, - "context": { - "app_id": "pied-piper", - "task_id": "medical-qa", - "thread_id": null, - "created_at": "2025-05-18T16:29:51.313314+00:00" - } - }, - { - "id": "comp_9e58f1cdc20c", - "request": { - "prompt": "Translate 'Hello, how are you?' to Spanish", - "messages": [], - "tools": [], - "model": "llama-3.1-8b-instruct" - }, - "response": { - "text": "The translation of 'Hello, how are you?' to Spanish is: 'Hola, \u00bfc\u00f3mo est\u00e1s?' (informal) or 'Hola, \u00bfc\u00f3mo est\u00e1 usted?' (formal).", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 75, - "entry_tokens": 160, - "total_tokens": 72 - } - }, - "context": { - "app_id": "acme-corp", - "task_id": "translation", - "thread_id": null, - "created_at": "2025-05-19T01:15:51.313321+00:00" - } - }, - { - "id": "comp_7a2d00ef9aaf", - "request": { - "prompt": "Analyze customer churn patterns in our dataset", - "messages": [], - "tools": [], - "model": "llama-3.1-8b-instruct" - }, - "response": { - "text": "Based on the analysis, customer churn is highest in months 3-6 after signup (28% churn rate). Key factors include lack of feature usage (45% correlation) and poor onboarding entry (62% of churned users didn't complete setup).", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 55, - "entry_tokens": 155, - "total_tokens": 126 - } - }, - "context": { - "app_id": "initech", - "task_id": "data-analysis", - "thread_id": null, - "created_at": "2025-05-26T15:02:51.313327+00:00" - } - }, - { - "id": "comp_c1fc401fcb62", - "request": { - "prompt": "Create social media copy for a new product launch", - "messages": [], - "tools": [], - "model": "llama-3.1-8b-instruct" - }, - "response": { - "text": "\ud83d\ude80 Introducing our revolutionary new product! Experience the future of innovation with cutting-edge technology that transforms how you work and play. #Innovation #TechLaunch #GameChanger", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 65, - "entry_tokens": 134, - "total_tokens": 254 - } - }, - "context": { - "app_id": "initech", - "task_id": "content-writing", - "thread_id": null, - "created_at": "2025-05-12T10:36:51.313334+00:00" - } - }, - { - "id": "comp_c30633c76b70", - "request": { - "prompt": "Write a blog post about sustainable living", - "messages": [], - "tools": [], - "model": "llama-3.1-8b-instruct" - }, - "response": { - "text": "# Sustainable Living: Small Changes, Big Impact\n\nIn today's world, making environmentally conscious choices has never been more important. Here are 10 simple ways to reduce your carbon footprint and contribute to a healthier planet...", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 37, - "entry_tokens": 173, - "total_tokens": 273 - } - }, - "context": { - "app_id": "hooli", - "task_id": "content-writing", - "thread_id": null, - "created_at": "2025-05-04T11:53:51.313341+00:00" - } - }, - { - "id": "comp_4dac291613f3", - "request": { - "prompt": "How do I reset my password?", - "messages": [], - "tools": [], - "model": "llama-3.1-8b-instruct" - }, - "response": { - "text": "I can help you reset your password. Please visit the account settings page and click 'Forgot Password'. You'll receive a reset link via email within 5 minutes.", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 29, - "entry_tokens": 116, - "total_tokens": 72 - } - }, - "context": { - "app_id": "hooli", - "task_id": "customer-support", - "thread_id": null, - "created_at": "2025-05-05T12:21:51.313348+00:00" - } - }, - { - "id": "comp_f2ddd005f695", - "request": { - "prompt": "I'm having trouble with the mobile app", - "messages": [], - "tools": [], - "model": "llama-3.1-8b-instruct" - }, - "response": { - "text": "I understand the app issues you're experiencing. Try clearing the cache and data, then restart the app. If the problem persists, please update to the latest version.", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 46, - "entry_tokens": 83, - "total_tokens": 189 - } - }, - "context": { - "app_id": "acme-corp", - "task_id": "customer-support", - "thread_id": null, - "created_at": "2025-05-11T17:31:51.313355+00:00" - } - }, - { - "id": "comp_54b35075cc3f", - "request": { - "prompt": "Create a REST API endpoint for user authentication", - "messages": [], - "tools": [], - "model": "llama-3.1-8b-instruct" - }, - "response": { - "text": "```python\nfrom flask import Flask, request, jsonify\nfrom werkzeug.security import check_password_hash\n\n@app.route('/auth', methods=['POST'])\ndef authenticate():\n data = request.get_json()\n # Authentication logic here\n return jsonify({'token': 'jwt_token_here'})\n```", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 59, - "entry_tokens": 174, - "total_tokens": 260 - } - }, - "context": { - "app_id": "acme-corp", - "task_id": "code-generation", - "thread_id": null, - "created_at": "2025-05-08T07:31:51.313361+00:00" - } - }, - { - "id": "comp_bfe287f6507b", - "request": { - "prompt": "Research trademark infringement laws", - "messages": [], - "tools": [], - "model": "llama-3.1-8b-instruct" - }, - "response": { - "text": "Trademark infringement occurs when a party uses a mark that is identical or confusingly similar to a registered trademark. Key factors include: likelihood of confusion, strength of the mark, and similarity of goods/services.", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 41, - "entry_tokens": 87, - "total_tokens": 179 - } - }, - "context": { - "app_id": "hooli", - "task_id": "legal-research", - "thread_id": null, - "created_at": "2025-05-09T01:52:51.313368+00:00" - } - }, - { - "id": "comp_e2010e6848d3", - "request": { - "prompt": "Research trademark infringement laws", - "messages": [], - "tools": [], - "model": "llama-3.1-8b-instruct" - }, - "response": { - "text": "Trademark infringement occurs when a party uses a mark that is identical or confusingly similar to a registered trademark. Key factors include: likelihood of confusion, strength of the mark, and similarity of goods/services.", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 90, - "entry_tokens": 113, - "total_tokens": 124 - } - }, - "context": { - "app_id": "acme-corp", - "task_id": "legal-research", - "thread_id": null, - "created_at": "2025-05-11T10:26:51.313375+00:00" - } - }, - { - "id": "comp_beb0afc33448", - "request": { - "prompt": "Create a dashboard showing sales performance metrics", - "messages": [], - "tools": [], - "model": "llama-3.1-8b-instruct" - }, - "response": { - "text": "The sales dashboard reveals a 15% increase in Q3 performance with strongest growth in the enterprise segment (34% YoY). Regional breakdown shows North America leading at $2.3M, followed by Europe at $1.8M.", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 78, - "entry_tokens": 50, - "total_tokens": 261 - } - }, - "context": { - "app_id": "initech", - "task_id": "data-analysis", - "thread_id": null, - "created_at": "2025-05-13T04:58:51.313382+00:00" - } - }, - { - "id": "comp_bf854aee11a4", - "request": { - "prompt": "Analyze customer churn patterns in our dataset", - "messages": [], - "tools": [], - "model": "llama-3.1-8b-instruct" - }, - "response": { - "text": "Based on the analysis, customer churn is highest in months 3-6 after signup (28% churn rate). Key factors include lack of feature usage (45% correlation) and poor onboarding entry (62% of churned users didn't complete setup).", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 35, - "entry_tokens": 149, - "total_tokens": 291 - } - }, - "context": { - "app_id": "hooli", - "task_id": "data-analysis", - "thread_id": null, - "created_at": "2025-05-01T08:28:51.313390+00:00" - } - }, - { - "id": "comp_06026da097b4", - "request": { - "prompt": "Translate 'Hello, how are you?' to Spanish", - "messages": [], - "tools": [], - "model": "llama-3.1-8b-instruct" - }, - "response": { - "text": "The translation of 'Hello, how are you?' to Spanish is: 'Hola, \u00bfc\u00f3mo est\u00e1s?' (informal) or 'Hola, \u00bfc\u00f3mo est\u00e1 usted?' (formal).", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 66, - "entry_tokens": 125, - "total_tokens": 126 - } - }, - "context": { - "app_id": "hooli", - "task_id": "translation", - "thread_id": null, - "created_at": "2025-05-18T23:27:51.313398+00:00" - } - }, - { - "id": "comp_ba30a1bcb2fa", - "request": { - "prompt": "How do I reset my password?", - "messages": [], - "tools": [], - "model": "llama-3.1-8b-instruct" - }, - "response": { - "text": "I can help you reset your password. Please visit the account settings page and click 'Forgot Password'. You'll receive a reset link via email within 5 minutes.", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 66, - "entry_tokens": 53, - "total_tokens": 261 - } - }, - "context": { - "app_id": "hooli", - "task_id": "customer-support", - "thread_id": null, - "created_at": "2025-05-05T17:44:51.313404+00:00" - } - }, - { - "id": "comp_c9cec8b713fa", - "request": { - "prompt": "Write a Python function to validate email addresses", - "messages": [], - "tools": [], - "model": "llama-3.1-8b-instruct" - }, - "response": { - "text": "```python\nimport re\n\ndef validate_email(email):\n pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$'\n return re.match(pattern, email) is not None\n```", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 43, - "entry_tokens": 180, - "total_tokens": 255 - } - }, - "context": { - "app_id": "hooli", - "task_id": "code-generation", - "thread_id": null, - "created_at": "2025-05-12T16:12:51.313411+00:00" - } - }, - { - "id": "comp_9b77552d4a64", - "request": { - "prompt": "Explain photosynthesis to a middle school student", - "messages": [], - "tools": [], - "model": "llama-3.1-8b-instruct" - }, - "response": { - "text": "Photosynthesis is like a plant's way of making food using sunlight! Plants take in carbon dioxide from the air, water from their roots, and use sunlight energy to create glucose (sugar) and release oxygen.", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 49, - "entry_tokens": 71, - "total_tokens": 271 - } - }, - "context": { - "app_id": "acme-corp", - "task_id": "education", - "thread_id": null, - "created_at": "2025-05-20T16:08:51.313418+00:00" - } - }, - { - "id": "comp_7eec2e78d98f", - "request": { - "prompt": "Research trademark infringement laws", - "messages": [], - "tools": [], - "model": "llama-3.1-8b-instruct" - }, - "response": { - "text": "Trademark infringement occurs when a party uses a mark that is identical or confusingly similar to a registered trademark. Key factors include: likelihood of confusion, strength of the mark, and similarity of goods/services.", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 99, - "entry_tokens": 114, - "total_tokens": 220 - } - }, - "context": { - "app_id": "acme-corp", - "task_id": "legal-research", - "thread_id": null, - "created_at": "2025-05-13T20:22:51.313425+00:00" - } - }, - { - "id": "comp_a6fcb3a27b23", - "request": { - "prompt": "Create a REST API endpoint for user authentication", - "messages": [], - "tools": [], - "model": "llama-3.1-8b-instruct" - }, - "response": { - "text": "```python\nfrom flask import Flask, request, jsonify\nfrom werkzeug.security import check_password_hash\n\n@app.route('/auth', methods=['POST'])\ndef authenticate():\n data = request.get_json()\n # Authentication logic here\n return jsonify({'token': 'jwt_token_here'})\n```", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 27, - "entry_tokens": 114, - "total_tokens": 254 - } - }, - "context": { - "app_id": "initech", - "task_id": "code-generation", - "thread_id": null, - "created_at": "2025-05-14T04:06:51.313432+00:00" - } - }, - { - "id": "comp_4e8d20dc09cd", - "request": { - "prompt": "Write a blog post about sustainable living", - "messages": [], - "tools": [], - "model": "llama-3.1-8b-instruct" - }, - "response": { - "text": "# Sustainable Living: Small Changes, Big Impact\n\nIn today's world, making environmentally conscious choices has never been more important. Here are 10 simple ways to reduce your carbon footprint and contribute to a healthier planet...", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 56, - "entry_tokens": 57, - "total_tokens": 279 - } - }, - "context": { - "app_id": "pied-piper", - "task_id": "content-writing", - "thread_id": null, - "created_at": "2025-05-20T11:43:51.313439+00:00" - } - }, - { - "id": "comp_2ad77baef825", - "request": { - "prompt": "Analyze customer churn patterns in our dataset", - "messages": [], - "tools": [], - "model": "llama-3.1-8b-instruct" - }, - "response": { - "text": "Based on the analysis, customer churn is highest in months 3-6 after signup (28% churn rate). Key factors include lack of feature usage (45% correlation) and poor onboarding entry (62% of churned users didn't complete setup).", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 64, - "entry_tokens": 173, - "total_tokens": 236 - } - }, - "context": { - "app_id": "acme-corp", - "task_id": "data-analysis", - "thread_id": null, - "created_at": "2025-05-11T13:52:51.313446+00:00" - } - }, - { - "id": "comp_8424576dede5", - "request": { - "prompt": "I'm having trouble logging into my account", - "messages": [ - { - "role": "user", - "content": "I'm having trouble logging into my account" - } - ], - "tools": [], - "model": "llama-3.1-70b-instruct" - }, - "response": { - "text": "I can help you with that login issue. Can you tell me what error message you're seeing?", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 200, - "entry_tokens": 300, - "total_tokens": 500 - } - }, - "context": { - "app_id": "acme-corp", - "task_id": "customer-support", - "thread_id": "thread_63dfd999", - "created_at": "2025-05-02T17:20:51.313530+00:00" - }, - "events": [ - { - "event_type": "user_feedback", - "thumb": "up", - "metadata": { - "helpful": true - } - } - ] - }, - { - "id": "comp_f2872cfd7595", - "request": { - "prompt": "It says 'invalid credentials' but I'm sure my password is correct", - "messages": [ - { - "role": "user", - "content": "I'm having trouble logging into my account" - }, - { - "role": "assistant", - "content": "I can help you with that login issue. Can you tell me what error message you're seeing?" - }, - { - "role": "user", - "content": "It says 'invalid credentials' but I'm sure my password is correct" - } - ], - "tools": [], - "model": "llama-3.1-70b-instruct" - }, - "response": { - "text": "That error usually means either the email or password is incorrect. Let's try resetting your password to be sure. I'll send you a reset link.", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 250, - "entry_tokens": 400, - "total_tokens": 650 - } - }, - "context": { - "app_id": "acme-corp", - "task_id": "customer-support", - "thread_id": "thread_63dfd999", - "created_at": "2025-05-01T23:26:51.313538+00:00" - }, - "events": null - }, - { - "id": "comp_46f1f65c2c58", - "request": { - "prompt": "I got the email and reset it, but it's still not working", - "messages": [ - { - "role": "user", - "content": "I'm having trouble logging into my account" - }, - { - "role": "assistant", - "content": "I can help you with that login issue. Can you tell me what error message you're seeing?" - }, - { - "role": "user", - "content": "It says 'invalid credentials' but I'm sure my password is correct" - }, - { - "role": "assistant", - "content": "That error usually means either the email or password is incorrect. Let's try resetting your password to be sure. I'll send you a reset link." - }, - { - "role": "user", - "content": "I got the email and reset it, but it's still not working" - } - ], - "tools": [], - "model": "llama-3.1-70b-instruct" - }, - "response": { - "text": "I see the issue now - your account was temporarily locked due to multiple failed attempts. I've unlocked it for you. Please try logging in again with your new password.", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 300, - "entry_tokens": 500, - "total_tokens": 800 - } - }, - "context": { - "app_id": "acme-corp", - "task_id": "customer-support", - "thread_id": "thread_63dfd999", - "created_at": "2025-05-19T21:55:51.313545+00:00" - }, - "events": [ - { - "event_type": "user_feedback", - "rating": 5, - "metadata": { - "session_helpful": true - } - } - ] - }, - { - "id": "comp_2d09cda8194f", - "request": { - "prompt": "I need to create a user registration API endpoint", - "messages": [ - { - "role": "user", - "content": "I need to create a user registration API endpoint" - } - ], - "tools": [], - "model": "llama-3.1-70b-instruct" - }, - "response": { - "text": "I'll help you create a user registration endpoint. Here's a basic structure:\n\n```python\nfrom flask import Flask, request, jsonify\nfrom werkzeug.security import generate_password_hash\n\n@app.route('/register', methods=['POST'])\ndef register():\n data = request.get_json()\n email = data.get('email')\n password = data.get('password')\n \n # Hash password and save user\n hashed_password = generate_password_hash(password)\n # Save to database...\n \n return jsonify({'message': 'User created successfully'}), 201\n```", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 200, - "entry_tokens": 300, - "total_tokens": 500 - } - }, - "context": { - "app_id": "hooli", - "task_id": "code-generation", - "thread_id": "thread_378fc4d2", - "created_at": "2025-04-30T19:09:51.313561+00:00" - }, - "events": [ - { - "event_type": "user_feedback", - "thumb": "up", - "metadata": { - "helpful": true - } - } - ] - }, - { - "id": "comp_5f2741273fed", - "request": { - "prompt": "This looks good, but I also need email validation and duplicate checking", - "messages": [ - { - "role": "user", - "content": "I need to create a user registration API endpoint" - }, - { - "role": "assistant", - "content": "I'll help you create a user registration endpoint. Here's a basic structure:\n\n```python\nfrom flask import Flask, request, jsonify\nfrom werkzeug.security import generate_password_hash\n\n@app.route('/register', methods=['POST'])\ndef register():\n data = request.get_json()\n email = data.get('email')\n password = data.get('password')\n \n # Hash password and save user\n hashed_password = generate_password_hash(password)\n # Save to database...\n \n return jsonify({'message': 'User created successfully'}), 201\n```" - }, - { - "role": "user", - "content": "This looks good, but I also need email validation and duplicate checking" - } - ], - "tools": [], - "model": "llama-3.1-70b-instruct" - }, - "response": { - "text": "Great point! Let me enhance that with proper validation:\n\n```python\nimport re\nfrom sqlalchemy.exc import IntegrityError\n\ndef validate_email(email):\n pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$'\n return re.match(pattern, email) is not None\n\n@app.route('/register', methods=['POST'])\ndef register():\n data = request.get_json()\n email = data.get('email')\n password = data.get('password')\n \n # Validate input\n if not email or not password:\n return jsonify({'error': 'Email and password required'}), 400\n \n if not validate_email(email):\n return jsonify({'error': 'Invalid email format'}), 400\n \n # Check for existing user\n existing_user = User.query.filter_by(email=email).first()\n if existing_user:\n return jsonify({'error': 'User already exists'}), 409\n \n # Create user...\n```", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 250, - "entry_tokens": 400, - "total_tokens": 650 - } - }, - "context": { - "app_id": "hooli", - "task_id": "code-generation", - "thread_id": "thread_378fc4d2", - "created_at": "2025-05-12T19:17:51.313569+00:00" - }, - "events": [ - { - "event_type": "user_action", - "action": "copy_clicked", - "metadata": { - "code_block": true - } - } - ] - }, - { - "id": "comp_675456ce400a", - "request": { - "prompt": "Perfect! Can you also add password strength validation?", - "messages": [ - { - "role": "user", - "content": "I need to create a user registration API endpoint" - }, - { - "role": "assistant", - "content": "I'll help you create a user registration endpoint. Here's a basic structure:\n\n```python\nfrom flask import Flask, request, jsonify\nfrom werkzeug.security import generate_password_hash\n\n@app.route('/register', methods=['POST'])\ndef register():\n data = request.get_json()\n email = data.get('email')\n password = data.get('password')\n \n # Hash password and save user\n hashed_password = generate_password_hash(password)\n # Save to database...\n \n return jsonify({'message': 'User created successfully'}), 201\n```" - }, - { - "role": "user", - "content": "This looks good, but I also need email validation and duplicate checking" - }, - { - "role": "assistant", - "content": "Great point! Let me enhance that with proper validation:\n\n```python\nimport re\nfrom sqlalchemy.exc import IntegrityError\n\ndef validate_email(email):\n pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$'\n return re.match(pattern, email) is not None\n\n@app.route('/register', methods=['POST'])\ndef register():\n data = request.get_json()\n email = data.get('email')\n password = data.get('password')\n \n # Validate input\n if not email or not password:\n return jsonify({'error': 'Email and password required'}), 400\n \n if not validate_email(email):\n return jsonify({'error': 'Invalid email format'}), 400\n \n # Check for existing user\n existing_user = User.query.filter_by(email=email).first()\n if existing_user:\n return jsonify({'error': 'User already exists'}), 409\n \n # Create user...\n```" - }, - { - "role": "user", - "content": "Perfect! Can you also add password strength validation?" - } - ], - "tools": [], - "model": "llama-3.1-70b-instruct" - }, - "response": { - "text": "Absolutely! Here's a password strength validator:\n\n```python\ndef validate_password_strength(password):\n if len(password) < 8:\n return False, 'Password must be at least 8 characters long'\n if not re.search(r'[A-Z]', password):\n return False, 'Password must contain at least one uppercase letter'\n if not re.search(r'[a-z]', password):\n return False, 'Password must contain at least one lowercase letter'\n if not re.search(r'\\d', password):\n return False, 'Password must contain at least one number'\n if not re.search(r'[!@#$%^&*(),.?\":{}|<>]', password):\n return False, 'Password must contain at least one special character'\n return True, 'Password is strong'\n\n# Add this validation before creating the user:\nis_strong, message = validate_password_strength(password)\nif not is_strong:\n return jsonify({'error': message}), 400\n```", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 300, - "entry_tokens": 500, - "total_tokens": 800 - } - }, - "context": { - "app_id": "hooli", - "task_id": "code-generation", - "thread_id": "thread_378fc4d2", - "created_at": "2025-05-03T19:03:51.313576+00:00" - }, - "events": [ - { - "event_type": "user_feedback", - "rating": 5, - "metadata": { - "session_helpful": true - } - } - ] - }, - { - "id": "comp_9a7a7d48e41f", - "request": { - "prompt": "Can you help me analyze our sales data trends?", - "messages": [ - { - "role": "user", - "content": "Can you help me analyze our sales data trends?" - } - ], - "tools": [], - "model": "llama-3.1-70b-instruct" - }, - "response": { - "text": "I'd be happy to help analyze your sales data! What specific aspects would you like to focus on? For example: monthly trends, regional performance, product categories, or seasonal patterns?", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 200, - "entry_tokens": 300, - "total_tokens": 500 - } - }, - "context": { - "app_id": "globodyne", - "task_id": "data-analysis", - "thread_id": "thread_53dd1d24", - "created_at": "2025-05-15T11:37:51.313587+00:00" - }, - "events": [ - { - "event_type": "user_feedback", - "thumb": "up", - "metadata": { - "helpful": true - } - } - ] - }, - { - "id": "comp_edcbaa6e0138", - "request": { - "prompt": "I want to understand why our Q2 sales dropped compared to Q1", - "messages": [ - { - "role": "user", - "content": "Can you help me analyze our sales data trends?" - }, - { - "role": "assistant", - "content": "I'd be happy to help analyze your sales data! What specific aspects would you like to focus on? For example: monthly trends, regional performance, product categories, or seasonal patterns?" - }, - { - "role": "user", - "content": "I want to understand why our Q2 sales dropped compared to Q1" - } - ], - "tools": [], - "model": "llama-3.1-70b-instruct" - }, - "response": { - "text": "Let's investigate the Q2 sales decline. I'll need to examine several factors:\n\n1. **Monthly breakdown**: Compare April, May, June individually\n2. **Product performance**: Which products/categories were most affected\n3. **Regional analysis**: Geographic impact patterns\n4. **External factors**: Market conditions, competition, seasonality\n\nCan you share your sales data or tell me what data sources you have available?", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 250, - "entry_tokens": 400, - "total_tokens": 650 - } - }, - "context": { - "app_id": "globodyne", - "task_id": "data-analysis", - "thread_id": "thread_53dd1d24", - "created_at": "2025-05-27T22:07:51.313595+00:00" - }, - "events": null - }, - { - "id": "comp_93b3bbbb82b0", - "request": { - "prompt": "We have sales by month, product category, and region. Q1 was $2.4M, Q2 was $1.8M", - "messages": [ - { - "role": "user", - "content": "Can you help me analyze our sales data trends?" - }, - { - "role": "assistant", - "content": "I'd be happy to help analyze your sales data! What specific aspects would you like to focus on? For example: monthly trends, regional performance, product categories, or seasonal patterns?" - }, - { - "role": "user", - "content": "I want to understand why our Q2 sales dropped compared to Q1" - }, - { - "role": "assistant", - "content": "Let's investigate the Q2 sales decline. I'll need to examine several factors:\n\n1. **Monthly breakdown**: Compare April, May, June individually\n2. **Product performance**: Which products/categories were most affected\n3. **Regional analysis**: Geographic impact patterns\n4. **External factors**: Market conditions, competition, seasonality\n\nCan you share your sales data or tell me what data sources you have available?" - }, - { - "role": "user", - "content": "We have sales by month, product category, and region. Q1 was $2.4M, Q2 was $1.8M" - } - ], - "tools": [], - "model": "llama-3.1-70b-instruct" - }, - "response": { - "text": "That's a significant 25% decline from Q1 to Q2 ($600k drop). Here's my analysis approach:\n\n**Key Questions:**\n- Which product categories drove the decline?\n- Was the drop consistent across all regions?\n- Did it happen gradually or sudden in a specific month?\n\n**Recommended Analysis:**\n1. Break down the $600k loss by category and region\n2. Compare month-over-month trends within Q2\n3. Look for external factors (market conditions, competitor launches)\n4. Check if this aligns with historical seasonal patterns\n\nThis will help identify if it's a temporary market condition or a deeper business issue requiring strategic changes.", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 300, - "entry_tokens": 500, - "total_tokens": 800 - } - }, - "context": { - "app_id": "globodyne", - "task_id": "data-analysis", - "thread_id": "thread_53dd1d24", - "created_at": "2025-05-16T14:32:51.313603+00:00" - }, - "events": [ - { - "event_type": "user_feedback", - "rating": 5, - "metadata": { - "session_helpful": true - } - } - ] - }, - { - "id": "comp_48c10d4ae39d", - "request": { - "prompt": "I need help writing a press release for our new product launch", - "messages": [ - { - "role": "user", - "content": "I need help writing a press release for our new product launch" - } - ], - "tools": [], - "model": "llama-3.1-70b-instruct" - }, - "response": { - "text": "I'd be happy to help you write a compelling press release! To create the most effective announcement, I'll need some key details:\n\n1. **Product name and category**\n2. **Key features and benefits**\n3. **Target audience**\n4. **Launch date and availability**\n5. **Company background**\n6. **Any notable partnerships or achievements**\n\nWhat's the product you're launching?", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 200, - "entry_tokens": 300, - "total_tokens": 500 - } - }, - "context": { - "app_id": "initech", - "task_id": "content-writing", - "thread_id": "thread_afbb9425", - "created_at": "2025-05-10T12:16:51.313614+00:00" - }, - "events": [ - { - "event_type": "user_feedback", - "thumb": "up", - "metadata": { - "helpful": true - } - } - ] - }, - { - "id": "comp_bf426c48d171", - "request": { - "prompt": "It's a new project management software called 'TaskFlow' that uses AI to optimize team workflows", - "messages": [ - { - "role": "user", - "content": "I need help writing a press release for our new product launch" - }, - { - "role": "assistant", - "content": "I'd be happy to help you write a compelling press release! To create the most effective announcement, I'll need some key details:\n\n1. **Product name and category**\n2. **Key features and benefits**\n3. **Target audience**\n4. **Launch date and availability**\n5. **Company background**\n6. **Any notable partnerships or achievements**\n\nWhat's the product you're launching?" - }, - { - "role": "user", - "content": "It's a new project management software called 'TaskFlow' that uses AI to optimize team workflows" - } - ], - "tools": [], - "model": "llama-3.1-70b-instruct" - }, - "response": { - "text": "Excellent! TaskFlow sounds innovative. Here's a draft press release structure:\n\n**FOR IMMEDIATE RELEASE**\n\n**Initech Launches TaskFlow: AI-Powered Project Management Platform Revolutionizes Team Productivity**\n\n*Revolutionary software leverages artificial intelligence to optimize workflows and boost team efficiency*\n\n[City, Date] \u2013 Initech today announced the launch of TaskFlow, a cutting-edge project management platform that harnesses the power of artificial intelligence to optimize team workflows and dramatically improve productivity.\n\nWould you like me to continue with specific features and benefits? What are TaskFlow's main AI capabilities?", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 250, - "entry_tokens": 400, - "total_tokens": 650 - } - }, - "context": { - "app_id": "initech", - "task_id": "content-writing", - "thread_id": "thread_afbb9425", - "created_at": "2025-05-07T02:50:51.313620+00:00" - }, - "events": null - }, - { - "id": "comp_7a5466510822", - "request": { - "prompt": "The AI can predict project bottlenecks, automatically assign tasks based on team skills, and suggest optimal deadlines", - "messages": [ - { - "role": "user", - "content": "I need help writing a press release for our new product launch" - }, - { - "role": "assistant", - "content": "I'd be happy to help you write a compelling press release! To create the most effective announcement, I'll need some key details:\n\n1. **Product name and category**\n2. **Key features and benefits**\n3. **Target audience**\n4. **Launch date and availability**\n5. **Company background**\n6. **Any notable partnerships or achievements**\n\nWhat's the product you're launching?" - }, - { - "role": "user", - "content": "It's a new project management software called 'TaskFlow' that uses AI to optimize team workflows" - }, - { - "role": "assistant", - "content": "Excellent! TaskFlow sounds innovative. Here's a draft press release structure:\n\n**FOR IMMEDIATE RELEASE**\n\n**Initech Launches TaskFlow: AI-Powered Project Management Platform Revolutionizes Team Productivity**\n\n*Revolutionary software leverages artificial intelligence to optimize workflows and boost team efficiency*\n\n[City, Date] \u2013 Initech today announced the launch of TaskFlow, a cutting-edge project management platform that harnesses the power of artificial intelligence to optimize team workflows and dramatically improve productivity.\n\nWould you like me to continue with specific features and benefits? What are TaskFlow's main AI capabilities?" - }, - { - "role": "user", - "content": "The AI can predict project bottlenecks, automatically assign tasks based on team skills, and suggest optimal deadlines" - } - ], - "tools": [], - "model": "llama-3.1-70b-instruct" - }, - "response": { - "text": "Perfect! Here's the complete press release:\n\n**FOR IMMEDIATE RELEASE**\n\n**Initech Launches TaskFlow: AI-Powered Project Management Platform Revolutionizes Team Productivity**\n\n*Revolutionary software leverages artificial intelligence to optimize workflows and boost team efficiency by up to 40%*\n\n[City, Date] \u2013 Initech today announced the launch of TaskFlow, a cutting-edge project management platform that harnesses artificial intelligence to transform how teams collaborate and execute projects.\n\n**Key Features:**\n\u2022 **Predictive Analytics**: AI algorithms identify potential bottlenecks before they impact deadlines\n\u2022 **Smart Task Assignment**: Automatically matches tasks to team members based on skills, availability, and task\n\u2022 **Optimized Scheduling**: Suggests realistic deadlines using historical data and team capacity\n\n\"TaskFlow represents the future of project management,\" said [Spokesperson Name], [Title] at Initech. \"Our AI doesn't just track progress \u2013 it actively optimizes your team's workflow to ensure projects succeed.\"\n\nTaskFlow is available immediately with pricing starting at $X per user per month. For more information, visit [website] or contact [email].\n\n**About Initech**\n[Company description]\n\n###", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 300, - "entry_tokens": 500, - "total_tokens": 800 - } - }, - "context": { - "app_id": "initech", - "task_id": "content-writing", - "thread_id": "thread_afbb9425", - "created_at": "2025-05-29T12:19:51.313630+00:00" - }, - "events": [ - { - "event_type": "user_feedback", - "rating": 5, - "metadata": { - "session_helpful": true - } - } - ] - }, - { - "id": "comp_fcae6f83cd77", - "request": { - "prompt": "Explain quantum computing in simple terms", - "messages": [ - { - "role": "user", - "content": "Explain quantum computing in simple terms" - } - ], - "tools": [], - "model": "llama-3.1-8b-instruct" - }, - "response": { - "text": "Quantum computing is like having a super-powered computer that can explore many solutions to a problem simultaneously, rather than checking them one by one like regular computers. Think of it as the difference between reading every book in a library one at a time versus somehow reading them all at once!", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 25, - "entry_tokens": 75, - "total_tokens": 100 - } - }, - "context": { - "app_id": "initech", - "task_id": "translation", - "thread_id": null, - "created_at": "2025-05-14T01:45:51.313640+00:00" - }, - "events": [ - { - "event_type": "user_feedback", - "thumb": "up", - "metadata": { - "clear_explanation": true - } - }, - { - "event_type": "user_feedback", - "rating": 4, - "metadata": { - "almost_perfect": true - } - }, - { - "event_type": "user_feedback", - "opinion": "Great analogy! Really helped me understand the concept.", - "metadata": { - "helpful_analogy": true - } - } - ] - }, - { - "id": "comp_05817698b6c5", - "request": { - "prompt": "Write a Python function to reverse a string", - "messages": [ - { - "role": "user", - "content": "Write a Python function to reverse a string" - } - ], - "tools": [], - "model": "llama-3.1-8b-instruct" - }, - "response": { - "text": "```python\ndef reverse_string(s):\n return s[::-1]\n```", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 25, - "entry_tokens": 75, - "total_tokens": 100 - } - }, - "context": { - "app_id": "hooli", - "task_id": "translation", - "thread_id": null, - "created_at": "2025-05-19T11:01:51.313647+00:00" - }, - "events": [ - { - "event_type": "user_feedback", - "thumb": "down", - "metadata": { - "too_simple": true, - "needs_explanation": true - } - }, - { - "event_type": "user_feedback", - "opinion": "This works but could you explain how slicing works?", - "metadata": { - "wants_detail": true - } - } - ] - }, - { - "id": "comp_7040ffaeccc5", - "request": { - "prompt": "What are the benefits of renewable energy?", - "messages": [ - { - "role": "user", - "content": "What are the benefits of renewable energy?" - } - ], - "tools": [], - "model": "llama-3.1-8b-instruct" - }, - "response": { - "text": "Renewable energy offers numerous benefits: 1) Environmental - reduces greenhouse gas emissions and air pollution, 2) Economic - creates jobs and reduces energy costs long-term, 3) Energy security - reduces dependence on fossil fuel imports, 4) Sustainability - inexhaustible energy sources, 5) Health - cleaner air leads to better public health outcomes.", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 25, - "entry_tokens": 75, - "total_tokens": 100 - } - }, - "context": { - "app_id": "initech", - "task_id": "legal-research", - "thread_id": null, - "created_at": "2025-05-04T12:46:51.313652+00:00" - }, - "events": [ - { - "event_type": "user_feedback", - "thumb": "up", - "metadata": { - "comprehensive": true - } - }, - { - "event_type": "user_feedback", - "rating": 5, - "metadata": { - "excellent_summary": true - } - }, - { - "event_type": "user_action", - "action": "share_clicked", - "metadata": { - "share_platform": "twitter" - } - } - ] - }, - { - "id": "comp_ddaee6c0fa71", - "request": { - "prompt": "Sample question about code-generation", - "messages": [], - "tools": [], - "model": "llama-3.1-8b-instruct" - }, - "response": { - "text": "Sample response for code-generation with feedback potential", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 20, - "entry_tokens": 50, - "total_tokens": 70 - } - }, - "context": { - "app_id": "globodyne", - "task_id": "code-generation", - "thread_id": null, - "created_at": "2025-05-12T14:22:51.313661+00:00" - }, - "events": [ - { - "event_type": "user_feedback", - "thumb": "up", - "metadata": { - "random_feedback": true - } - } - ] - }, - { - "id": "comp_c0a1f7664b2c", - "request": { - "prompt": "Sample question about content-writing", - "messages": [], - "tools": [], - "model": "llama-3.1-8b-instruct" - }, - "response": { - "text": "Sample response for content-writing with feedback potential", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 20, - "entry_tokens": 50, - "total_tokens": 70 - } - }, - "context": { - "app_id": "initech", - "task_id": "content-writing", - "thread_id": null, - "created_at": "2025-05-24T00:13:51.313667+00:00" - }, - "events": null - }, - { - "id": "comp_d88cf0482d97", - "request": { - "prompt": "Sample question about content-writing", - "messages": [], - "tools": [], - "model": "llama-3.1-8b-instruct" - }, - "response": { - "text": "Sample response for content-writing with feedback potential", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 20, - "entry_tokens": 50, - "total_tokens": 70 - } - }, - "context": { - "app_id": "initech", - "task_id": "content-writing", - "thread_id": null, - "created_at": "2025-05-08T14:39:51.313673+00:00" - }, - "events": null - }, - { - "id": "comp_f9e5b91c78db", - "request": { - "prompt": "Sample question about customer-support", - "messages": [], - "tools": [], - "model": "llama-3.1-8b-instruct" - }, - "response": { - "text": "Sample response for customer-support with feedback potential", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 20, - "entry_tokens": 50, - "total_tokens": 70 - } - }, - "context": { - "app_id": "hooli", - "task_id": "customer-support", - "thread_id": null, - "created_at": "2025-05-21T02:15:51.313679+00:00" - }, - "events": [ - { - "event_type": "user_feedback", - "thumb": "up", - "metadata": { - "random_feedback": true - } - } - ] - }, - { - "id": "comp_723337184968", - "request": { - "prompt": "Sample question about medical-qa", - "messages": [], - "tools": [], - "model": "llama-3.1-8b-instruct" - }, - "response": { - "text": "Sample response for medical-qa with feedback potential", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 20, - "entry_tokens": 50, - "total_tokens": 70 - } - }, - "context": { - "app_id": "acme-corp", - "task_id": "medical-qa", - "thread_id": null, - "created_at": "2025-05-18T13:25:51.313689+00:00" - }, - "events": [ - { - "event_type": "user_feedback", - "thumb": "up", - "metadata": { - "random_feedback": true - } - }, - { - "event_type": "user_feedback", - "rating": 3, - "metadata": { - "rating_context": "overall_quality" - } - } - ] - }, - { - "id": "comp_bf25d684fa56", - "request": { - "prompt": "Sample question about medical-qa", - "messages": [], - "tools": [], - "model": "llama-3.1-8b-instruct" - }, - "response": { - "text": "Sample response for medical-qa with feedback potential", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 20, - "entry_tokens": 50, - "total_tokens": 70 - } - }, - "context": { - "app_id": "pied-piper", - "task_id": "medical-qa", - "thread_id": null, - "created_at": "2025-05-05T21:41:51.313729+00:00" - }, - "events": [ - { - "event_type": "user_feedback", - "thumb": "up", - "metadata": { - "random_feedback": true - } - }, - { - "event_type": "user_feedback", - "rating": 2, - "metadata": { - "rating_context": "overall_quality" - } - } - ] - }, - { - "id": "comp_ae495a00be3e", - "request": { - "prompt": "Sample question about translation", - "messages": [], - "tools": [], - "model": "llama-3.1-8b-instruct" - }, - "response": { - "text": "Sample response for translation with feedback potential", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 20, - "entry_tokens": 50, - "total_tokens": 70 - } - }, - "context": { - "app_id": "globodyne", - "task_id": "translation", - "thread_id": null, - "created_at": "2025-05-19T07:04:51.313749+00:00" - }, - "events": [ - { - "event_type": "user_feedback", - "thumb": "up", - "metadata": { - "random_feedback": true - } - }, - { - "event_type": "user_feedback", - "opinion": "Could be more detailed", - "metadata": { - "feedback_type": "textual" - } - } - ] - }, - { - "id": "comp_bb109e3761f4", - "request": { - "prompt": "Sample question about content-writing", - "messages": [], - "tools": [], - "model": "llama-3.1-8b-instruct" - }, - "response": { - "text": "Sample response for content-writing with feedback potential", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 20, - "entry_tokens": 50, - "total_tokens": 70 - } - }, - "context": { - "app_id": "hooli", - "task_id": "content-writing", - "thread_id": null, - "created_at": "2025-05-28T12:54:51.313761+00:00" - }, - "events": [ - { - "event_type": "user_feedback", - "rating": 5, - "metadata": { - "rating_context": "overall_quality" - } - }, - { - "event_type": "user_feedback", - "opinion": "Could use more context", - "metadata": { - "feedback_type": "textual" - } - } - ] - }, - { - "id": "comp_1c405312e955", - "request": { - "prompt": "Sample question about medical-qa", - "messages": [], - "tools": [], - "model": "llama-3.1-8b-instruct" - }, - "response": { - "text": "Sample response for medical-qa with feedback potential", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 20, - "entry_tokens": 50, - "total_tokens": 70 - } - }, - "context": { - "app_id": "hooli", - "task_id": "medical-qa", - "thread_id": null, - "created_at": "2025-05-24T02:48:51.313770+00:00" - }, - "events": [ - { - "event_type": "user_feedback", - "thumb": "up", - "metadata": { - "random_feedback": true - } - }, - { - "event_type": "user_feedback", - "rating": 3, - "metadata": { - "rating_context": "overall_quality" - } - }, - { - "event_type": "user_feedback", - "opinion": "Perfect explanation", - "metadata": { - "feedback_type": "textual" - } - } - ] - }, - { - "id": "comp_9c81d85d7412", - "request": { - "prompt": "Sample question about translation", - "messages": [], - "tools": [], - "model": "llama-3.1-8b-instruct" - }, - "response": { - "text": "Sample response for translation with feedback potential", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 20, - "entry_tokens": 50, - "total_tokens": 70 - } - }, - "context": { - "app_id": "globodyne", - "task_id": "translation", - "thread_id": null, - "created_at": "2025-05-30T08:20:51.313778+00:00" - }, - "events": [ - { - "event_type": "user_feedback", - "thumb": "up", - "metadata": { - "random_feedback": true - } - }, - { - "event_type": "user_feedback", - "rating": 5, - "metadata": { - "rating_context": "overall_quality" - } - }, - { - "event_type": "user_feedback", - "opinion": "Not quite what I was looking for", - "metadata": { - "feedback_type": "textual" - } - } - ] - }, - { - "id": "comp_57ed605f351a", - "request": { - "prompt": "Sample question about legal-research", - "messages": [], - "tools": [], - "model": "llama-3.1-8b-instruct" - }, - "response": { - "text": "Sample response for legal-research with feedback potential", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 20, - "entry_tokens": 50, - "total_tokens": 70 - } - }, - "context": { - "app_id": "pied-piper", - "task_id": "legal-research", - "thread_id": null, - "created_at": "2025-05-11T12:45:51.313784+00:00" - }, - "events": [ - { - "event_type": "user_feedback", - "thumb": "up", - "metadata": { - "random_feedback": true - } - } - ] - }, - { - "id": "comp_60dd202c0306", - "request": { - "prompt": "Sample question about customer-support", - "messages": [], - "tools": [], - "model": "llama-3.1-8b-instruct" - }, - "response": { - "text": "Sample response for customer-support with feedback potential", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 20, - "entry_tokens": 50, - "total_tokens": 70 - } - }, - "context": { - "app_id": "initech", - "task_id": "customer-support", - "thread_id": null, - "created_at": "2025-05-02T01:45:51.313790+00:00" - }, - "events": [ - { - "event_type": "user_feedback", - "thumb": "down", - "metadata": { - "random_feedback": true - } - } - ] - }, - { - "id": "comp_8e9911fa7ba4", - "request": { - "prompt": "Sample question about legal-research", - "messages": [], - "tools": [], - "model": "llama-3.1-8b-instruct" - }, - "response": { - "text": "Sample response for legal-research with feedback potential", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 20, - "entry_tokens": 50, - "total_tokens": 70 - } - }, - "context": { - "app_id": "globodyne", - "task_id": "legal-research", - "thread_id": null, - "created_at": "2025-04-30T13:11:51.313796+00:00" - }, - "events": [ - { - "event_type": "user_feedback", - "thumb": "up", - "metadata": { - "random_feedback": true - } - } - ] - }, - { - "id": "comp_35b75d9ed2a8", - "request": { - "prompt": "Sample question about translation", - "messages": [], - "tools": [], - "model": "llama-3.1-8b-instruct" - }, - "response": { - "text": "Sample response for translation with feedback potential", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 20, - "entry_tokens": 50, - "total_tokens": 70 - } - }, - "context": { - "app_id": "globodyne", - "task_id": "translation", - "thread_id": null, - "created_at": "2025-05-04T14:51:51.313802+00:00" - }, - "events": [ - { - "event_type": "user_feedback", - "rating": 2, - "metadata": { - "rating_context": "overall_quality" - } - } - ] - }, - { - "id": "comp_191ace3af62a", - "request": { - "prompt": "Sample question about code-generation", - "messages": [], - "tools": [], - "model": "llama-3.1-8b-instruct" - }, - "response": { - "text": "Sample response for code-generation with feedback potential", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 20, - "entry_tokens": 50, - "total_tokens": 70 - } - }, - "context": { - "app_id": "globodyne", - "task_id": "code-generation", - "thread_id": null, - "created_at": "2025-05-03T08:36:51.313808+00:00" - }, - "events": null - }, - { - "id": "comp_af2f390869f3", - "request": { - "prompt": "Sample question about content-writing", - "messages": [], - "tools": [], - "model": "llama-3.1-8b-instruct" - }, - "response": { - "text": "Sample response for content-writing with feedback potential", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 20, - "entry_tokens": 50, - "total_tokens": 70 - } - }, - "context": { - "app_id": "hooli", - "task_id": "content-writing", - "thread_id": null, - "created_at": "2025-05-06T21:51:51.313814+00:00" - }, - "events": [ - { - "event_type": "user_feedback", - "thumb": "up", - "metadata": { - "random_feedback": true - } - }, - { - "event_type": "user_feedback", - "opinion": "Very helpful response!", - "metadata": { - "feedback_type": "textual" - } - } - ] - }, - { - "id": "comp_9b07165016f8", - "request": { - "prompt": "Sample question about customer-support", - "messages": [], - "tools": [], - "model": "llama-3.1-8b-instruct" - }, - "response": { - "text": "Sample response for customer-support with feedback potential", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 20, - "entry_tokens": 50, - "total_tokens": 70 - } - }, - "context": { - "app_id": "pied-piper", - "task_id": "customer-support", - "thread_id": null, - "created_at": "2025-05-04T10:39:51.313820+00:00" - }, - "events": [ - { - "event_type": "user_feedback", - "thumb": "up", - "metadata": { - "random_feedback": true - } - }, - { - "event_type": "user_feedback", - "rating": 3, - "metadata": { - "rating_context": "overall_quality" - } - } - ] - }, - { - "id": "comp_b59790daa5d2", - "request": { - "prompt": "Sample question about medical-qa", - "messages": [], - "tools": [], - "model": "llama-3.1-8b-instruct" - }, - "response": { - "text": "Sample response for medical-qa with feedback potential", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 20, - "entry_tokens": 50, - "total_tokens": 70 - } - }, - "context": { - "app_id": "initech", - "task_id": "medical-qa", - "thread_id": null, - "created_at": "2025-05-10T00:21:51.313826+00:00" - }, - "events": null - }, - { - "id": "comp_4702f592a8d9", - "request": { - "prompt": "Sample question about translation", - "messages": [], - "tools": [], - "model": "llama-3.1-8b-instruct" - }, - "response": { - "text": "Sample response for translation with feedback potential", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 20, - "entry_tokens": 50, - "total_tokens": 70 - } - }, - "context": { - "app_id": "pied-piper", - "task_id": "translation", - "thread_id": null, - "created_at": "2025-04-30T12:21:51.313832+00:00" - }, - "events": [ - { - "event_type": "user_feedback", - "thumb": "down", - "metadata": { - "random_feedback": true - } - } - ] - }, - { - "id": "comp_46ca7d295e10", - "request": { - "prompt": "Sample question about legal-research", - "messages": [], - "tools": [], - "model": "llama-3.1-8b-instruct" - }, - "response": { - "text": "Sample response for legal-research with feedback potential", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 20, - "entry_tokens": 50, - "total_tokens": 70 - } - }, - "context": { - "app_id": "acme-corp", - "task_id": "legal-research", - "thread_id": null, - "created_at": "2025-05-27T17:44:51.313840+00:00" - }, - "events": [ - { - "event_type": "user_feedback", - "thumb": "down", - "metadata": { - "random_feedback": true - } - }, - { - "event_type": "user_feedback", - "rating": 2, - "metadata": { - "rating_context": "overall_quality" - } - }, - { - "event_type": "user_feedback", - "opinion": "This solved my problem exactly", - "metadata": { - "feedback_type": "textual" - } - } - ] - }, - { - "id": "comp_8f035b64c9a6", - "request": { - "prompt": "Sample question about translation", - "messages": [], - "tools": [], - "model": "llama-3.1-8b-instruct" - }, - "response": { - "text": "Sample response for translation with feedback potential", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 20, - "entry_tokens": 50, - "total_tokens": 70 - } - }, - "context": { - "app_id": "pied-piper", - "task_id": "translation", - "thread_id": null, - "created_at": "2025-05-28T02:32:51.313846+00:00" - }, - "events": [ - { - "event_type": "user_feedback", - "rating": 1, - "metadata": { - "rating_context": "overall_quality" - } - } - ] - }, - { - "id": "comp_44cda69085e6", - "request": { - "prompt": "Sample question about medical-qa", - "messages": [], - "tools": [], - "model": "llama-3.1-8b-instruct" - }, - "response": { - "text": "Sample response for medical-qa with feedback potential", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 20, - "entry_tokens": 50, - "total_tokens": 70 - } - }, - "context": { - "app_id": "hooli", - "task_id": "medical-qa", - "thread_id": null, - "created_at": "2025-05-30T15:31:51.313853+00:00" - }, - "events": [ - { - "event_type": "user_feedback", - "thumb": "down", - "metadata": { - "random_feedback": true - } - }, - { - "event_type": "user_feedback", - "rating": 2, - "metadata": { - "rating_context": "overall_quality" - } - }, - { - "event_type": "user_feedback", - "opinion": "Very helpful response!", - "metadata": { - "feedback_type": "textual" - } - } - ] - }, - { - "id": "comp_023421a28883", - "request": { - "prompt": "Sample question about education", - "messages": [], - "tools": [], - "model": "llama-3.1-8b-instruct" - }, - "response": { - "text": "Sample response for education with feedback potential", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 20, - "entry_tokens": 50, - "total_tokens": 70 - } - }, - "context": { - "app_id": "globodyne", - "task_id": "education", - "thread_id": null, - "created_at": "2025-05-23T20:59:51.313859+00:00" - }, - "events": [ - { - "event_type": "user_feedback", - "thumb": "up", - "metadata": { - "random_feedback": true - } - }, - { - "event_type": "user_feedback", - "rating": 5, - "metadata": { - "rating_context": "overall_quality" - } - } - ] - }, - { - "id": "comp_2a9cbac246d4", - "request": { - "prompt": "Sample question about code-generation", - "messages": [], - "tools": [], - "model": "llama-3.1-8b-instruct" - }, - "response": { - "text": "Sample response for code-generation with feedback potential", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 20, - "entry_tokens": 50, - "total_tokens": 70 - } - }, - "context": { - "app_id": "hooli", - "task_id": "code-generation", - "thread_id": null, - "created_at": "2025-05-04T10:33:51.313866+00:00" - }, - "events": null - }, - { - "id": "comp_aea071e6d541", - "request": { - "prompt": "Sample question about education", - "messages": [], - "tools": [], - "model": "llama-3.1-8b-instruct" - }, - "response": { - "text": "Sample response for education with feedback potential", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 20, - "entry_tokens": 50, - "total_tokens": 70 - } - }, - "context": { - "app_id": "pied-piper", - "task_id": "education", - "thread_id": null, - "created_at": "2025-05-27T00:11:51.313873+00:00" - }, - "events": [ - { - "event_type": "user_feedback", - "thumb": "up", - "metadata": { - "random_feedback": true - } - }, - { - "event_type": "user_feedback", - "opinion": "Clear and concise, thanks!", - "metadata": { - "feedback_type": "textual" - } - } - ] - }, - { - "id": "comp_a3bc0df7fe1d", - "request": { - "prompt": "Sample question about customer-support", - "messages": [], - "tools": [], - "model": "llama-3.1-8b-instruct" - }, - "response": { - "text": "Sample response for customer-support with feedback potential", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 20, - "entry_tokens": 50, - "total_tokens": 70 - } - }, - "context": { - "app_id": "acme-corp", - "task_id": "customer-support", - "thread_id": null, - "created_at": "2025-05-16T06:58:51.313879+00:00" - }, - "events": [ - { - "event_type": "user_feedback", - "thumb": "up", - "metadata": { - "random_feedback": true - } - } - ] - }, - { - "id": "comp_71cc30f9b3ce", - "request": { - "prompt": "Sample question about education", - "messages": [], - "tools": [], - "model": "llama-3.1-8b-instruct" - }, - "response": { - "text": "Sample response for education with feedback potential", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 20, - "entry_tokens": 50, - "total_tokens": 70 - } - }, - "context": { - "app_id": "acme-corp", - "task_id": "education", - "thread_id": null, - "created_at": "2025-05-03T04:53:51.313885+00:00" - }, - "events": [ - { - "event_type": "user_feedback", - "thumb": "down", - "metadata": { - "random_feedback": true - } - }, - { - "event_type": "user_feedback", - "rating": 1, - "metadata": { - "rating_context": "overall_quality" - } - } - ] - }, - { - "id": "comp_a6d33218d24d", - "request": { - "prompt": "Generate a SQL query to find active users", - "messages": [], - "tools": [], - "model": "llama-3.1-8b-instruct" - }, - "response": { - "text": "```sql\nSELECT user_id, email, last_login\nFROM users \nWHERE last_login >= DATE_SUB(NOW(), INTERVAL 30 DAY)\nAND status = 'active'\nORDER BY last_login DESC;\n```", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 30, - "entry_tokens": 120, - "total_tokens": 150 - } - }, - "context": { - "app_id": "initech", - "task_id": "code-generation", - "thread_id": null, - "created_at": "2025-05-30T00:52:51.313893+00:00" - }, - "events": [ - { - "event_type": "user_action", - "action": "copy_clicked", - "metadata": { - "copied_content": "sql_query" - } - }, - { - "event_type": "user_action", - "action": "share_clicked", - "metadata": { - "share_platform": "slack", - "team_share": true - } - } - ] - }, - { - "id": "comp_b6d2559e4858", - "request": { - "prompt": "Show me how to deploy a React app to production", - "messages": [], - "tools": [], - "model": "llama-3.1-8b-instruct" - }, - "response": { - "text": "Here's a step-by-step guide to deploy your React app:\n\n1. Build the production version:\n```bash\nnpm run build\n```\n\n2. Choose a hosting platform (Netlify, Vercel, or AWS S3)\n\n3. For Netlify:\n- Connect your GitHub repo\n- Set build command: `npm run build`\n- Set publish directory: `build`\n\n4. Configure environment variables if needed\n\nYour app will be live with automatic SSL and CDN!", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 30, - "entry_tokens": 120, - "total_tokens": 150 - } - }, - "context": { - "app_id": "acme-corp", - "task_id": "code-generation", - "thread_id": null, - "created_at": "2025-05-14T15:39:51.313899+00:00" - }, - "events": [ - { - "event_type": "user_action", - "action": "copy_clicked", - "metadata": { - "copied_content": "bash_command" - } - }, - { - "event_type": "user_action", - "action": "link_clicked", - "metadata": { - "external_link": "netlify.com" - } - } - ] - }, - { - "id": "comp_cef60b2c28fe", - "request": { - "prompt": "Sample prompt for data-analysis task", - "messages": [], - "tools": [], - "model": "llama-3.1-8b-instruct" - }, - "response": { - "text": "Response with actionable content for data-analysis", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 30, - "entry_tokens": 80, - "total_tokens": 110 - } - }, - "context": { - "app_id": "hooli", - "task_id": "data-analysis", - "thread_id": null, - "created_at": "2025-05-26T03:49:51.313907+00:00" - }, - "events": [ - { - "event_type": "user_action", - "action": "bookmark_clicked", - "metadata": { - "action_context": "data-analysis" - } - }, - { - "event_type": "user_action", - "action": "link_clicked", - "metadata": { - "secondary_action": true - } - } - ] - }, - { - "id": "comp_16c554e19579", - "request": { - "prompt": "Sample prompt for education task", - "messages": [], - "tools": [], - "model": "llama-3.1-8b-instruct" - }, - "response": { - "text": "Response with actionable content for education", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 30, - "entry_tokens": 80, - "total_tokens": 110 - } - }, - "context": { - "app_id": "hooli", - "task_id": "education", - "thread_id": null, - "created_at": "2025-05-16T19:01:51.313915+00:00" - }, - "events": [ - { - "event_type": "user_action", - "action": "share_clicked", - "metadata": { - "action_context": "education" - } - } - ] - }, - { - "id": "comp_11c3174525f1", - "request": { - "prompt": "Sample prompt for education task", - "messages": [], - "tools": [], - "model": "llama-3.1-8b-instruct" - }, - "response": { - "text": "Response with actionable content for education", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 30, - "entry_tokens": 80, - "total_tokens": 110 - } - }, - "context": { - "app_id": "globodyne", - "task_id": "education", - "thread_id": null, - "created_at": "2025-05-03T16:34:51.313922+00:00" - }, - "events": [ - { - "event_type": "user_action", - "action": "copy_clicked", - "metadata": { - "action_context": "education" - } - } - ] - }, - { - "id": "comp_c72433085781", - "request": { - "prompt": "Sample prompt for legal-research task", - "messages": [], - "tools": [], - "model": "llama-3.1-8b-instruct" - }, - "response": { - "text": "Response with actionable content for legal-research", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 30, - "entry_tokens": 80, - "total_tokens": 110 - } - }, - "context": { - "app_id": "hooli", - "task_id": "legal-research", - "thread_id": null, - "created_at": "2025-05-14T07:13:51.313928+00:00" - }, - "events": null - }, - { - "id": "comp_bb2a81e83f95", - "request": { - "prompt": "Sample prompt for education task", - "messages": [], - "tools": [], - "model": "llama-3.1-8b-instruct" - }, - "response": { - "text": "Response with actionable content for education", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 30, - "entry_tokens": 80, - "total_tokens": 110 - } - }, - "context": { - "app_id": "initech", - "task_id": "education", - "thread_id": null, - "created_at": "2025-05-06T00:13:51.313934+00:00" - }, - "events": [ - { - "event_type": "user_action", - "action": "share_clicked", - "metadata": { - "secondary_action": true - } - } - ] - }, - { - "id": "comp_14d42ed41bcd", - "request": { - "prompt": "Sample prompt for data-analysis task", - "messages": [], - "tools": [], - "model": "llama-3.1-8b-instruct" - }, - "response": { - "text": "Response with actionable content for data-analysis", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 30, - "entry_tokens": 80, - "total_tokens": 110 - } - }, - "context": { - "app_id": "initech", - "task_id": "data-analysis", - "thread_id": null, - "created_at": "2025-05-26T06:27:51.313944+00:00" - }, - "events": [ - { - "event_type": "user_action", - "action": "bookmark_clicked", - "metadata": { - "action_context": "data-analysis" - } - }, - { - "event_type": "user_action", - "action": "share_clicked", - "metadata": { - "secondary_action": true - } - } - ] - }, - { - "id": "comp_a012898a02d7", - "request": { - "prompt": "Sample prompt for education task", - "messages": [], - "tools": [], - "model": "llama-3.1-8b-instruct" - }, - "response": { - "text": "Response with actionable content for education", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 30, - "entry_tokens": 80, - "total_tokens": 110 - } - }, - "context": { - "app_id": "acme-corp", - "task_id": "education", - "thread_id": null, - "created_at": "2025-05-27T02:49:51.313950+00:00" - }, - "events": [ - { - "event_type": "user_action", - "action": "export_clicked", - "metadata": { - "action_context": "education" - } - } - ] - }, - { - "id": "comp_5c20f87d68bd", - "request": { - "prompt": "Sample prompt for data-analysis task", - "messages": [], - "tools": [], - "model": "llama-3.1-8b-instruct" - }, - "response": { - "text": "Response with actionable content for data-analysis", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 30, - "entry_tokens": 80, - "total_tokens": 110 - } - }, - "context": { - "app_id": "globodyne", - "task_id": "data-analysis", - "thread_id": null, - "created_at": "2025-05-04T18:45:51.313956+00:00" - }, - "events": [ - { - "event_type": "user_action", - "action": "link_clicked", - "metadata": { - "action_context": "data-analysis" - } - } - ] - }, - { - "id": "comp_090e988b72a8", - "request": { - "prompt": "Sample prompt for education task", - "messages": [], - "tools": [], - "model": "llama-3.1-8b-instruct" - }, - "response": { - "text": "Response with actionable content for education", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 30, - "entry_tokens": 80, - "total_tokens": 110 - } - }, - "context": { - "app_id": "pied-piper", - "task_id": "education", - "thread_id": null, - "created_at": "2025-05-08T13:11:51.313963+00:00" - }, - "events": [ - { - "event_type": "user_action", - "action": "copy_clicked", - "metadata": { - "action_context": "education" - } - }, - { - "event_type": "user_action", - "action": "bookmark_clicked", - "metadata": { - "secondary_action": true - } - } - ] - }, - { - "id": "comp_53850b304b04", - "request": { - "prompt": "Sample prompt for translation task", - "messages": [], - "tools": [], - "model": "llama-3.1-8b-instruct" - }, - "response": { - "text": "Response with actionable content for translation", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 30, - "entry_tokens": 80, - "total_tokens": 110 - } - }, - "context": { - "app_id": "hooli", - "task_id": "translation", - "thread_id": null, - "created_at": "2025-05-04T21:06:51.313970+00:00" - }, - "events": [ - { - "event_type": "user_action", - "action": "bookmark_clicked", - "metadata": { - "action_context": "translation" - } - }, - { - "event_type": "user_action", - "action": "export_clicked", - "metadata": { - "secondary_action": true - } - } - ] - }, - { - "id": "comp_4527ff3f527f", - "request": { - "prompt": "Sample prompt for education task", - "messages": [], - "tools": [], - "model": "llama-3.1-8b-instruct" - }, - "response": { - "text": "Response with actionable content for education", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 30, - "entry_tokens": 80, - "total_tokens": 110 - } - }, - "context": { - "app_id": "pied-piper", - "task_id": "education", - "thread_id": null, - "created_at": "2025-05-06T09:26:51.313976+00:00" - }, - "events": [ - { - "event_type": "user_action", - "action": "copy_clicked", - "metadata": { - "secondary_action": true - } - } - ] - }, - { - "id": "comp_bcf7aff6755a", - "request": { - "prompt": "Sample prompt for legal-research task", - "messages": [], - "tools": [], - "model": "llama-3.1-8b-instruct" - }, - "response": { - "text": "Response with actionable content for legal-research", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 30, - "entry_tokens": 80, - "total_tokens": 110 - } - }, - "context": { - "app_id": "globodyne", - "task_id": "legal-research", - "thread_id": null, - "created_at": "2025-05-19T23:12:51.313984+00:00" - }, - "events": [ - { - "event_type": "user_action", - "action": "link_clicked", - "metadata": { - "action_context": "legal-research" - } - } - ] - }, - { - "id": "comp_1ffb1feabfce", - "request": { - "prompt": "Sample prompt for legal-research task", - "messages": [], - "tools": [], - "model": "llama-3.1-8b-instruct" - }, - "response": { - "text": "Response with actionable content for legal-research", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 30, - "entry_tokens": 80, - "total_tokens": 110 - } - }, - "context": { - "app_id": "hooli", - "task_id": "legal-research", - "thread_id": null, - "created_at": "2025-05-15T08:44:51.313990+00:00" - }, - "events": [ - { - "event_type": "user_action", - "action": "link_clicked", - "metadata": { - "action_context": "legal-research" - } - }, - { - "event_type": "user_action", - "action": "export_clicked", - "metadata": { - "secondary_action": true - } - } - ] - }, - { - "id": "comp_4ced1fcc5313", - "request": { - "prompt": "Sample prompt for translation task", - "messages": [], - "tools": [], - "model": "llama-3.1-8b-instruct" - }, - "response": { - "text": "Response with actionable content for translation", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 30, - "entry_tokens": 80, - "total_tokens": 110 - } - }, - "context": { - "app_id": "acme-corp", - "task_id": "translation", - "thread_id": null, - "created_at": "2025-05-03T06:34:51.313996+00:00" - }, - "events": [ - { - "event_type": "user_action", - "action": "link_clicked", - "metadata": { - "action_context": "translation" - } - }, - { - "event_type": "user_action", - "action": "link_clicked", - "metadata": { - "secondary_action": true - } - } - ] - }, - { - "id": "comp_44ce33badea3", - "request": { - "prompt": "Sample prompt for education task", - "messages": [], - "tools": [], - "model": "llama-3.1-8b-instruct" - }, - "response": { - "text": "Response with actionable content for education", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 30, - "entry_tokens": 80, - "total_tokens": 110 - } - }, - "context": { - "app_id": "globodyne", - "task_id": "education", - "thread_id": null, - "created_at": "2025-05-12T23:33:51.314002+00:00" - }, - "events": [ - { - "event_type": "user_action", - "action": "share_clicked", - "metadata": { - "action_context": "education" - } - } - ] - }, - { - "id": "comp_513645f965fd", - "request": { - "prompt": "Sample prompt for legal-research task", - "messages": [], - "tools": [], - "model": "llama-3.1-8b-instruct" - }, - "response": { - "text": "Response with actionable content for legal-research", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 30, - "entry_tokens": 80, - "total_tokens": 110 - } - }, - "context": { - "app_id": "initech", - "task_id": "legal-research", - "thread_id": null, - "created_at": "2025-05-19T21:58:51.314009+00:00" - }, - "events": [ - { - "event_type": "user_action", - "action": "copy_clicked", - "metadata": { - "action_context": "legal-research" - } - } - ] - }, - { - "id": "comp_0e28cdc687a1", - "request": { - "prompt": "Sample prompt for legal-research task", - "messages": [], - "tools": [], - "model": "llama-3.1-8b-instruct" - }, - "response": { - "text": "Response with actionable content for legal-research", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 30, - "entry_tokens": 80, - "total_tokens": 110 - } - }, - "context": { - "app_id": "hooli", - "task_id": "legal-research", - "thread_id": null, - "created_at": "2025-05-04T10:59:51.314016+00:00" - }, - "events": null - }, - { - "id": "comp_47bf70c41b4e", - "request": { - "prompt": "Sample prompt for code-generation task", - "messages": [], - "tools": [], - "model": "llama-3.1-8b-instruct" - }, - "response": { - "text": "Response with actionable content for code-generation", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 30, - "entry_tokens": 80, - "total_tokens": 110 - } - }, - "context": { - "app_id": "globodyne", - "task_id": "code-generation", - "thread_id": null, - "created_at": "2025-05-20T10:27:51.314022+00:00" - }, - "events": null - } -] \ No newline at end of file diff --git a/services/intake/tests/data/conversations.json b/services/intake/tests/data/conversations.json deleted file mode 100644 index 518ae0b404..0000000000 --- a/services/intake/tests/data/conversations.json +++ /dev/null @@ -1,530 +0,0 @@ -[ - { - "id": "comp_8424576dede5", - "request": { - "prompt": "I'm having trouble logging into my account", - "messages": [ - { - "role": "user", - "content": "I'm having trouble logging into my account" - } - ], - "tools": [], - "model": "llama-3.1-70b-instruct" - }, - "response": { - "text": "I can help you with that login issue. Can you tell me what error message you're seeing?", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 200, - "entry_tokens": 300, - "total_tokens": 500 - } - }, - "context": { - "app_id": "acme-corp", - "task_id": "customer-support", - "thread_id": "thread_63dfd999", - "created_at": "2025-05-02T17:20:51.313530+00:00" - }, - "events": [ - { - "event_type": "user_feedback", - "thumb": "up", - "metadata": { - "helpful": true - } - } - ] - }, - { - "id": "comp_f2872cfd7595", - "request": { - "prompt": "It says 'invalid credentials' but I'm sure my password is correct", - "messages": [ - { - "role": "user", - "content": "I'm having trouble logging into my account" - }, - { - "role": "assistant", - "content": "I can help you with that login issue. Can you tell me what error message you're seeing?" - }, - { - "role": "user", - "content": "It says 'invalid credentials' but I'm sure my password is correct" - } - ], - "tools": [], - "model": "llama-3.1-70b-instruct" - }, - "response": { - "text": "That error usually means either the email or password is incorrect. Let's try resetting your password to be sure. I'll send you a reset link.", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 250, - "entry_tokens": 400, - "total_tokens": 650 - } - }, - "context": { - "app_id": "acme-corp", - "task_id": "customer-support", - "thread_id": "thread_63dfd999", - "created_at": "2025-05-01T23:26:51.313538+00:00" - }, - "events": null - }, - { - "id": "comp_46f1f65c2c58", - "request": { - "prompt": "I got the email and reset it, but it's still not working", - "messages": [ - { - "role": "user", - "content": "I'm having trouble logging into my account" - }, - { - "role": "assistant", - "content": "I can help you with that login issue. Can you tell me what error message you're seeing?" - }, - { - "role": "user", - "content": "It says 'invalid credentials' but I'm sure my password is correct" - }, - { - "role": "assistant", - "content": "That error usually means either the email or password is incorrect. Let's try resetting your password to be sure. I'll send you a reset link." - }, - { - "role": "user", - "content": "I got the email and reset it, but it's still not working" - } - ], - "tools": [], - "model": "llama-3.1-70b-instruct" - }, - "response": { - "text": "I see the issue now - your account was temporarily locked due to multiple failed attempts. I've unlocked it for you. Please try logging in again with your new password.", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 300, - "entry_tokens": 500, - "total_tokens": 800 - } - }, - "context": { - "app_id": "acme-corp", - "task_id": "customer-support", - "thread_id": "thread_63dfd999", - "created_at": "2025-05-19T21:55:51.313545+00:00" - }, - "events": [ - { - "event_type": "user_feedback", - "rating": 5, - "metadata": { - "session_helpful": true - } - } - ] - }, - { - "id": "comp_2d09cda8194f", - "request": { - "prompt": "I need to create a user registration API endpoint", - "messages": [ - { - "role": "user", - "content": "I need to create a user registration API endpoint" - } - ], - "tools": [], - "model": "llama-3.1-70b-instruct" - }, - "response": { - "text": "I'll help you create a user registration endpoint. Here's a basic structure:\n\n```python\nfrom flask import Flask, request, jsonify\nfrom werkzeug.security import generate_password_hash\n\n@app.route('/register', methods=['POST'])\ndef register():\n data = request.get_json()\n email = data.get('email')\n password = data.get('password')\n \n # Hash password and save user\n hashed_password = generate_password_hash(password)\n # Save to database...\n \n return jsonify({'message': 'User created successfully'}), 201\n```", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 200, - "entry_tokens": 300, - "total_tokens": 500 - } - }, - "context": { - "app_id": "hooli", - "task_id": "code-generation", - "thread_id": "thread_378fc4d2", - "created_at": "2025-04-30T19:09:51.313561+00:00" - }, - "events": [ - { - "event_type": "user_feedback", - "thumb": "up", - "metadata": { - "helpful": true - } - } - ] - }, - { - "id": "comp_5f2741273fed", - "request": { - "prompt": "This looks good, but I also need email validation and duplicate checking", - "messages": [ - { - "role": "user", - "content": "I need to create a user registration API endpoint" - }, - { - "role": "assistant", - "content": "I'll help you create a user registration endpoint. Here's a basic structure:\n\n```python\nfrom flask import Flask, request, jsonify\nfrom werkzeug.security import generate_password_hash\n\n@app.route('/register', methods=['POST'])\ndef register():\n data = request.get_json()\n email = data.get('email')\n password = data.get('password')\n \n # Hash password and save user\n hashed_password = generate_password_hash(password)\n # Save to database...\n \n return jsonify({'message': 'User created successfully'}), 201\n```" - }, - { - "role": "user", - "content": "This looks good, but I also need email validation and duplicate checking" - } - ], - "tools": [], - "model": "llama-3.1-70b-instruct" - }, - "response": { - "text": "Great point! Let me enhance that with proper validation:\n\n```python\nimport re\nfrom sqlalchemy.exc import IntegrityError\n\ndef validate_email(email):\n pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$'\n return re.match(pattern, email) is not None\n\n@app.route('/register', methods=['POST'])\ndef register():\n data = request.get_json()\n email = data.get('email')\n password = data.get('password')\n \n # Validate input\n if not email or not password:\n return jsonify({'error': 'Email and password required'}), 400\n \n if not validate_email(email):\n return jsonify({'error': 'Invalid email format'}), 400\n \n # Check for existing user\n existing_user = User.query.filter_by(email=email).first()\n if existing_user:\n return jsonify({'error': 'User already exists'}), 409\n \n # Create user...\n```", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 250, - "entry_tokens": 400, - "total_tokens": 650 - } - }, - "context": { - "app_id": "hooli", - "task_id": "code-generation", - "thread_id": "thread_378fc4d2", - "created_at": "2025-05-12T19:17:51.313569+00:00" - }, - "events": [ - { - "event_type": "user_action", - "action": "copy_clicked", - "metadata": { - "code_block": true - } - } - ] - }, - { - "id": "comp_675456ce400a", - "request": { - "prompt": "Perfect! Can you also add password strength validation?", - "messages": [ - { - "role": "user", - "content": "I need to create a user registration API endpoint" - }, - { - "role": "assistant", - "content": "I'll help you create a user registration endpoint. Here's a basic structure:\n\n```python\nfrom flask import Flask, request, jsonify\nfrom werkzeug.security import generate_password_hash\n\n@app.route('/register', methods=['POST'])\ndef register():\n data = request.get_json()\n email = data.get('email')\n password = data.get('password')\n \n # Hash password and save user\n hashed_password = generate_password_hash(password)\n # Save to database...\n \n return jsonify({'message': 'User created successfully'}), 201\n```" - }, - { - "role": "user", - "content": "This looks good, but I also need email validation and duplicate checking" - }, - { - "role": "assistant", - "content": "Great point! Let me enhance that with proper validation:\n\n```python\nimport re\nfrom sqlalchemy.exc import IntegrityError\n\ndef validate_email(email):\n pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$'\n return re.match(pattern, email) is not None\n\n@app.route('/register', methods=['POST'])\ndef register():\n data = request.get_json()\n email = data.get('email')\n password = data.get('password')\n \n # Validate input\n if not email or not password:\n return jsonify({'error': 'Email and password required'}), 400\n \n if not validate_email(email):\n return jsonify({'error': 'Invalid email format'}), 400\n \n # Check for existing user\n existing_user = User.query.filter_by(email=email).first()\n if existing_user:\n return jsonify({'error': 'User already exists'}), 409\n \n # Create user...\n```" - }, - { - "role": "user", - "content": "Perfect! Can you also add password strength validation?" - } - ], - "tools": [], - "model": "llama-3.1-70b-instruct" - }, - "response": { - "text": "Absolutely! Here's a password strength validator:\n\n```python\ndef validate_password_strength(password):\n if len(password) < 8:\n return False, 'Password must be at least 8 characters long'\n if not re.search(r'[A-Z]', password):\n return False, 'Password must contain at least one uppercase letter'\n if not re.search(r'[a-z]', password):\n return False, 'Password must contain at least one lowercase letter'\n if not re.search(r'\\d', password):\n return False, 'Password must contain at least one number'\n if not re.search(r'[!@#$%^&*(),.?\":{}|<>]', password):\n return False, 'Password must contain at least one special character'\n return True, 'Password is strong'\n\n# Add this validation before creating the user:\nis_strong, message = validate_password_strength(password)\nif not is_strong:\n return jsonify({'error': message}), 400\n```", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 300, - "entry_tokens": 500, - "total_tokens": 800 - } - }, - "context": { - "app_id": "hooli", - "task_id": "code-generation", - "thread_id": "thread_378fc4d2", - "created_at": "2025-05-03T19:03:51.313576+00:00" - }, - "events": [ - { - "event_type": "user_feedback", - "rating": 5, - "metadata": { - "session_helpful": true - } - } - ] - }, - { - "id": "comp_9a7a7d48e41f", - "request": { - "prompt": "Can you help me analyze our sales data trends?", - "messages": [ - { - "role": "user", - "content": "Can you help me analyze our sales data trends?" - } - ], - "tools": [], - "model": "llama-3.1-70b-instruct" - }, - "response": { - "text": "I'd be happy to help analyze your sales data! What specific aspects would you like to focus on? For example: monthly trends, regional performance, product categories, or seasonal patterns?", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 200, - "entry_tokens": 300, - "total_tokens": 500 - } - }, - "context": { - "app_id": "globodyne", - "task_id": "data-analysis", - "thread_id": "thread_53dd1d24", - "created_at": "2025-05-15T11:37:51.313587+00:00" - }, - "events": [ - { - "event_type": "user_feedback", - "thumb": "up", - "metadata": { - "helpful": true - } - } - ] - }, - { - "id": "comp_edcbaa6e0138", - "request": { - "prompt": "I want to understand why our Q2 sales dropped compared to Q1", - "messages": [ - { - "role": "user", - "content": "Can you help me analyze our sales data trends?" - }, - { - "role": "assistant", - "content": "I'd be happy to help analyze your sales data! What specific aspects would you like to focus on? For example: monthly trends, regional performance, product categories, or seasonal patterns?" - }, - { - "role": "user", - "content": "I want to understand why our Q2 sales dropped compared to Q1" - } - ], - "tools": [], - "model": "llama-3.1-70b-instruct" - }, - "response": { - "text": "Let's investigate the Q2 sales decline. I'll need to examine several factors:\n\n1. **Monthly breakdown**: Compare April, May, June individually\n2. **Product performance**: Which products/categories were most affected\n3. **Regional analysis**: Geographic impact patterns\n4. **External factors**: Market conditions, competition, seasonality\n\nCan you share your sales data or tell me what data sources you have available?", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 250, - "entry_tokens": 400, - "total_tokens": 650 - } - }, - "context": { - "app_id": "globodyne", - "task_id": "data-analysis", - "thread_id": "thread_53dd1d24", - "created_at": "2025-05-27T22:07:51.313595+00:00" - }, - "events": null - }, - { - "id": "comp_93b3bbbb82b0", - "request": { - "prompt": "We have sales by month, product category, and region. Q1 was $2.4M, Q2 was $1.8M", - "messages": [ - { - "role": "user", - "content": "Can you help me analyze our sales data trends?" - }, - { - "role": "assistant", - "content": "I'd be happy to help analyze your sales data! What specific aspects would you like to focus on? For example: monthly trends, regional performance, product categories, or seasonal patterns?" - }, - { - "role": "user", - "content": "I want to understand why our Q2 sales dropped compared to Q1" - }, - { - "role": "assistant", - "content": "Let's investigate the Q2 sales decline. I'll need to examine several factors:\n\n1. **Monthly breakdown**: Compare April, May, June individually\n2. **Product performance**: Which products/categories were most affected\n3. **Regional analysis**: Geographic impact patterns\n4. **External factors**: Market conditions, competition, seasonality\n\nCan you share your sales data or tell me what data sources you have available?" - }, - { - "role": "user", - "content": "We have sales by month, product category, and region. Q1 was $2.4M, Q2 was $1.8M" - } - ], - "tools": [], - "model": "llama-3.1-70b-instruct" - }, - "response": { - "text": "That's a significant 25% decline from Q1 to Q2 ($600k drop). Here's my analysis approach:\n\n**Key Questions:**\n- Which product categories drove the decline?\n- Was the drop consistent across all regions?\n- Did it happen gradually or sudden in a specific month?\n\n**Recommended Analysis:**\n1. Break down the $600k loss by category and region\n2. Compare month-over-month trends within Q2\n3. Look for external factors (market conditions, competitor launches)\n4. Check if this aligns with historical seasonal patterns\n\nThis will help identify if it's a temporary market condition or a deeper business issue requiring strategic changes.", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 300, - "entry_tokens": 500, - "total_tokens": 800 - } - }, - "context": { - "app_id": "globodyne", - "task_id": "data-analysis", - "thread_id": "thread_53dd1d24", - "created_at": "2025-05-16T14:32:51.313603+00:00" - }, - "events": [ - { - "event_type": "user_feedback", - "rating": 5, - "metadata": { - "session_helpful": true - } - } - ] - }, - { - "id": "comp_48c10d4ae39d", - "request": { - "prompt": "I need help writing a press release for our new product launch", - "messages": [ - { - "role": "user", - "content": "I need help writing a press release for our new product launch" - } - ], - "tools": [], - "model": "llama-3.1-70b-instruct" - }, - "response": { - "text": "I'd be happy to help you write a compelling press release! To create the most effective announcement, I'll need some key details:\n\n1. **Product name and category**\n2. **Key features and benefits**\n3. **Target audience**\n4. **Launch date and availability**\n5. **Company background**\n6. **Any notable partnerships or achievements**\n\nWhat's the product you're launching?", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 200, - "entry_tokens": 300, - "total_tokens": 500 - } - }, - "context": { - "app_id": "initech", - "task_id": "content-writing", - "thread_id": "thread_afbb9425", - "created_at": "2025-05-10T12:16:51.313614+00:00" - }, - "events": [ - { - "event_type": "user_feedback", - "thumb": "up", - "metadata": { - "helpful": true - } - } - ] - }, - { - "id": "comp_bf426c48d171", - "request": { - "prompt": "It's a new project management software called 'TaskFlow' that uses AI to optimize team workflows", - "messages": [ - { - "role": "user", - "content": "I need help writing a press release for our new product launch" - }, - { - "role": "assistant", - "content": "I'd be happy to help you write a compelling press release! To create the most effective announcement, I'll need some key details:\n\n1. **Product name and category**\n2. **Key features and benefits**\n3. **Target audience**\n4. **Launch date and availability**\n5. **Company background**\n6. **Any notable partnerships or achievements**\n\nWhat's the product you're launching?" - }, - { - "role": "user", - "content": "It's a new project management software called 'TaskFlow' that uses AI to optimize team workflows" - } - ], - "tools": [], - "model": "llama-3.1-70b-instruct" - }, - "response": { - "text": "Excellent! TaskFlow sounds innovative. Here's a draft press release structure:\n\n**FOR IMMEDIATE RELEASE**\n\n**Initech Launches TaskFlow: AI-Powered Project Management Platform Revolutionizes Team Productivity**\n\n*Revolutionary software leverages artificial intelligence to optimize workflows and boost team efficiency*\n\n[City, Date] \u2013 Initech today announced the launch of TaskFlow, a cutting-edge project management platform that harnesses the power of artificial intelligence to optimize team workflows and dramatically improve productivity.\n\nWould you like me to continue with specific features and benefits? What are TaskFlow's main AI capabilities?", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 250, - "entry_tokens": 400, - "total_tokens": 650 - } - }, - "context": { - "app_id": "initech", - "task_id": "content-writing", - "thread_id": "thread_afbb9425", - "created_at": "2025-05-07T02:50:51.313620+00:00" - }, - "events": null - }, - { - "id": "comp_7a5466510822", - "request": { - "prompt": "The AI can predict project bottlenecks, automatically assign tasks based on team skills, and suggest optimal deadlines", - "messages": [ - { - "role": "user", - "content": "I need help writing a press release for our new product launch" - }, - { - "role": "assistant", - "content": "I'd be happy to help you write a compelling press release! To create the most effective announcement, I'll need some key details:\n\n1. **Product name and category**\n2. **Key features and benefits**\n3. **Target audience**\n4. **Launch date and availability**\n5. **Company background**\n6. **Any notable partnerships or achievements**\n\nWhat's the product you're launching?" - }, - { - "role": "user", - "content": "It's a new project management software called 'TaskFlow' that uses AI to optimize team workflows" - }, - { - "role": "assistant", - "content": "Excellent! TaskFlow sounds innovative. Here's a draft press release structure:\n\n**FOR IMMEDIATE RELEASE**\n\n**Initech Launches TaskFlow: AI-Powered Project Management Platform Revolutionizes Team Productivity**\n\n*Revolutionary software leverages artificial intelligence to optimize workflows and boost team efficiency*\n\n[City, Date] \u2013 Initech today announced the launch of TaskFlow, a cutting-edge project management platform that harnesses the power of artificial intelligence to optimize team workflows and dramatically improve productivity.\n\nWould you like me to continue with specific features and benefits? What are TaskFlow's main AI capabilities?" - }, - { - "role": "user", - "content": "The AI can predict project bottlenecks, automatically assign tasks based on team skills, and suggest optimal deadlines" - } - ], - "tools": [], - "model": "llama-3.1-70b-instruct" - }, - "response": { - "text": "Perfect! Here's the complete press release:\n\n**FOR IMMEDIATE RELEASE**\n\n**Initech Launches TaskFlow: AI-Powered Project Management Platform Revolutionizes Team Productivity**\n\n*Revolutionary software leverages artificial intelligence to optimize workflows and boost team efficiency by up to 40%*\n\n[City, Date] \u2013 Initech today announced the launch of TaskFlow, a cutting-edge project management platform that harnesses artificial intelligence to transform how teams collaborate and execute projects.\n\n**Key Features:**\n\u2022 **Predictive Analytics**: AI algorithms identify potential bottlenecks before they impact deadlines\n\u2022 **Smart Task Assignment**: Automatically matches tasks to team members based on skills, availability, and task\n\u2022 **Optimized Scheduling**: Suggests realistic deadlines using historical data and team capacity\n\n\"TaskFlow represents the future of project management,\" said [Spokesperson Name], [Title] at Initech. \"Our AI doesn't just track progress \u2013 it actively optimizes your team's workflow to ensure projects succeed.\"\n\nTaskFlow is available immediately with pricing starting at $X per user per month. For more information, visit [website] or contact [email].\n\n**About Initech**\n[Company description]\n\n###", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 300, - "entry_tokens": 500, - "total_tokens": 800 - } - }, - "context": { - "app_id": "initech", - "task_id": "content-writing", - "thread_id": "thread_afbb9425", - "created_at": "2025-05-29T12:19:51.313630+00:00" - }, - "events": [ - { - "event_type": "user_feedback", - "rating": 5, - "metadata": { - "session_helpful": true - } - } - ] - } -] \ No newline at end of file diff --git a/services/intake/tests/data/feedback_heavy.json b/services/intake/tests/data/feedback_heavy.json deleted file mode 100644 index f0948a7596..0000000000 --- a/services/intake/tests/data/feedback_heavy.json +++ /dev/null @@ -1,1122 +0,0 @@ -[ - { - "id": "comp_fcae6f83cd77", - "request": { - "prompt": "Explain quantum computing in simple terms", - "messages": [ - { - "role": "user", - "content": "Explain quantum computing in simple terms" - } - ], - "tools": [], - "model": "llama-3.1-8b-instruct" - }, - "response": { - "text": "Quantum computing is like having a super-powered computer that can explore many solutions to a problem simultaneously, rather than checking them one by one like regular computers. Think of it as the difference between reading every book in a library one at a time versus somehow reading them all at once!", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 25, - "entry_tokens": 75, - "total_tokens": 100 - } - }, - "context": { - "app_id": "initech", - "task_id": "translation", - "thread_id": null, - "created_at": "2025-05-14T01:45:51.313640+00:00" - }, - "events": [ - { - "event_type": "user_feedback", - "thumb": "up", - "metadata": { - "clear_explanation": true - } - }, - { - "event_type": "user_feedback", - "rating": 4, - "metadata": { - "almost_perfect": true - } - }, - { - "event_type": "user_feedback", - "opinion": "Great analogy! Really helped me understand the concept.", - "metadata": { - "helpful_analogy": true - } - } - ] - }, - { - "id": "comp_05817698b6c5", - "request": { - "prompt": "Write a Python function to reverse a string", - "messages": [ - { - "role": "user", - "content": "Write a Python function to reverse a string" - } - ], - "tools": [], - "model": "llama-3.1-8b-instruct" - }, - "response": { - "text": "```python\ndef reverse_string(s):\n return s[::-1]\n```", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 25, - "entry_tokens": 75, - "total_tokens": 100 - } - }, - "context": { - "app_id": "hooli", - "task_id": "translation", - "thread_id": null, - "created_at": "2025-05-19T11:01:51.313647+00:00" - }, - "events": [ - { - "event_type": "user_feedback", - "thumb": "down", - "metadata": { - "too_simple": true, - "needs_explanation": true - } - }, - { - "event_type": "user_feedback", - "opinion": "This works but could you explain how slicing works?", - "metadata": { - "wants_detail": true - } - } - ] - }, - { - "id": "comp_7040ffaeccc5", - "request": { - "prompt": "What are the benefits of renewable energy?", - "messages": [ - { - "role": "user", - "content": "What are the benefits of renewable energy?" - } - ], - "tools": [], - "model": "llama-3.1-8b-instruct" - }, - "response": { - "text": "Renewable energy offers numerous benefits: 1) Environmental - reduces greenhouse gas emissions and air pollution, 2) Economic - creates jobs and reduces energy costs long-term, 3) Energy security - reduces dependence on fossil fuel imports, 4) Sustainability - inexhaustible energy sources, 5) Health - cleaner air leads to better public health outcomes.", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 25, - "entry_tokens": 75, - "total_tokens": 100 - } - }, - "context": { - "app_id": "initech", - "task_id": "legal-research", - "thread_id": null, - "created_at": "2025-05-04T12:46:51.313652+00:00" - }, - "events": [ - { - "event_type": "user_feedback", - "thumb": "up", - "metadata": { - "comprehensive": true - } - }, - { - "event_type": "user_feedback", - "rating": 5, - "metadata": { - "excellent_summary": true - } - }, - { - "event_type": "user_action", - "action": "share_clicked", - "metadata": { - "share_platform": "twitter" - } - } - ] - }, - { - "id": "comp_ddaee6c0fa71", - "request": { - "prompt": "Sample question about code-generation", - "messages": [], - "tools": [], - "model": "llama-3.1-8b-instruct" - }, - "response": { - "text": "Sample response for code-generation with feedback potential", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 20, - "entry_tokens": 50, - "total_tokens": 70 - } - }, - "context": { - "app_id": "globodyne", - "task_id": "code-generation", - "thread_id": null, - "created_at": "2025-05-12T14:22:51.313661+00:00" - }, - "events": [ - { - "event_type": "user_feedback", - "thumb": "up", - "metadata": { - "random_feedback": true - } - } - ] - }, - { - "id": "comp_c0a1f7664b2c", - "request": { - "prompt": "Sample question about content-writing", - "messages": [], - "tools": [], - "model": "llama-3.1-8b-instruct" - }, - "response": { - "text": "Sample response for content-writing with feedback potential", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 20, - "entry_tokens": 50, - "total_tokens": 70 - } - }, - "context": { - "app_id": "initech", - "task_id": "content-writing", - "thread_id": null, - "created_at": "2025-05-24T00:13:51.313667+00:00" - }, - "events": null - }, - { - "id": "comp_d88cf0482d97", - "request": { - "prompt": "Sample question about content-writing", - "messages": [], - "tools": [], - "model": "llama-3.1-8b-instruct" - }, - "response": { - "text": "Sample response for content-writing with feedback potential", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 20, - "entry_tokens": 50, - "total_tokens": 70 - } - }, - "context": { - "app_id": "initech", - "task_id": "content-writing", - "thread_id": null, - "created_at": "2025-05-08T14:39:51.313673+00:00" - }, - "events": null - }, - { - "id": "comp_f9e5b91c78db", - "request": { - "prompt": "Sample question about customer-support", - "messages": [], - "tools": [], - "model": "llama-3.1-8b-instruct" - }, - "response": { - "text": "Sample response for customer-support with feedback potential", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 20, - "entry_tokens": 50, - "total_tokens": 70 - } - }, - "context": { - "app_id": "hooli", - "task_id": "customer-support", - "thread_id": null, - "created_at": "2025-05-21T02:15:51.313679+00:00" - }, - "events": [ - { - "event_type": "user_feedback", - "thumb": "up", - "metadata": { - "random_feedback": true - } - } - ] - }, - { - "id": "comp_723337184968", - "request": { - "prompt": "Sample question about medical-qa", - "messages": [], - "tools": [], - "model": "llama-3.1-8b-instruct" - }, - "response": { - "text": "Sample response for medical-qa with feedback potential", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 20, - "entry_tokens": 50, - "total_tokens": 70 - } - }, - "context": { - "app_id": "acme-corp", - "task_id": "medical-qa", - "thread_id": null, - "created_at": "2025-05-18T13:25:51.313689+00:00" - }, - "events": [ - { - "event_type": "user_feedback", - "thumb": "up", - "metadata": { - "random_feedback": true - } - }, - { - "event_type": "user_feedback", - "rating": 3, - "metadata": { - "rating_context": "overall_quality" - } - } - ] - }, - { - "id": "comp_bf25d684fa56", - "request": { - "prompt": "Sample question about medical-qa", - "messages": [], - "tools": [], - "model": "llama-3.1-8b-instruct" - }, - "response": { - "text": "Sample response for medical-qa with feedback potential", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 20, - "entry_tokens": 50, - "total_tokens": 70 - } - }, - "context": { - "app_id": "pied-piper", - "task_id": "medical-qa", - "thread_id": null, - "created_at": "2025-05-05T21:41:51.313729+00:00" - }, - "events": [ - { - "event_type": "user_feedback", - "thumb": "up", - "metadata": { - "random_feedback": true - } - }, - { - "event_type": "user_feedback", - "rating": 2, - "metadata": { - "rating_context": "overall_quality" - } - } - ] - }, - { - "id": "comp_ae495a00be3e", - "request": { - "prompt": "Sample question about translation", - "messages": [], - "tools": [], - "model": "llama-3.1-8b-instruct" - }, - "response": { - "text": "Sample response for translation with feedback potential", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 20, - "entry_tokens": 50, - "total_tokens": 70 - } - }, - "context": { - "app_id": "globodyne", - "task_id": "translation", - "thread_id": null, - "created_at": "2025-05-19T07:04:51.313749+00:00" - }, - "events": [ - { - "event_type": "user_feedback", - "thumb": "up", - "metadata": { - "random_feedback": true - } - }, - { - "event_type": "user_feedback", - "opinion": "Could be more detailed", - "metadata": { - "feedback_type": "textual" - } - } - ] - }, - { - "id": "comp_bb109e3761f4", - "request": { - "prompt": "Sample question about content-writing", - "messages": [], - "tools": [], - "model": "llama-3.1-8b-instruct" - }, - "response": { - "text": "Sample response for content-writing with feedback potential", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 20, - "entry_tokens": 50, - "total_tokens": 70 - } - }, - "context": { - "app_id": "hooli", - "task_id": "content-writing", - "thread_id": null, - "created_at": "2025-05-28T12:54:51.313761+00:00" - }, - "events": [ - { - "event_type": "user_feedback", - "rating": 5, - "metadata": { - "rating_context": "overall_quality" - } - }, - { - "event_type": "user_feedback", - "opinion": "Could use more context", - "metadata": { - "feedback_type": "textual" - } - } - ] - }, - { - "id": "comp_1c405312e955", - "request": { - "prompt": "Sample question about medical-qa", - "messages": [], - "tools": [], - "model": "llama-3.1-8b-instruct" - }, - "response": { - "text": "Sample response for medical-qa with feedback potential", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 20, - "entry_tokens": 50, - "total_tokens": 70 - } - }, - "context": { - "app_id": "hooli", - "task_id": "medical-qa", - "thread_id": null, - "created_at": "2025-05-24T02:48:51.313770+00:00" - }, - "events": [ - { - "event_type": "user_feedback", - "thumb": "up", - "metadata": { - "random_feedback": true - } - }, - { - "event_type": "user_feedback", - "rating": 3, - "metadata": { - "rating_context": "overall_quality" - } - }, - { - "event_type": "user_feedback", - "opinion": "Perfect explanation", - "metadata": { - "feedback_type": "textual" - } - } - ] - }, - { - "id": "comp_9c81d85d7412", - "request": { - "prompt": "Sample question about translation", - "messages": [], - "tools": [], - "model": "llama-3.1-8b-instruct" - }, - "response": { - "text": "Sample response for translation with feedback potential", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 20, - "entry_tokens": 50, - "total_tokens": 70 - } - }, - "context": { - "app_id": "globodyne", - "task_id": "translation", - "thread_id": null, - "created_at": "2025-05-30T08:20:51.313778+00:00" - }, - "events": [ - { - "event_type": "user_feedback", - "thumb": "up", - "metadata": { - "random_feedback": true - } - }, - { - "event_type": "user_feedback", - "rating": 5, - "metadata": { - "rating_context": "overall_quality" - } - }, - { - "event_type": "user_feedback", - "opinion": "Not quite what I was looking for", - "metadata": { - "feedback_type": "textual" - } - } - ] - }, - { - "id": "comp_57ed605f351a", - "request": { - "prompt": "Sample question about legal-research", - "messages": [], - "tools": [], - "model": "llama-3.1-8b-instruct" - }, - "response": { - "text": "Sample response for legal-research with feedback potential", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 20, - "entry_tokens": 50, - "total_tokens": 70 - } - }, - "context": { - "app_id": "pied-piper", - "task_id": "legal-research", - "thread_id": null, - "created_at": "2025-05-11T12:45:51.313784+00:00" - }, - "events": [ - { - "event_type": "user_feedback", - "thumb": "up", - "metadata": { - "random_feedback": true - } - } - ] - }, - { - "id": "comp_60dd202c0306", - "request": { - "prompt": "Sample question about customer-support", - "messages": [], - "tools": [], - "model": "llama-3.1-8b-instruct" - }, - "response": { - "text": "Sample response for customer-support with feedback potential", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 20, - "entry_tokens": 50, - "total_tokens": 70 - } - }, - "context": { - "app_id": "initech", - "task_id": "customer-support", - "thread_id": null, - "created_at": "2025-05-02T01:45:51.313790+00:00" - }, - "events": [ - { - "event_type": "user_feedback", - "thumb": "down", - "metadata": { - "random_feedback": true - } - } - ] - }, - { - "id": "comp_8e9911fa7ba4", - "request": { - "prompt": "Sample question about legal-research", - "messages": [], - "tools": [], - "model": "llama-3.1-8b-instruct" - }, - "response": { - "text": "Sample response for legal-research with feedback potential", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 20, - "entry_tokens": 50, - "total_tokens": 70 - } - }, - "context": { - "app_id": "globodyne", - "task_id": "legal-research", - "thread_id": null, - "created_at": "2025-04-30T13:11:51.313796+00:00" - }, - "events": [ - { - "event_type": "user_feedback", - "thumb": "up", - "metadata": { - "random_feedback": true - } - } - ] - }, - { - "id": "comp_35b75d9ed2a8", - "request": { - "prompt": "Sample question about translation", - "messages": [], - "tools": [], - "model": "llama-3.1-8b-instruct" - }, - "response": { - "text": "Sample response for translation with feedback potential", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 20, - "entry_tokens": 50, - "total_tokens": 70 - } - }, - "context": { - "app_id": "globodyne", - "task_id": "translation", - "thread_id": null, - "created_at": "2025-05-04T14:51:51.313802+00:00" - }, - "events": [ - { - "event_type": "user_feedback", - "rating": 2, - "metadata": { - "rating_context": "overall_quality" - } - } - ] - }, - { - "id": "comp_191ace3af62a", - "request": { - "prompt": "Sample question about code-generation", - "messages": [], - "tools": [], - "model": "llama-3.1-8b-instruct" - }, - "response": { - "text": "Sample response for code-generation with feedback potential", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 20, - "entry_tokens": 50, - "total_tokens": 70 - } - }, - "context": { - "app_id": "globodyne", - "task_id": "code-generation", - "thread_id": null, - "created_at": "2025-05-03T08:36:51.313808+00:00" - }, - "events": null - }, - { - "id": "comp_af2f390869f3", - "request": { - "prompt": "Sample question about content-writing", - "messages": [], - "tools": [], - "model": "llama-3.1-8b-instruct" - }, - "response": { - "text": "Sample response for content-writing with feedback potential", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 20, - "entry_tokens": 50, - "total_tokens": 70 - } - }, - "context": { - "app_id": "hooli", - "task_id": "content-writing", - "thread_id": null, - "created_at": "2025-05-06T21:51:51.313814+00:00" - }, - "events": [ - { - "event_type": "user_feedback", - "thumb": "up", - "metadata": { - "random_feedback": true - } - }, - { - "event_type": "user_feedback", - "opinion": "Very helpful response!", - "metadata": { - "feedback_type": "textual" - } - } - ] - }, - { - "id": "comp_9b07165016f8", - "request": { - "prompt": "Sample question about customer-support", - "messages": [], - "tools": [], - "model": "llama-3.1-8b-instruct" - }, - "response": { - "text": "Sample response for customer-support with feedback potential", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 20, - "entry_tokens": 50, - "total_tokens": 70 - } - }, - "context": { - "app_id": "pied-piper", - "task_id": "customer-support", - "thread_id": null, - "created_at": "2025-05-04T10:39:51.313820+00:00" - }, - "events": [ - { - "event_type": "user_feedback", - "thumb": "up", - "metadata": { - "random_feedback": true - } - }, - { - "event_type": "user_feedback", - "rating": 3, - "metadata": { - "rating_context": "overall_quality" - } - } - ] - }, - { - "id": "comp_b59790daa5d2", - "request": { - "prompt": "Sample question about medical-qa", - "messages": [], - "tools": [], - "model": "llama-3.1-8b-instruct" - }, - "response": { - "text": "Sample response for medical-qa with feedback potential", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 20, - "entry_tokens": 50, - "total_tokens": 70 - } - }, - "context": { - "app_id": "initech", - "task_id": "medical-qa", - "thread_id": null, - "created_at": "2025-05-10T00:21:51.313826+00:00" - }, - "events": null - }, - { - "id": "comp_4702f592a8d9", - "request": { - "prompt": "Sample question about translation", - "messages": [], - "tools": [], - "model": "llama-3.1-8b-instruct" - }, - "response": { - "text": "Sample response for translation with feedback potential", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 20, - "entry_tokens": 50, - "total_tokens": 70 - } - }, - "context": { - "app_id": "pied-piper", - "task_id": "translation", - "thread_id": null, - "created_at": "2025-04-30T12:21:51.313832+00:00" - }, - "events": [ - { - "event_type": "user_feedback", - "thumb": "down", - "metadata": { - "random_feedback": true - } - } - ] - }, - { - "id": "comp_46ca7d295e10", - "request": { - "prompt": "Sample question about legal-research", - "messages": [], - "tools": [], - "model": "llama-3.1-8b-instruct" - }, - "response": { - "text": "Sample response for legal-research with feedback potential", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 20, - "entry_tokens": 50, - "total_tokens": 70 - } - }, - "context": { - "app_id": "acme-corp", - "task_id": "legal-research", - "thread_id": null, - "created_at": "2025-05-27T17:44:51.313840+00:00" - }, - "events": [ - { - "event_type": "user_feedback", - "thumb": "down", - "metadata": { - "random_feedback": true - } - }, - { - "event_type": "user_feedback", - "rating": 2, - "metadata": { - "rating_context": "overall_quality" - } - }, - { - "event_type": "user_feedback", - "opinion": "This solved my problem exactly", - "metadata": { - "feedback_type": "textual" - } - } - ] - }, - { - "id": "comp_8f035b64c9a6", - "request": { - "prompt": "Sample question about translation", - "messages": [], - "tools": [], - "model": "llama-3.1-8b-instruct" - }, - "response": { - "text": "Sample response for translation with feedback potential", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 20, - "entry_tokens": 50, - "total_tokens": 70 - } - }, - "context": { - "app_id": "pied-piper", - "task_id": "translation", - "thread_id": null, - "created_at": "2025-05-28T02:32:51.313846+00:00" - }, - "events": [ - { - "event_type": "user_feedback", - "rating": 1, - "metadata": { - "rating_context": "overall_quality" - } - } - ] - }, - { - "id": "comp_44cda69085e6", - "request": { - "prompt": "Sample question about medical-qa", - "messages": [], - "tools": [], - "model": "llama-3.1-8b-instruct" - }, - "response": { - "text": "Sample response for medical-qa with feedback potential", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 20, - "entry_tokens": 50, - "total_tokens": 70 - } - }, - "context": { - "app_id": "hooli", - "task_id": "medical-qa", - "thread_id": null, - "created_at": "2025-05-30T15:31:51.313853+00:00" - }, - "events": [ - { - "event_type": "user_feedback", - "thumb": "down", - "metadata": { - "random_feedback": true - } - }, - { - "event_type": "user_feedback", - "rating": 2, - "metadata": { - "rating_context": "overall_quality" - } - }, - { - "event_type": "user_feedback", - "opinion": "Very helpful response!", - "metadata": { - "feedback_type": "textual" - } - } - ] - }, - { - "id": "comp_023421a28883", - "request": { - "prompt": "Sample question about education", - "messages": [], - "tools": [], - "model": "llama-3.1-8b-instruct" - }, - "response": { - "text": "Sample response for education with feedback potential", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 20, - "entry_tokens": 50, - "total_tokens": 70 - } - }, - "context": { - "app_id": "globodyne", - "task_id": "education", - "thread_id": null, - "created_at": "2025-05-23T20:59:51.313859+00:00" - }, - "events": [ - { - "event_type": "user_feedback", - "thumb": "up", - "metadata": { - "random_feedback": true - } - }, - { - "event_type": "user_feedback", - "rating": 5, - "metadata": { - "rating_context": "overall_quality" - } - } - ] - }, - { - "id": "comp_2a9cbac246d4", - "request": { - "prompt": "Sample question about code-generation", - "messages": [], - "tools": [], - "model": "llama-3.1-8b-instruct" - }, - "response": { - "text": "Sample response for code-generation with feedback potential", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 20, - "entry_tokens": 50, - "total_tokens": 70 - } - }, - "context": { - "app_id": "hooli", - "task_id": "code-generation", - "thread_id": null, - "created_at": "2025-05-04T10:33:51.313866+00:00" - }, - "events": null - }, - { - "id": "comp_aea071e6d541", - "request": { - "prompt": "Sample question about education", - "messages": [], - "tools": [], - "model": "llama-3.1-8b-instruct" - }, - "response": { - "text": "Sample response for education with feedback potential", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 20, - "entry_tokens": 50, - "total_tokens": 70 - } - }, - "context": { - "app_id": "pied-piper", - "task_id": "education", - "thread_id": null, - "created_at": "2025-05-27T00:11:51.313873+00:00" - }, - "events": [ - { - "event_type": "user_feedback", - "thumb": "up", - "metadata": { - "random_feedback": true - } - }, - { - "event_type": "user_feedback", - "opinion": "Clear and concise, thanks!", - "metadata": { - "feedback_type": "textual" - } - } - ] - }, - { - "id": "comp_a3bc0df7fe1d", - "request": { - "prompt": "Sample question about customer-support", - "messages": [], - "tools": [], - "model": "llama-3.1-8b-instruct" - }, - "response": { - "text": "Sample response for customer-support with feedback potential", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 20, - "entry_tokens": 50, - "total_tokens": 70 - } - }, - "context": { - "app_id": "acme-corp", - "task_id": "customer-support", - "thread_id": null, - "created_at": "2025-05-16T06:58:51.313879+00:00" - }, - "events": [ - { - "event_type": "user_feedback", - "thumb": "up", - "metadata": { - "random_feedback": true - } - } - ] - }, - { - "id": "comp_71cc30f9b3ce", - "request": { - "prompt": "Sample question about education", - "messages": [], - "tools": [], - "model": "llama-3.1-8b-instruct" - }, - "response": { - "text": "Sample response for education with feedback potential", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 20, - "entry_tokens": 50, - "total_tokens": 70 - } - }, - "context": { - "app_id": "acme-corp", - "task_id": "education", - "thread_id": null, - "created_at": "2025-05-03T04:53:51.313885+00:00" - }, - "events": [ - { - "event_type": "user_feedback", - "thumb": "down", - "metadata": { - "random_feedback": true - } - }, - { - "event_type": "user_feedback", - "rating": 1, - "metadata": { - "rating_context": "overall_quality" - } - } - ] - } -] \ No newline at end of file diff --git a/services/intake/tests/data/single_turns.json b/services/intake/tests/data/single_turns.json deleted file mode 100644 index 7d5068c02d..0000000000 --- a/services/intake/tests/data/single_turns.json +++ /dev/null @@ -1,962 +0,0 @@ -[ - { - "id": "comp_8287dcd5d647", - "request": { - "prompt": "Explain photosynthesis to a middle school student", - "messages": [], - "tools": [], - "model": "llama-3.1-8b-instruct" - }, - "response": { - "text": "Photosynthesis is like a plant's way of making food using sunlight! Plants take in carbon dioxide from the air, water from their roots, and use sunlight energy to create glucose (sugar) and release oxygen.", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 89, - "entry_tokens": 99, - "total_tokens": 285 - } - }, - "context": { - "app_id": "acme-corp", - "task_id": "education", - "thread_id": null, - "created_at": "2025-05-03T07:18:51.311380+00:00" - } - }, - { - "id": "comp_5f49942666db", - "request": { - "prompt": "Research trademark infringement laws", - "messages": [], - "tools": [], - "model": "llama-3.1-8b-instruct" - }, - "response": { - "text": "Trademark infringement occurs when a party uses a mark that is identical or confusingly similar to a registered trademark. Key factors include: likelihood of confusion, strength of the mark, and similarity of goods/services.", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 64, - "entry_tokens": 55, - "total_tokens": 223 - } - }, - "context": { - "app_id": "acme-corp", - "task_id": "legal-research", - "thread_id": null, - "created_at": "2025-05-16T11:27:51.313114+00:00" - } - }, - { - "id": "comp_6f8389973692", - "request": { - "prompt": "Explain photosynthesis to a middle school student", - "messages": [], - "tools": [], - "model": "llama-3.1-8b-instruct" - }, - "response": { - "text": "Photosynthesis is like a plant's way of making food using sunlight! Plants take in carbon dioxide from the air, water from their roots, and use sunlight energy to create glucose (sugar) and release oxygen.", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 41, - "entry_tokens": 193, - "total_tokens": 156 - } - }, - "context": { - "app_id": "initech", - "task_id": "education", - "thread_id": null, - "created_at": "2025-05-20T10:58:51.313132+00:00" - } - }, - { - "id": "comp_4142018bfa4e", - "request": { - "prompt": "Research trademark infringement laws", - "messages": [], - "tools": [], - "model": "llama-3.1-8b-instruct" - }, - "response": { - "text": "Trademark infringement occurs when a party uses a mark that is identical or confusingly similar to a registered trademark. Key factors include: likelihood of confusion, strength of the mark, and similarity of goods/services.", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 66, - "entry_tokens": 182, - "total_tokens": 123 - } - }, - "context": { - "app_id": "hooli", - "task_id": "legal-research", - "thread_id": null, - "created_at": "2025-05-15T03:50:51.313146+00:00" - } - }, - { - "id": "comp_a6674fc471f2", - "request": { - "prompt": "Explain photosynthesis to a middle school student", - "messages": [], - "tools": [], - "model": "llama-3.1-8b-instruct" - }, - "response": { - "text": "Photosynthesis is like a plant's way of making food using sunlight! Plants take in carbon dioxide from the air, water from their roots, and use sunlight energy to create glucose (sugar) and release oxygen.", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 51, - "entry_tokens": 67, - "total_tokens": 289 - } - }, - "context": { - "app_id": "pied-piper", - "task_id": "education", - "thread_id": null, - "created_at": "2025-05-29T13:29:51.313155+00:00" - } - }, - { - "id": "comp_ad120c5aff99", - "request": { - "prompt": "Write a Python function to validate email addresses", - "messages": [], - "tools": [], - "model": "llama-3.1-8b-instruct" - }, - "response": { - "text": "```python\nimport re\n\ndef validate_email(email):\n pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$'\n return re.match(pattern, email) is not None\n```", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 53, - "entry_tokens": 173, - "total_tokens": 233 - } - }, - "context": { - "app_id": "acme-corp", - "task_id": "code-generation", - "thread_id": null, - "created_at": "2025-05-28T11:58:51.313163+00:00" - } - }, - { - "id": "comp_8ea1df8a755e", - "request": { - "prompt": "Analyze customer churn patterns in our dataset", - "messages": [], - "tools": [], - "model": "llama-3.1-8b-instruct" - }, - "response": { - "text": "Based on the analysis, customer churn is highest in months 3-6 after signup (28% churn rate). Key factors include lack of feature usage (45% correlation) and poor onboarding entry (62% of churned users didn't complete setup).", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 99, - "entry_tokens": 127, - "total_tokens": 185 - } - }, - "context": { - "app_id": "acme-corp", - "task_id": "data-analysis", - "thread_id": null, - "created_at": "2025-05-11T14:15:51.313194+00:00" - } - }, - { - "id": "comp_747c7533695d", - "request": { - "prompt": "Research trademark infringement laws", - "messages": [], - "tools": [], - "model": "llama-3.1-8b-instruct" - }, - "response": { - "text": "Trademark infringement occurs when a party uses a mark that is identical or confusingly similar to a registered trademark. Key factors include: likelihood of confusion, strength of the mark, and similarity of goods/services.", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 54, - "entry_tokens": 155, - "total_tokens": 128 - } - }, - "context": { - "app_id": "hooli", - "task_id": "legal-research", - "thread_id": null, - "created_at": "2025-05-05T08:47:51.313214+00:00" - } - }, - { - "id": "comp_dcf1e2221530", - "request": { - "prompt": "Write a blog post about sustainable living", - "messages": [], - "tools": [], - "model": "llama-3.1-8b-instruct" - }, - "response": { - "text": "# Sustainable Living: Small Changes, Big Impact\n\nIn today's world, making environmentally conscious choices has never been more important. Here are 10 simple ways to reduce your carbon footprint and contribute to a healthier planet...", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 81, - "entry_tokens": 174, - "total_tokens": 162 - } - }, - "context": { - "app_id": "initech", - "task_id": "content-writing", - "thread_id": null, - "created_at": "2025-04-30T02:06:51.313223+00:00" - } - }, - { - "id": "comp_608af96131a8", - "request": { - "prompt": "Translate 'Hello, how are you?' to Spanish", - "messages": [], - "tools": [], - "model": "llama-3.1-8b-instruct" - }, - "response": { - "text": "The translation of 'Hello, how are you?' to Spanish is: 'Hola, \u00bfc\u00f3mo est\u00e1s?' (informal) or 'Hola, \u00bfc\u00f3mo est\u00e1 usted?' (formal).", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 55, - "entry_tokens": 149, - "total_tokens": 104 - } - }, - "context": { - "app_id": "pied-piper", - "task_id": "translation", - "thread_id": null, - "created_at": "2025-05-05T19:30:51.313232+00:00" - } - }, - { - "id": "comp_0a15e509eaed", - "request": { - "prompt": "Research trademark infringement laws", - "messages": [], - "tools": [], - "model": "llama-3.1-8b-instruct" - }, - "response": { - "text": "Trademark infringement occurs when a party uses a mark that is identical or confusingly similar to a registered trademark. Key factors include: likelihood of confusion, strength of the mark, and similarity of goods/services.", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 79, - "entry_tokens": 84, - "total_tokens": 271 - } - }, - "context": { - "app_id": "acme-corp", - "task_id": "legal-research", - "thread_id": null, - "created_at": "2025-05-27T07:13:51.313239+00:00" - } - }, - { - "id": "comp_541d2e65ddb5", - "request": { - "prompt": "Explain the symptoms of diabetes", - "messages": [], - "tools": [], - "model": "llama-3.1-8b-instruct" - }, - "response": { - "text": "Common diabetes symptoms include frequent urination, excessive thirst, unexplained weight loss, fatigue, and blurred vision. If you experience these symptoms, consult a healthcare provider for proper evaluation and testing.", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 88, - "entry_tokens": 89, - "total_tokens": 182 - } - }, - "context": { - "app_id": "acme-corp", - "task_id": "medical-qa", - "thread_id": null, - "created_at": "2025-05-06T02:08:51.313247+00:00" - } - }, - { - "id": "comp_45972e45c9c9", - "request": { - "prompt": "Analyze customer churn patterns in our dataset", - "messages": [], - "tools": [], - "model": "llama-3.1-8b-instruct" - }, - "response": { - "text": "Based on the analysis, customer churn is highest in months 3-6 after signup (28% churn rate). Key factors include lack of feature usage (45% correlation) and poor onboarding entry (62% of churned users didn't complete setup).", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 29, - "entry_tokens": 50, - "total_tokens": 112 - } - }, - "context": { - "app_id": "initech", - "task_id": "data-analysis", - "thread_id": null, - "created_at": "2025-05-29T08:07:51.313254+00:00" - } - }, - { - "id": "comp_49894dc762e2", - "request": { - "prompt": "Create a REST API endpoint for user authentication", - "messages": [], - "tools": [], - "model": "llama-3.1-8b-instruct" - }, - "response": { - "text": "```python\nfrom flask import Flask, request, jsonify\nfrom werkzeug.security import check_password_hash\n\n@app.route('/auth', methods=['POST'])\ndef authenticate():\n data = request.get_json()\n # Authentication logic here\n return jsonify({'token': 'jwt_token_here'})\n```", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 26, - "entry_tokens": 107, - "total_tokens": 112 - } - }, - "context": { - "app_id": "pied-piper", - "task_id": "code-generation", - "thread_id": null, - "created_at": "2025-05-14T19:39:51.313262+00:00" - } - }, - { - "id": "comp_462a340a51d2", - "request": { - "prompt": "How do I reset my password?", - "messages": [], - "tools": [], - "model": "llama-3.1-8b-instruct" - }, - "response": { - "text": "I can help you reset your password. Please visit the account settings page and click 'Forgot Password'. You'll receive a reset link via email within 5 minutes.", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 33, - "entry_tokens": 148, - "total_tokens": 114 - } - }, - "context": { - "app_id": "pied-piper", - "task_id": "customer-support", - "thread_id": null, - "created_at": "2025-05-09T19:15:51.313269+00:00" - } - }, - { - "id": "comp_1746e72f5d42", - "request": { - "prompt": "Explain photosynthesis to a middle school student", - "messages": [], - "tools": [], - "model": "llama-3.1-8b-instruct" - }, - "response": { - "text": "Photosynthesis is like a plant's way of making food using sunlight! Plants take in carbon dioxide from the air, water from their roots, and use sunlight energy to create glucose (sugar) and release oxygen.", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 20, - "entry_tokens": 131, - "total_tokens": 162 - } - }, - "context": { - "app_id": "hooli", - "task_id": "education", - "thread_id": null, - "created_at": "2025-05-23T20:46:51.313276+00:00" - } - }, - { - "id": "comp_2e917c61ece8", - "request": { - "prompt": "How do I reset my password?", - "messages": [], - "tools": [], - "model": "llama-3.1-8b-instruct" - }, - "response": { - "text": "I can help you reset your password. Please visit the account settings page and click 'Forgot Password'. You'll receive a reset link via email within 5 minutes.", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 37, - "entry_tokens": 157, - "total_tokens": 107 - } - }, - "context": { - "app_id": "hooli", - "task_id": "customer-support", - "thread_id": null, - "created_at": "2025-05-01T19:56:51.313283+00:00" - } - }, - { - "id": "comp_ca3b5b4f5ee7", - "request": { - "prompt": "Explain the symptoms of diabetes", - "messages": [], - "tools": [], - "model": "llama-3.1-8b-instruct" - }, - "response": { - "text": "Common diabetes symptoms include frequent urination, excessive thirst, unexplained weight loss, fatigue, and blurred vision. If you experience these symptoms, consult a healthcare provider for proper evaluation and testing.", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 52, - "entry_tokens": 91, - "total_tokens": 179 - } - }, - "context": { - "app_id": "hooli", - "task_id": "medical-qa", - "thread_id": null, - "created_at": "2025-05-06T05:57:51.313293+00:00" - } - }, - { - "id": "comp_febeab1a79b2", - "request": { - "prompt": "Translate 'Hello, how are you?' to Spanish", - "messages": [], - "tools": [], - "model": "llama-3.1-8b-instruct" - }, - "response": { - "text": "The translation of 'Hello, how are you?' to Spanish is: 'Hola, \u00bfc\u00f3mo est\u00e1s?' (informal) or 'Hola, \u00bfc\u00f3mo est\u00e1 usted?' (formal).", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 51, - "entry_tokens": 111, - "total_tokens": 118 - } - }, - "context": { - "app_id": "acme-corp", - "task_id": "translation", - "thread_id": null, - "created_at": "2025-05-21T19:07:51.313300+00:00" - } - }, - { - "id": "comp_32df0cbe8ad8", - "request": { - "prompt": "Write a blog post about sustainable living", - "messages": [], - "tools": [], - "model": "llama-3.1-8b-instruct" - }, - "response": { - "text": "# Sustainable Living: Small Changes, Big Impact\n\nIn today's world, making environmentally conscious choices has never been more important. Here are 10 simple ways to reduce your carbon footprint and contribute to a healthier planet...", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 77, - "entry_tokens": 130, - "total_tokens": 100 - } - }, - "context": { - "app_id": "pied-piper", - "task_id": "content-writing", - "thread_id": null, - "created_at": "2025-05-09T04:50:51.313307+00:00" - } - }, - { - "id": "comp_33fbe54f5f35", - "request": { - "prompt": "Explain the symptoms of diabetes", - "messages": [], - "tools": [], - "model": "llama-3.1-8b-instruct" - }, - "response": { - "text": "Common diabetes symptoms include frequent urination, excessive thirst, unexplained weight loss, fatigue, and blurred vision. If you experience these symptoms, consult a healthcare provider for proper evaluation and testing.", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 69, - "entry_tokens": 173, - "total_tokens": 168 - } - }, - "context": { - "app_id": "pied-piper", - "task_id": "medical-qa", - "thread_id": null, - "created_at": "2025-05-18T16:29:51.313314+00:00" - } - }, - { - "id": "comp_9e58f1cdc20c", - "request": { - "prompt": "Translate 'Hello, how are you?' to Spanish", - "messages": [], - "tools": [], - "model": "llama-3.1-8b-instruct" - }, - "response": { - "text": "The translation of 'Hello, how are you?' to Spanish is: 'Hola, \u00bfc\u00f3mo est\u00e1s?' (informal) or 'Hola, \u00bfc\u00f3mo est\u00e1 usted?' (formal).", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 75, - "entry_tokens": 160, - "total_tokens": 72 - } - }, - "context": { - "app_id": "acme-corp", - "task_id": "translation", - "thread_id": null, - "created_at": "2025-05-19T01:15:51.313321+00:00" - } - }, - { - "id": "comp_7a2d00ef9aaf", - "request": { - "prompt": "Analyze customer churn patterns in our dataset", - "messages": [], - "tools": [], - "model": "llama-3.1-8b-instruct" - }, - "response": { - "text": "Based on the analysis, customer churn is highest in months 3-6 after signup (28% churn rate). Key factors include lack of feature usage (45% correlation) and poor onboarding entry (62% of churned users didn't complete setup).", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 55, - "entry_tokens": 155, - "total_tokens": 126 - } - }, - "context": { - "app_id": "initech", - "task_id": "data-analysis", - "thread_id": null, - "created_at": "2025-05-26T15:02:51.313327+00:00" - } - }, - { - "id": "comp_c1fc401fcb62", - "request": { - "prompt": "Create social media copy for a new product launch", - "messages": [], - "tools": [], - "model": "llama-3.1-8b-instruct" - }, - "response": { - "text": "\ud83d\ude80 Introducing our revolutionary new product! Experience the future of innovation with cutting-edge technology that transforms how you work and play. #Innovation #TechLaunch #GameChanger", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 65, - "entry_tokens": 134, - "total_tokens": 254 - } - }, - "context": { - "app_id": "initech", - "task_id": "content-writing", - "thread_id": null, - "created_at": "2025-05-12T10:36:51.313334+00:00" - } - }, - { - "id": "comp_c30633c76b70", - "request": { - "prompt": "Write a blog post about sustainable living", - "messages": [], - "tools": [], - "model": "llama-3.1-8b-instruct" - }, - "response": { - "text": "# Sustainable Living: Small Changes, Big Impact\n\nIn today's world, making environmentally conscious choices has never been more important. Here are 10 simple ways to reduce your carbon footprint and contribute to a healthier planet...", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 37, - "entry_tokens": 173, - "total_tokens": 273 - } - }, - "context": { - "app_id": "hooli", - "task_id": "content-writing", - "thread_id": null, - "created_at": "2025-05-04T11:53:51.313341+00:00" - } - }, - { - "id": "comp_4dac291613f3", - "request": { - "prompt": "How do I reset my password?", - "messages": [], - "tools": [], - "model": "llama-3.1-8b-instruct" - }, - "response": { - "text": "I can help you reset your password. Please visit the account settings page and click 'Forgot Password'. You'll receive a reset link via email within 5 minutes.", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 29, - "entry_tokens": 116, - "total_tokens": 72 - } - }, - "context": { - "app_id": "hooli", - "task_id": "customer-support", - "thread_id": null, - "created_at": "2025-05-05T12:21:51.313348+00:00" - } - }, - { - "id": "comp_f2ddd005f695", - "request": { - "prompt": "I'm having trouble with the mobile app", - "messages": [], - "tools": [], - "model": "llama-3.1-8b-instruct" - }, - "response": { - "text": "I understand the app issues you're experiencing. Try clearing the cache and data, then restart the app. If the problem persists, please update to the latest version.", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 46, - "entry_tokens": 83, - "total_tokens": 189 - } - }, - "context": { - "app_id": "acme-corp", - "task_id": "customer-support", - "thread_id": null, - "created_at": "2025-05-11T17:31:51.313355+00:00" - } - }, - { - "id": "comp_54b35075cc3f", - "request": { - "prompt": "Create a REST API endpoint for user authentication", - "messages": [], - "tools": [], - "model": "llama-3.1-8b-instruct" - }, - "response": { - "text": "```python\nfrom flask import Flask, request, jsonify\nfrom werkzeug.security import check_password_hash\n\n@app.route('/auth', methods=['POST'])\ndef authenticate():\n data = request.get_json()\n # Authentication logic here\n return jsonify({'token': 'jwt_token_here'})\n```", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 59, - "entry_tokens": 174, - "total_tokens": 260 - } - }, - "context": { - "app_id": "acme-corp", - "task_id": "code-generation", - "thread_id": null, - "created_at": "2025-05-08T07:31:51.313361+00:00" - } - }, - { - "id": "comp_bfe287f6507b", - "request": { - "prompt": "Research trademark infringement laws", - "messages": [], - "tools": [], - "model": "llama-3.1-8b-instruct" - }, - "response": { - "text": "Trademark infringement occurs when a party uses a mark that is identical or confusingly similar to a registered trademark. Key factors include: likelihood of confusion, strength of the mark, and similarity of goods/services.", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 41, - "entry_tokens": 87, - "total_tokens": 179 - } - }, - "context": { - "app_id": "hooli", - "task_id": "legal-research", - "thread_id": null, - "created_at": "2025-05-09T01:52:51.313368+00:00" - } - }, - { - "id": "comp_e2010e6848d3", - "request": { - "prompt": "Research trademark infringement laws", - "messages": [], - "tools": [], - "model": "llama-3.1-8b-instruct" - }, - "response": { - "text": "Trademark infringement occurs when a party uses a mark that is identical or confusingly similar to a registered trademark. Key factors include: likelihood of confusion, strength of the mark, and similarity of goods/services.", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 90, - "entry_tokens": 113, - "total_tokens": 124 - } - }, - "context": { - "app_id": "acme-corp", - "task_id": "legal-research", - "thread_id": null, - "created_at": "2025-05-11T10:26:51.313375+00:00" - } - }, - { - "id": "comp_beb0afc33448", - "request": { - "prompt": "Create a dashboard showing sales performance metrics", - "messages": [], - "tools": [], - "model": "llama-3.1-8b-instruct" - }, - "response": { - "text": "The sales dashboard reveals a 15% increase in Q3 performance with strongest growth in the enterprise segment (34% YoY). Regional breakdown shows North America leading at $2.3M, followed by Europe at $1.8M.", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 78, - "entry_tokens": 50, - "total_tokens": 261 - } - }, - "context": { - "app_id": "initech", - "task_id": "data-analysis", - "thread_id": null, - "created_at": "2025-05-13T04:58:51.313382+00:00" - } - }, - { - "id": "comp_bf854aee11a4", - "request": { - "prompt": "Analyze customer churn patterns in our dataset", - "messages": [], - "tools": [], - "model": "llama-3.1-8b-instruct" - }, - "response": { - "text": "Based on the analysis, customer churn is highest in months 3-6 after signup (28% churn rate). Key factors include lack of feature usage (45% correlation) and poor onboarding entry (62% of churned users didn't complete setup).", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 35, - "entry_tokens": 149, - "total_tokens": 291 - } - }, - "context": { - "app_id": "hooli", - "task_id": "data-analysis", - "thread_id": null, - "created_at": "2025-05-01T08:28:51.313390+00:00" - } - }, - { - "id": "comp_06026da097b4", - "request": { - "prompt": "Translate 'Hello, how are you?' to Spanish", - "messages": [], - "tools": [], - "model": "llama-3.1-8b-instruct" - }, - "response": { - "text": "The translation of 'Hello, how are you?' to Spanish is: 'Hola, \u00bfc\u00f3mo est\u00e1s?' (informal) or 'Hola, \u00bfc\u00f3mo est\u00e1 usted?' (formal).", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 66, - "entry_tokens": 125, - "total_tokens": 126 - } - }, - "context": { - "app_id": "hooli", - "task_id": "translation", - "thread_id": null, - "created_at": "2025-05-18T23:27:51.313398+00:00" - } - }, - { - "id": "comp_ba30a1bcb2fa", - "request": { - "prompt": "How do I reset my password?", - "messages": [], - "tools": [], - "model": "llama-3.1-8b-instruct" - }, - "response": { - "text": "I can help you reset your password. Please visit the account settings page and click 'Forgot Password'. You'll receive a reset link via email within 5 minutes.", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 66, - "entry_tokens": 53, - "total_tokens": 261 - } - }, - "context": { - "app_id": "hooli", - "task_id": "customer-support", - "thread_id": null, - "created_at": "2025-05-05T17:44:51.313404+00:00" - } - }, - { - "id": "comp_c9cec8b713fa", - "request": { - "prompt": "Write a Python function to validate email addresses", - "messages": [], - "tools": [], - "model": "llama-3.1-8b-instruct" - }, - "response": { - "text": "```python\nimport re\n\ndef validate_email(email):\n pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$'\n return re.match(pattern, email) is not None\n```", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 43, - "entry_tokens": 180, - "total_tokens": 255 - } - }, - "context": { - "app_id": "hooli", - "task_id": "code-generation", - "thread_id": null, - "created_at": "2025-05-12T16:12:51.313411+00:00" - } - }, - { - "id": "comp_9b77552d4a64", - "request": { - "prompt": "Explain photosynthesis to a middle school student", - "messages": [], - "tools": [], - "model": "llama-3.1-8b-instruct" - }, - "response": { - "text": "Photosynthesis is like a plant's way of making food using sunlight! Plants take in carbon dioxide from the air, water from their roots, and use sunlight energy to create glucose (sugar) and release oxygen.", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 49, - "entry_tokens": 71, - "total_tokens": 271 - } - }, - "context": { - "app_id": "acme-corp", - "task_id": "education", - "thread_id": null, - "created_at": "2025-05-20T16:08:51.313418+00:00" - } - }, - { - "id": "comp_7eec2e78d98f", - "request": { - "prompt": "Research trademark infringement laws", - "messages": [], - "tools": [], - "model": "llama-3.1-8b-instruct" - }, - "response": { - "text": "Trademark infringement occurs when a party uses a mark that is identical or confusingly similar to a registered trademark. Key factors include: likelihood of confusion, strength of the mark, and similarity of goods/services.", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 99, - "entry_tokens": 114, - "total_tokens": 220 - } - }, - "context": { - "app_id": "acme-corp", - "task_id": "legal-research", - "thread_id": null, - "created_at": "2025-05-13T20:22:51.313425+00:00" - } - }, - { - "id": "comp_a6fcb3a27b23", - "request": { - "prompt": "Create a REST API endpoint for user authentication", - "messages": [], - "tools": [], - "model": "llama-3.1-8b-instruct" - }, - "response": { - "text": "```python\nfrom flask import Flask, request, jsonify\nfrom werkzeug.security import check_password_hash\n\n@app.route('/auth', methods=['POST'])\ndef authenticate():\n data = request.get_json()\n # Authentication logic here\n return jsonify({'token': 'jwt_token_here'})\n```", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 27, - "entry_tokens": 114, - "total_tokens": 254 - } - }, - "context": { - "app_id": "initech", - "task_id": "code-generation", - "thread_id": null, - "created_at": "2025-05-14T04:06:51.313432+00:00" - } - }, - { - "id": "comp_4e8d20dc09cd", - "request": { - "prompt": "Write a blog post about sustainable living", - "messages": [], - "tools": [], - "model": "llama-3.1-8b-instruct" - }, - "response": { - "text": "# Sustainable Living: Small Changes, Big Impact\n\nIn today's world, making environmentally conscious choices has never been more important. Here are 10 simple ways to reduce your carbon footprint and contribute to a healthier planet...", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 56, - "entry_tokens": 57, - "total_tokens": 279 - } - }, - "context": { - "app_id": "pied-piper", - "task_id": "content-writing", - "thread_id": null, - "created_at": "2025-05-20T11:43:51.313439+00:00" - } - }, - { - "id": "comp_2ad77baef825", - "request": { - "prompt": "Analyze customer churn patterns in our dataset", - "messages": [], - "tools": [], - "model": "llama-3.1-8b-instruct" - }, - "response": { - "text": "Based on the analysis, customer churn is highest in months 3-6 after signup (28% churn rate). Key factors include lack of feature usage (45% correlation) and poor onboarding entry (62% of churned users didn't complete setup).", - "finish_reason": "stop", - "usage": { - "prompt_tokens": 64, - "entry_tokens": 173, - "total_tokens": 236 - } - }, - "context": { - "app_id": "acme-corp", - "task_id": "data-analysis", - "thread_id": null, - "created_at": "2025-05-11T13:52:51.313446+00:00" - } - } -] \ No newline at end of file diff --git a/services/intake/tests/integration/test_intake.py b/services/intake/tests/integration/test_intake.py index 7c61527eec..e3fb27536b 100644 --- a/services/intake/tests/integration/test_intake.py +++ b/services/intake/tests/integration/test_intake.py @@ -1,21 +1,9 @@ # SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. # SPDX-License-Identifier: Apache-2.0 -"""Integration tests for the intake service. +"""Integration tests for the Intake service route surface.""" -These tests verify: -- Entry CRUD endpoints functionality -- App CRUD endpoints functionality -- Task CRUD endpoints functionality -- Events sub-resource functionality -- API routes are properly registered in OpenAPI - -Uses the create_test_client pattern for fast in-memory testing. -""" - -import time -import uuid -from typing import Generator +from collections.abc import Generator import pytest from fastapi.testclient import TestClient @@ -23,9 +11,6 @@ from nmp.intake.service import IntakeService from nmp.testing.client import create_test_client -# Default workspace for tests -DEFAULT_WORKSPACE = "default" - @pytest.fixture(scope="module") def http_client() -> Generator[TestClient, None, None]: @@ -43,433 +28,43 @@ def sdk(http_client: TestClient) -> NeMoPlatform: return NeMoPlatform(base_url="http://testserver", http_client=http_client) -def generate_entry_data(app_name: str = "test-app", task_name: str = "test-task"): - """Generate test entry data matching the EntryInput schema.""" - return { - "data": { - "request": { - "messages": [{"role": "user", "content": "Hello, how are you?"}], - "model": "test-model", - }, - "response": { - "choices": [ - { - "message": {"role": "assistant", "content": "I'm doing well, thank you!"}, - "finish_reason": "stop", - } - ] - }, - }, - "context": { - "app": f"{DEFAULT_WORKSPACE}/{app_name}", - "task": task_name, - "thread_id": str(uuid.uuid4()), - "user_id": "test-user", - }, +def test_intake_openapi_keeps_span_era_routes(sdk: NeMoPlatform) -> None: + response = sdk._client.get("/openapi.json") + assert response.status_code == 200 + + paths = response.json().get("paths", {}) + + assert "/apis/intake/v2/workspaces/{workspace}/spans" in paths + assert "get" in paths["/apis/intake/v2/workspaces/{workspace}/spans"] + assert "/apis/intake/v2/workspaces/{workspace}/spans/{span_id}" in paths + assert "get" in paths["/apis/intake/v2/workspaces/{workspace}/spans/{span_id}"] + assert "/apis/intake/v2/workspaces/{workspace}/traces" in paths + assert "get" in paths["/apis/intake/v2/workspaces/{workspace}/traces"] + assert "/apis/intake/v2/workspaces/{workspace}/traces/{id}" in paths + assert "get" in paths["/apis/intake/v2/workspaces/{workspace}/traces/{id}"] + assert "/apis/intake/v2/workspaces/{workspace}/annotations" in paths + assert "/apis/intake/v2/workspaces/{workspace}/evaluator-results" in paths + assert "/apis/intake/v2/workspaces/{workspace}/ingest/otlp/v1/traces" in paths + + +def test_intake_openapi_removes_legacy_entry_app_task_and_export_routes(sdk: NeMoPlatform) -> None: + response = sdk._client.get("/openapi.json") + assert response.status_code == 200 + + paths = response.json().get("paths", {}) + + legacy_paths = { + "/apis/intake/v2/workspaces/{workspace}/apps", + "/apis/intake/v2/workspaces/{workspace}/apps/{name}", + "/apis/intake/v2/workspaces/{workspace}/apps/{name}/tasks", + "/apis/intake/v2/workspaces/{workspace}/apps/{app}/tasks/{name}", + "/apis/intake/v2/workspaces/{workspace}/entries", + "/apis/intake/v2/workspaces/{workspace}/entries/{name}", + "/apis/intake/v2/workspaces/{workspace}/entries/{name}/events", + "/apis/intake/v2/workspaces/{workspace}/entries/{entry}/events/{name}", + "/apis/intake/v2/workspaces/{workspace}/export/jobs", + "/apis/intake/v2/workspaces/{workspace}/export/jobs/{name}", + "/apis/intake/v2/workspaces/{workspace}/export/preview", } - -class TestIntakeOpenAPI: - """Tests for intake routes in OpenAPI spec.""" - - def test_entries_routes_in_openapi(self, sdk: NeMoPlatform): - """Test that entry endpoints are documented in OpenAPI spec.""" - response = sdk._client.get("/openapi.json") - assert response.status_code == 200 - - spec = response.json() - paths = spec.get("paths", {}) - - # Verify workspace-scoped entry endpoints are present - assert "/apis/intake/v2/workspaces/{workspace}/entries" in paths - assert "get" in paths["/apis/intake/v2/workspaces/{workspace}/entries"] - assert "post" in paths["/apis/intake/v2/workspaces/{workspace}/entries"] - - def test_apps_routes_in_openapi(self, sdk: NeMoPlatform): - """Test that app endpoints are documented in OpenAPI spec.""" - response = sdk._client.get("/openapi.json") - assert response.status_code == 200 - - spec = response.json() - paths = spec.get("paths", {}) - - # Verify workspace-scoped app endpoints are present - assert "/apis/intake/v2/workspaces/{workspace}/apps" in paths - assert "get" in paths["/apis/intake/v2/workspaces/{workspace}/apps"] - assert "post" in paths["/apis/intake/v2/workspaces/{workspace}/apps"] - - def test_tasks_routes_in_openapi(self, sdk: NeMoPlatform): - """Test that task endpoints are documented in OpenAPI spec.""" - response = sdk._client.get("/openapi.json") - assert response.status_code == 200 - - spec = response.json() - paths = spec.get("paths", {}) - - # Verify task endpoints are present (nested under apps, workspace-scoped) - assert "/apis/intake/v2/workspaces/{workspace}/apps/{name}/tasks" in paths - assert "post" in paths["/apis/intake/v2/workspaces/{workspace}/apps/{name}/tasks"] - assert "get" in paths["/apis/intake/v2/workspaces/{workspace}/apps/{name}/tasks"] - - def test_trace_routes_in_openapi(self, sdk: NeMoPlatform): - """Test that span trace endpoints are documented in OpenAPI spec.""" - response = sdk._client.get("/openapi.json") - assert response.status_code == 200 - - paths = response.json().get("paths", {}) - - assert "/apis/intake/v2/workspaces/{workspace}/sessions" not in paths - assert "/apis/intake/v2/workspaces/{workspace}/sessions/{session_id}/spans" not in paths - assert "/apis/intake/v2/workspaces/{workspace}/spans" in paths - assert "get" in paths["/apis/intake/v2/workspaces/{workspace}/spans"] - assert "/apis/intake/v2/workspaces/{workspace}/ingest/otlp/v1/traces" in paths - assert "post" in paths["/apis/intake/v2/workspaces/{workspace}/ingest/otlp/v1/traces"] - - -class TestIntakeEntries: - """Tests for the intake entry endpoints.""" - - def test_entry_crud_lifecycle(self, sdk: NeMoPlatform): - """Test full CRUD lifecycle for entries.""" - entry_data = generate_entry_data( - app_name=f"e2e-app-{uuid.uuid4().hex[:8]}", - task_name=f"e2e-task-{uuid.uuid4().hex[:8]}", - ) - - # CREATE - response = sdk._client.post(f"/apis/intake/v2/workspaces/{DEFAULT_WORKSPACE}/entries", json=entry_data) - assert response.status_code == 201, f"Create failed: {response.text}" - created = response.json() - assert "id" in created - entry_id = created["id"] - - # READ (single) - response = sdk._client.get(f"/apis/intake/v2/workspaces/{DEFAULT_WORKSPACE}/entries/{entry_id}") - assert response.status_code == 200, f"Get failed: {response.text}" - fetched = response.json() - assert fetched["id"] == entry_id - assert fetched["context"]["thread_id"] == entry_data["context"]["thread_id"] - - # LIST (workspace-scoped) - response = sdk._client.get(f"/apis/intake/v2/workspaces/{DEFAULT_WORKSPACE}/entries") - assert response.status_code == 200, f"List failed: {response.text}" - entries = response.json() - assert "data" in entries - assert any(e["id"] == entry_id for e in entries["data"]) - - # UPDATE - update user_rating since context has required fields - update_data = {"user_rating": {"opinion": "Updated via e2e test"}} - response = sdk._client.patch( - f"/apis/intake/v2/workspaces/{DEFAULT_WORKSPACE}/entries/{entry_id}", json=update_data - ) - assert response.status_code == 200, f"Update failed: {response.text}" - updated = response.json() - assert updated["user_rating"]["opinion"] == "Updated via e2e test" - - # DELETE - response = sdk._client.delete(f"/apis/intake/v2/workspaces/{DEFAULT_WORKSPACE}/entries/{entry_id}") - assert response.status_code == 204, f"Delete failed: {response.text}" - - # Verify deleted - response = sdk._client.get(f"/apis/intake/v2/workspaces/{DEFAULT_WORKSPACE}/entries/{entry_id}") - assert response.status_code == 404 - - def test_get_nonexistent_entry_returns_404(self, sdk: NeMoPlatform): - """Test that getting a non-existent entry returns 404.""" - response = sdk._client.get(f"/apis/intake/v2/workspaces/{DEFAULT_WORKSPACE}/entries/nonexistent-entry-id") - assert response.status_code == 404 - - def test_entry_list_pagination(self, sdk: NeMoPlatform): - """Test that entry listing supports pagination.""" - response = sdk._client.get(f"/apis/intake/v2/workspaces/{DEFAULT_WORKSPACE}/entries?page=1&page_size=5") - assert response.status_code == 200 - - data = response.json() - assert "data" in data - assert "pagination" in data - assert data["pagination"]["page"] == 1 - assert data["pagination"]["page_size"] == 5 - - -class TestIntakeEntryEvents: - """Tests for the intake entry events sub-resource.""" - - def test_add_events_to_entry(self, sdk: NeMoPlatform): - """Test adding events (feedback) to an entry.""" - # Create an entry first - entry_data = generate_entry_data( - app_name=f"e2e-app-{uuid.uuid4().hex[:8]}", - task_name=f"e2e-task-{uuid.uuid4().hex[:8]}", - ) - response = sdk._client.post(f"/apis/intake/v2/workspaces/{DEFAULT_WORKSPACE}/entries", json=entry_data) - assert response.status_code == 201 - entry_id = response.json()["id"] - - # Add thumbs up feedback event - events_data = {"events": [{"event_type": "user_feedback", "thumb": "up"}]} - - response = sdk._client.post( - f"/apis/intake/v2/workspaces/{DEFAULT_WORKSPACE}/entries/{entry_id}/events", json=events_data - ) - assert response.status_code == 200, f"Add events failed: {response.text}" - updated = response.json() - assert len(updated.get("events", [])) >= 1 - # user_rating should be updated from the feedback event - assert updated.get("user_rating", {}).get("thumb") == "up" - - # Cleanup - sdk._client.delete(f"/apis/intake/v2/workspaces/{DEFAULT_WORKSPACE}/entries/{entry_id}") - - def test_add_rating_event_to_entry(self, sdk: NeMoPlatform): - """Test adding a rating event to an entry.""" - # Create an entry first - entry_data = generate_entry_data( - app_name=f"e2e-app-{uuid.uuid4().hex[:8]}", - task_name=f"e2e-task-{uuid.uuid4().hex[:8]}", - ) - response = sdk._client.post(f"/apis/intake/v2/workspaces/{DEFAULT_WORKSPACE}/entries", json=entry_data) - assert response.status_code == 201 - entry_id = response.json()["id"] - - # Add rating feedback event - events_data = { - "events": [ - { - "event_type": "user_feedback", - "rating": 5, - "opinion": "Great response!", - } - ] - } - - response = sdk._client.post( - f"/apis/intake/v2/workspaces/{DEFAULT_WORKSPACE}/entries/{entry_id}/events", json=events_data - ) - assert response.status_code == 200, f"Add events failed: {response.text}" - updated = response.json() - assert updated.get("user_rating", {}).get("rating") == 5 - assert updated.get("user_rating", {}).get("opinion") == "Great response!" - - # Cleanup - sdk._client.delete(f"/apis/intake/v2/workspaces/{DEFAULT_WORKSPACE}/entries/{entry_id}") - - -class TestIntakeApps: - """Tests for the intake app entity endpoints.""" - - def test_app_crud_lifecycle(self, sdk: NeMoPlatform): - """Test full CRUD lifecycle for apps.""" - test_name = f"test-app-{uuid.uuid4().hex[:8]}" - - # CREATE - workspace comes from URL path, not body - app_data = { - "name": test_name, - "description": "E2E test app", - } - response = sdk._client.post(f"/apis/intake/v2/workspaces/{DEFAULT_WORKSPACE}/apps", json=app_data) - assert response.status_code == 201, f"Create failed: {response.text}" - created = response.json() - assert created["name"] == test_name - assert created["workspace"] == DEFAULT_WORKSPACE - - # READ (single) - response = sdk._client.get(f"/apis/intake/v2/workspaces/{DEFAULT_WORKSPACE}/apps/{test_name}") - assert response.status_code == 200, f"Get failed: {response.text}" - fetched = response.json() - assert fetched["name"] == created["name"] - assert fetched["description"] == "E2E test app" - - # LIST (workspace-scoped) - response = sdk._client.get(f"/apis/intake/v2/workspaces/{DEFAULT_WORKSPACE}/apps?page_size=100") - assert response.status_code == 200, f"List failed: {response.text}" - apps = response.json() - assert "data" in apps - assert any(a["name"] == test_name for a in apps["data"]) - - # UPDATE - update_data = {"description": "Updated description"} - response = sdk._client.patch( - f"/apis/intake/v2/workspaces/{DEFAULT_WORKSPACE}/apps/{test_name}", json=update_data - ) - assert response.status_code == 200, f"Update failed: {response.text}" - updated = response.json() - assert updated["description"] == "Updated description" - - # DELETE - response = sdk._client.delete(f"/apis/intake/v2/workspaces/{DEFAULT_WORKSPACE}/apps/{test_name}") - assert response.status_code == 204, f"Delete failed: {response.text}" - - # Verify deleted - response = sdk._client.get(f"/apis/intake/v2/workspaces/{DEFAULT_WORKSPACE}/apps/{test_name}") - assert response.status_code == 404 - - @pytest.mark.skip( - reason="TODO: Re-enable once entity store supports unique constraint on (workspace_id, entity_type, name)" - ) - def test_create_duplicate_app_fails(self, sdk: NeMoPlatform): - """Test that creating a duplicate app returns 409.""" - test_name = f"dup-app-{uuid.uuid4().hex[:8]}" - - app_data = { - "name": test_name, - } - - # Create first app - response = sdk._client.post(f"/apis/intake/v2/workspaces/{DEFAULT_WORKSPACE}/apps", json=app_data) - assert response.status_code == 201 - - # Try to create duplicate - response = sdk._client.post(f"/apis/intake/v2/workspaces/{DEFAULT_WORKSPACE}/apps", json=app_data) - assert response.status_code == 409 - - # Cleanup - sdk._client.delete(f"/apis/intake/v2/workspaces/{DEFAULT_WORKSPACE}/apps/{test_name}") - - def test_get_nonexistent_app_returns_404(self, sdk: NeMoPlatform): - """Test that getting a non-existent app returns 404.""" - response = sdk._client.get(f"/apis/intake/v2/workspaces/{DEFAULT_WORKSPACE}/apps/nonexistent-app") - assert response.status_code == 404 - - -class TestIntakeTasks: - """Tests for the intake task entity endpoints.""" - - def test_task_crud_lifecycle(self, sdk: NeMoPlatform): - """Test full CRUD lifecycle for tasks.""" - # First create an app for the task - app_name = f"test-app-{uuid.uuid4().hex[:8]}" - app_data = {"name": app_name} - response = sdk._client.post(f"/apis/intake/v2/workspaces/{DEFAULT_WORKSPACE}/apps", json=app_data) - assert response.status_code == 201 - - task_name = f"test-task-{uuid.uuid4().hex[:8]}" - - # CREATE - task_data = { - "name": task_name, - "description": "E2E test task", - } - response = sdk._client.post( - f"/apis/intake/v2/workspaces/{DEFAULT_WORKSPACE}/apps/{app_name}/tasks", - json=task_data, - ) - assert response.status_code == 201, f"Create failed: {response.text}" - created = response.json() - assert created["name"] == task_name - assert created["workspace"] == DEFAULT_WORKSPACE - - # READ (single) - response = sdk._client.get(f"/apis/intake/v2/workspaces/{DEFAULT_WORKSPACE}/apps/{app_name}/tasks/{task_name}") - assert response.status_code == 200, f"Get failed: {response.text}" - fetched = response.json() - assert fetched["name"] == created["name"] - assert fetched["description"] == "E2E test task" - - # LIST - response = sdk._client.get(f"/apis/intake/v2/workspaces/{DEFAULT_WORKSPACE}/apps/{app_name}/tasks") - assert response.status_code == 200, f"List failed: {response.text}" - tasks = response.json() - assert "data" in tasks - assert any(t["name"] == task_name for t in tasks["data"]) - - # UPDATE - update_data = {"description": "Updated task description"} - response = sdk._client.patch( - f"/apis/intake/v2/workspaces/{DEFAULT_WORKSPACE}/apps/{app_name}/tasks/{task_name}", - json=update_data, - ) - assert response.status_code == 200, f"Update failed: {response.text}" - updated = response.json() - assert updated["description"] == "Updated task description" - - # DELETE task - response = sdk._client.delete( - f"/apis/intake/v2/workspaces/{DEFAULT_WORKSPACE}/apps/{app_name}/tasks/{task_name}" - ) - assert response.status_code == 204, f"Delete task failed: {response.text}" - - # Verify task deleted - response = sdk._client.get(f"/apis/intake/v2/workspaces/{DEFAULT_WORKSPACE}/apps/{app_name}/tasks/{task_name}") - assert response.status_code == 404 - - # Cleanup - delete the app - sdk._client.delete(f"/apis/intake/v2/workspaces/{DEFAULT_WORKSPACE}/apps/{app_name}") - - @pytest.mark.skip( - reason="TODO: Re-enable once entity store supports unique constraint on (workspace_id, entity_type, name)" - ) - def test_create_duplicate_task_fails(self, sdk: NeMoPlatform): - """Test that creating a duplicate task returns 409.""" - # First create an app - app_name = f"test-app-{uuid.uuid4().hex[:8]}" - app_data = {"name": app_name} - response = sdk._client.post(f"/apis/intake/v2/workspaces/{DEFAULT_WORKSPACE}/apps", json=app_data) - assert response.status_code == 201 - - task_name = f"dup-task-{uuid.uuid4().hex[:8]}" - task_data = {"name": task_name} - - # Create first task - response = sdk._client.post( - f"/apis/intake/v2/workspaces/{DEFAULT_WORKSPACE}/apps/{app_name}/tasks", - json=task_data, - ) - assert response.status_code == 201 - - # Try to create duplicate - response = sdk._client.post( - f"/apis/intake/v2/workspaces/{DEFAULT_WORKSPACE}/apps/{app_name}/tasks", - json=task_data, - ) - assert response.status_code == 409 - - # Cleanup - sdk._client.delete(f"/apis/intake/v2/workspaces/{DEFAULT_WORKSPACE}/apps/{app_name}/tasks/{task_name}") - sdk._client.delete(f"/apis/intake/v2/workspaces/{DEFAULT_WORKSPACE}/apps/{app_name}") - - def test_get_nonexistent_task_returns_404(self, sdk: NeMoPlatform): - """Test that getting a non-existent task returns 404.""" - # First create an app - app_name = f"test-app-{uuid.uuid4().hex[:8]}" - app_data = {"name": app_name} - response = sdk._client.post(f"/apis/intake/v2/workspaces/{DEFAULT_WORKSPACE}/apps", json=app_data) - assert response.status_code == 201 - - response = sdk._client.get( - f"/apis/intake/v2/workspaces/{DEFAULT_WORKSPACE}/apps/{app_name}/tasks/nonexistent-task" - ) - assert response.status_code == 404 - - # Cleanup - sdk._client.delete(f"/apis/intake/v2/workspaces/{DEFAULT_WORKSPACE}/apps/{app_name}") - - -class TestIntakeAutoCreation: - """Tests for automatic app/task creation when creating entries.""" - - def test_entry_auto_creates_app_and_task(self, sdk: NeMoPlatform): - """Test that creating an entry auto-creates referenced app and task.""" - app_name = f"auto-app-{uuid.uuid4().hex[:8]}" - task_name = f"auto-task-{uuid.uuid4().hex[:8]}" - - # Create entry with new app/task references - entry_data = generate_entry_data(app_name=app_name, task_name=task_name) - response = sdk._client.post(f"/apis/intake/v2/workspaces/{DEFAULT_WORKSPACE}/entries", json=entry_data) - assert response.status_code == 201, f"Create entry failed: {response.text}" - entry_id = response.json()["id"] - - # Wait a moment for async auto-creation to complete - time.sleep(0.5) - - # Verify app was auto-created - response = sdk._client.get(f"/apis/intake/v2/workspaces/{DEFAULT_WORKSPACE}/apps/{app_name}") - assert response.status_code == 200, f"App was not auto-created: {response.text}" - - # Verify task was auto-created - response = sdk._client.get(f"/apis/intake/v2/workspaces/{DEFAULT_WORKSPACE}/apps/{app_name}/tasks/{task_name}") - assert response.status_code == 200, f"Task was not auto-created: {response.text}" - - # Cleanup - sdk._client.delete(f"/apis/intake/v2/workspaces/{DEFAULT_WORKSPACE}/entries/{entry_id}") - sdk._client.delete(f"/apis/intake/v2/workspaces/{DEFAULT_WORKSPACE}/apps/{app_name}/tasks/{task_name}") - sdk._client.delete(f"/apis/intake/v2/workspaces/{DEFAULT_WORKSPACE}/apps/{app_name}") + assert legacy_paths.isdisjoint(paths) diff --git a/services/intake/tests/test_apps.py b/services/intake/tests/test_apps.py deleted file mode 100644 index 10c59ce51b..0000000000 --- a/services/intake/tests/test_apps.py +++ /dev/null @@ -1,107 +0,0 @@ -# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. -# SPDX-License-Identifier: Apache-2.0 - -"""Tests for Apps API endpoints.""" - -import pytest -from fastapi.testclient import TestClient - - -class TestAppsAPI: - """Tests for Apps API endpoints.""" - - def test_create_app(self, client: TestClient): - """Test creating a new app.""" - response = client.post( - "/apis/intake/v2/workspaces/default/apps", - json={"name": "test-app", "description": "Test application"}, - ) - assert response.status_code == 201 - data = response.json() - assert data["name"] == "test-app" - assert data["workspace"] == "default" - assert data["description"] == "Test application" - assert "created_at" in data - assert "updated_at" in data - - def test_get_app(self, client: TestClient): - """Test getting an app by workspace_id and app_id.""" - # Create app first - client.post( - "/apis/intake/v2/workspaces/default/apps", - json={"name": "test-app", "description": "Test application"}, - ) - - # Get the app - workspace_id is "default", app_id is "test-app" - response = client.get("/apis/intake/v2/workspaces/default/apps/test-app") - assert response.status_code == 200 - data = response.json() - assert data["name"] == "test-app" - assert data["workspace"] == "default" - - def test_list_apps(self, client: TestClient): - """Test listing apps with pagination.""" - # Create multiple apps - for i in range(3): - client.post( - "/apis/intake/v2/workspaces/default/apps", - json={"name": f"app-{i}", "description": f"App {i}"}, - ) - - # List apps - response = client.get("/apis/intake/v2/workspaces/default/apps?page=1&page_size=10") - assert response.status_code == 200 - data = response.json() - assert "data" in data - assert "pagination" in data - assert len(data["data"]) == 3 - assert data["pagination"]["total_results"] == 3 - - def test_update_app(self, client: TestClient): - """Test updating an app.""" - # Create app - client.post( - "/apis/intake/v2/workspaces/default/apps", - json={"name": "test-app", "description": "Original description"}, - ) - - # Update app - response = client.patch( - "/apis/intake/v2/workspaces/default/apps/test-app", json={"description": "Updated description"} - ) - assert response.status_code == 200 - data = response.json() - assert data["description"] == "Updated description" - - def test_delete_app(self, client: TestClient): - """Test deleting an app.""" - # Create app - client.post( - "/apis/intake/v2/workspaces/default/apps", - json={"name": "test-app", "description": "Test application"}, - ) - - # Delete app - response = client.delete("/apis/intake/v2/workspaces/default/apps/test-app") - assert response.status_code == 204 - - # Verify it's deleted - response = client.get("/apis/intake/v2/workspaces/default/apps/test-app") - assert response.status_code == 404 - - # TODO(v2): Reactivate when Entities Service supports unique constraints - @pytest.mark.skip(reason="Entities Service does not yet enforce unique constraints") - def test_create_duplicate_app(self, client: TestClient): - """Test that creating duplicate app fails.""" - # Create app - client.post( - "/apis/intake/v2/workspaces/default/apps", - json={"name": "test-app", "description": "Test application"}, - ) - - # Try to create duplicate - response = client.post( - "/apis/intake/v2/workspaces/default/apps", - json={"name": "test-app", "description": "Duplicate"}, - ) - assert response.status_code == 409 diff --git a/services/intake/tests/test_entries.py b/services/intake/tests/test_entries.py deleted file mode 100644 index fe8c374dd8..0000000000 --- a/services/intake/tests/test_entries.py +++ /dev/null @@ -1,851 +0,0 @@ -# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. -# SPDX-License-Identifier: Apache-2.0 - -"""Tests for Entries API endpoints.""" - -import time -from datetime import datetime - -import pytest -from fastapi.testclient import TestClient - - -def wait_for_status( - client: TestClient, url: str, expected_status: int = 200, max_attempts: int = 20, delay: float = 0.05 -): - """Poll a URL until it returns the expected status code. - - Used for endpoints that depend on fire-and-forget background tasks completing. - """ - response = client.get(url) - for _ in range(max_attempts): - if response.status_code == expected_status: - return response - time.sleep(delay) - response = client.get(url) - return response # Return last response for assertion error messages - - -class TestEntriesAPI: - """Tests for Entries API endpoints.""" - - @pytest.fixture(autouse=True) - def create_test_app_and_task(self, client: TestClient): - """Create test app and task before each test.""" - client.post( - "/apis/intake/v2/workspaces/default/apps", - json={"name": "test-app", "description": "Test application"}, - ) - client.post( - "/apis/intake/v2/workspaces/default/apps/test-app/tasks", - json={"name": "chat", "description": "Chat task"}, - ) - - def test_create_entry(self, client: TestClient): - """Test creating an entry.""" - response = client.post( - "/apis/intake/v2/workspaces/default/entries", - json={ - "external_id": "chatcmpl-test123", - "data": { - "request": {"model": "gpt-4", "messages": [{"role": "user", "content": "What is 2+2?"}]}, - "response": {"choices": [{"message": {"role": "assistant", "content": "4"}}]}, - }, - "context": {"app": "default/test-app", "task": "chat", "thread_id": "thread_123"}, - }, - ) - assert response.status_code == 201 - data = response.json() - assert "id" in data - assert data["id"] is not None - assert data["external_id"] == "chatcmpl-test123" - assert data["workspace"] == "default" - assert data["data"]["request"]["model"] == "gpt-4" - assert data["context"]["app"] == "default/test-app" - assert data["context"]["task"] == "chat" - - def test_create_entry_without_external_id(self, client: TestClient): - """Test creating an entry without external_id - should still return an id.""" - response = client.post( - "/apis/intake/v2/workspaces/default/entries", - json={ - "data": { - "request": {"model": "gpt-4", "messages": [{"role": "user", "content": "What is 2+2?"}]}, - "response": {"choices": [{"message": {"role": "assistant", "content": "4"}}]}, - }, - "context": {"app": "default/test-app", "task": "chat", "thread_id": "thread_123"}, - }, - ) - assert response.status_code == 201 - data = response.json() - assert "id" in data - assert data["id"] is not None - assert data["id"].startswith("intake-entry-") # ID format: {entity_type}-{uuid} - assert data["external_id"] is None # external_id should be None but still present - assert data["workspace"] == "default" - - # Verify we can retrieve it by the generated ID - entry_id = data["id"] - get_response = client.get(f"/apis/intake/v2/workspaces/default/entries/{entry_id}") - assert get_response.status_code == 200 - get_data = get_response.json() - assert get_data["id"] == entry_id - - def test_get_entry_by_external_id(self, client: TestClient): - """Test getting entry using external: prefix.""" - # Create entry - client.post( - "/apis/intake/v2/workspaces/default/entries", - json={ - "external_id": "chatcmpl-test456", - "data": {"request": {"model": "gpt-4", "messages": []}, "response": {"choices": []}}, - "context": {"app": "default/test-app", "task": "chat", "thread_id": "thread_123"}, - }, - ) - - # Get by external_id using external: prefix - response = client.get("/apis/intake/v2/workspaces/default/entries/external:chatcmpl-test456") - assert response.status_code == 200 - data = response.json() - assert data["external_id"] == "chatcmpl-test456" - - def test_list_entries(self, client: TestClient): - """Test listing entries with pagination.""" - # Create multiple entries - for i in range(3): - client.post( - "/apis/intake/v2/workspaces/default/entries", - json={ - "external_id": f"chatcmpl-{i}", - "data": {"request": {"model": "gpt-4", "messages": []}, "response": {"choices": []}}, - "context": {"app": "default/test-app", "task": "chat", "thread_id": f"thread_{i}"}, - }, - ) - - # List entries - response = client.get("/apis/intake/v2/workspaces/default/entries?page=1&page_size=10") - assert response.status_code == 200 - data = response.json() - assert len(data["data"]) == 3 - assert data["pagination"]["total_results"] == 3 - - # Verify each entry has id and external_id fields (even if external_id is None) - for entry in data["data"]: - assert "id" in entry - assert entry["id"] is not None - assert "external_id" in entry # Should be present even if None - - def test_update_entry_by_external_id(self, client: TestClient): - """Test updating entry using external: prefix.""" - # Create entry - client.post( - "/apis/intake/v2/workspaces/default/entries", - json={ - "external_id": "chatcmpl-update", - "data": {"request": {"model": "gpt-4", "messages": []}, "response": {"choices": []}}, - "context": {"app": "default/test-app", "task": "chat", "thread_id": "thread_123"}, - }, - ) - - # Update using external_id - response = client.patch( - "/apis/intake/v2/workspaces/default/entries/external:chatcmpl-update", json={"user_rating": {"thumb": "up"}} - ) - assert response.status_code == 200 - data = response.json() - assert data["user_rating"]["thumb"] == "up" - - def test_delete_entry(self, client: TestClient): - """Test deleting an entry.""" - # Create entry - response = client.post( - "/apis/intake/v2/workspaces/default/entries", - json={ - "external_id": "chatcmpl-delete", - "data": {"request": {"model": "gpt-4", "messages": []}, "response": {"choices": []}}, - "context": {"app": "default/test-app", "task": "chat", "thread_id": "thread_123"}, - }, - ) - - # Delete entry using external_id - response = client.delete("/apis/intake/v2/workspaces/default/entries/external:chatcmpl-delete") - assert response.status_code == 204 - - # Verify it's deleted - response = client.get("/apis/intake/v2/workspaces/default/entries/external:chatcmpl-delete") - assert response.status_code == 404 - - def test_add_events_to_entry(self, client: TestClient): - """Test adding events to an entry.""" - # Create entry - client.post( - "/apis/intake/v2/workspaces/default/entries", - json={ - "external_id": "chatcmpl-events", - "data": {"request": {"model": "gpt-4", "messages": []}, "response": {"choices": []}}, - "context": {"app": "default/test-app", "task": "chat", "thread_id": "thread_123"}, - }, - ) - - # Add events - response = client.post( - "/apis/intake/v2/workspaces/default/entries/external:chatcmpl-events/events", - json={"events": [{"event_type": "user_feedback", "thumb": "up"}]}, - ) - assert response.status_code == 200 - data = response.json() - assert len(data["events"]) == 1 - assert data["events"][0]["event_type"] == "user_feedback" - - def test_filter_entries_by_workspace(self, client: TestClient): - """Test filtering entries by workspace using workspace-scoped endpoint.""" - # Create entry with full data structure like other tests - create_response = client.post( - "/apis/intake/v2/workspaces/default/entries", - json={ - "external_id": "chatcmpl-ns1", - "data": {"request": {"model": "gpt-4", "messages": []}, "response": {"choices": []}}, - "context": {"app": "default/test-app", "task": "chat", "thread_id": "thread_1"}, - }, - ) - assert create_response.status_code == 201, f"Failed to create entry: {create_response.json()}" - - # List all entries first - list_response = client.get("/apis/intake/v2/workspaces/default/entries") - assert list_response.status_code == 200 - all_data = list_response.json() - - # Filter by workspace via workspace-scoped endpoint - # The workspace in URL path acts as workspace filter - response = client.get("/apis/intake/v2/workspaces/default/entries") - assert response.status_code == 200 - data = response.json() - # Should have at least the entry we just created - assert len(data["data"]) >= 1, ( - f"Expected at least 1 entry, got {len(data['data'])}. All entries: {len(all_data['data'])}" - ) - for entry in data["data"]: - assert entry["workspace"] == "default" - - def test_path_workspace_overrides_workspace_filter(self, client: TestClient): - """Test that the route workspace is authoritative over query filters.""" - create_response = client.post( - "/apis/intake/v2/workspaces/default/entries", - json={ - "external_id": "chatcmpl-path-workspace", - "data": {"request": {"model": "gpt-4", "messages": []}, "response": {"choices": []}}, - "context": {"app": "default/test-app", "task": "chat", "thread_id": "thread_path_workspace"}, - }, - ) - assert create_response.status_code == 201 - entry_id = create_response.json()["id"] - - response = client.get("/apis/intake/v2/workspaces/default/entries?filter[workspace]=other") - assert response.status_code == 200 - data = response.json() - - assert any(entry["id"] == entry_id for entry in data["data"]) - for entry in data["data"]: - assert entry["workspace"] == "default" - - def test_filter_entries_by_id(self, client: TestClient): - """Test filtering entries by a single ID.""" - # Create entry - create_response = client.post( - "/apis/intake/v2/workspaces/default/entries", - json={ - "external_id": "chatcmpl-id-filter", - "data": {"request": {"model": "gpt-4", "messages": []}, "response": {"choices": []}}, - "context": {"app": "default/test-app", "task": "chat", "thread_id": "thread_id_filter"}, - }, - ) - assert create_response.status_code == 201 - entry_id = create_response.json()["id"] - - # Filter by ID - response = client.get(f"/apis/intake/v2/workspaces/default/entries?filter[id]={entry_id}") - assert response.status_code == 200 - data = response.json() - assert len(data["data"]) == 1 - assert data["data"][0]["id"] == entry_id - - # TODO(v2): Investigate 'in' operator filter conversion for EntityClient - @pytest.mark.skip(reason="Complex filter operators need EntityClient filter conversion support") - def test_filter_entries_by_multiple_ids(self, client: TestClient): - """Test filtering entries by multiple IDs using the 'in' operator.""" - # Create multiple entries - entry_ids = [] - for i in range(3): - create_response = client.post( - "/apis/intake/v2/workspaces/default/entries", - json={ - "external_id": f"chatcmpl-multi-{i}", - "data": {"request": {"model": "gpt-4", "messages": []}, "response": {"choices": []}}, - "context": {"app": "default/test-app", "task": "chat", "thread_id": f"thread_multi_{i}"}, - }, - ) - assert create_response.status_code == 201 - entry_ids.append(create_response.json()["id"]) - - # Filter by first two IDs using 'in' operator with proper JSON syntax - import json - - ids_json = json.dumps([entry_ids[0], entry_ids[1]]) - response = client.get(f"/apis/intake/v2/workspaces/default/entries?filter[id][in]={ids_json}") - assert response.status_code == 200 - data = response.json() - assert len(data["data"]) == 2 - returned_ids = [entry["id"] for entry in data["data"]] - assert entry_ids[0] in returned_ids - assert entry_ids[1] in returned_ids - assert entry_ids[2] not in returned_ids - - def test_filter_entries_by_id_with_invalid_json(self, client: TestClient): - """Test that invalid JSON in filter gives helpful error message.""" - # Try to filter with invalid JSON syntax (missing quotes) - response = client.get("/apis/intake/v2/workspaces/default/entries?filter[id][in]=[entry-ABC,entry-XYZ]") - assert response.status_code == 400 - error_data = response.json() - assert "Invalid filter value" in error_data["detail"] - assert "valid JSON with proper quoting" in error_data["detail"] - assert '["item1","item2"]' in error_data["detail"] - - def test_filter_entries_by_external_id(self, client: TestClient): - """Test filtering entries by a single external_id.""" - # Create entry - create_response = client.post( - "/apis/intake/v2/workspaces/default/entries", - json={ - "external_id": "chatcmpl-ext-filter", - "data": {"request": {"model": "gpt-4", "messages": []}, "response": {"choices": []}}, - "context": {"app": "default/test-app", "task": "chat", "thread_id": "thread_ext_filter"}, - }, - ) - assert create_response.status_code == 201 - external_id = create_response.json()["external_id"] - - # Filter by external_id - response = client.get(f"/apis/intake/v2/workspaces/default/entries?filter[external_id]={external_id}") - assert response.status_code == 200 - data = response.json() - assert len(data["data"]) == 1 - assert data["data"][0]["external_id"] == external_id - - # TODO(v2): Investigate 'in' operator filter conversion for EntityClient - @pytest.mark.skip(reason="Complex filter operators need EntityClient filter conversion support") - def test_filter_entries_by_multiple_external_ids(self, client: TestClient): - """Test filtering entries by multiple external_ids using the 'in' operator.""" - # Create multiple entries with external_ids - external_ids = [] - for i in range(3): - create_response = client.post( - "/apis/intake/v2/workspaces/default/entries", - json={ - "external_id": f"chatcmpl-ext-multi-{i}", - "data": {"request": {"model": "gpt-4", "messages": []}, "response": {"choices": []}}, - "context": {"app": "default/test-app", "task": "chat", "thread_id": f"thread_ext_multi_{i}"}, - }, - ) - assert create_response.status_code == 201 - external_ids.append(create_response.json()["external_id"]) - - # Filter by first two external_ids using 'in' operator with proper JSON syntax - import json - - ext_ids_json = json.dumps([external_ids[0], external_ids[1]]) - response = client.get(f"/apis/intake/v2/workspaces/default/entries?filter[external_id][in]={ext_ids_json}") - assert response.status_code == 200 - data = response.json() - assert len(data["data"]) == 2 - returned_ext_ids = [entry["external_id"] for entry in data["data"]] - assert external_ids[0] in returned_ext_ids - assert external_ids[1] in returned_ext_ids - assert external_ids[2] not in returned_ext_ids - - def test_filter_entries_by_external_id_with_invalid_json(self, client: TestClient): - """Test that invalid JSON in external_id filter gives helpful error message.""" - # Try to filter with invalid JSON syntax (missing quotes) - the actual issue from the user - response = client.get( - "/apis/intake/v2/workspaces/default/entries?filter[external_id][in]=[entry-CTXhK1QzYR7E62mwoxqGp9]" - ) - assert response.status_code == 400 - error_data = response.json() - assert "Invalid filter value" in error_data["detail"] - assert "valid JSON with proper quoting" in error_data["detail"] - - def test_auto_registration_of_app_and_task(self, client: TestClient): - """Test that apps and tasks are auto-created when entry is created.""" - # Create entry without pre-creating app and task - response = client.post( - "/apis/intake/v2/workspaces/default/entries", - json={ - "external_id": "chatcmpl-autoreg", - "data": {"request": {"model": "gpt-4", "messages": []}, "response": {"choices": []}}, - "context": {"app": "default/auto-app", "task": "auto-task", "thread_id": "thread_auto"}, - }, - ) - assert response.status_code == 201 - - # Verify app was auto-created (poll because auto-registration is fire-and-forget) - app_response = wait_for_status(client, "/apis/intake/v2/workspaces/default/apps/auto-app") - assert app_response.status_code == 200 - app_data = app_response.json() - assert app_data["name"] == "auto-app" - assert "Auto-registered" in app_data["description"] - - # Verify task was auto-created - task_response = wait_for_status(client, "/apis/intake/v2/workspaces/default/apps/auto-app/tasks/auto-task") - assert task_response.status_code == 200 - task_data = task_response.json() - assert task_data["name"] == "auto-task" - assert "Auto-registered" in task_data["description"] - - def test_auto_registration_without_workspace_prefix(self, client: TestClient): - """Test that apps and tasks auto-created without workspace prefix can be updated.""" - # Create entry with app name WITHOUT workspace prefix (just "my-app" not "default/my-app") - response = client.post( - "/apis/intake/v2/workspaces/default/entries", - json={ - "external_id": "chatcmpl-no-ns", - "data": {"request": {"model": "gpt-4", "messages": []}, "response": {"choices": []}}, - "context": {"app": "no-ns-app", "task": "no-ns-task", "thread_id": "thread_no_ns"}, - }, - ) - assert response.status_code == 201 - - # Verify app was auto-created (poll because auto-registration is fire-and-forget) - app_response = wait_for_status(client, "/apis/intake/v2/workspaces/default/apps/no-ns-app") - assert app_response.status_code == 200 - - # Verify task was auto-created and can be retrieved - task_response = wait_for_status(client, "/apis/intake/v2/workspaces/default/apps/no-ns-app/tasks/no-ns-task") - assert task_response.status_code == 200 - - # Verify task can be updated (this was the bug) - update_response = client.patch( - "/apis/intake/v2/workspaces/default/apps/no-ns-app/tasks/no-ns-task", - json={"description": "This task was updated successfully"}, - ) - assert update_response.status_code == 200 - updated_task = update_response.json() - assert updated_task["description"] == "This task was updated successfully" - - # TODO(v2): Investigate longest_per_thread filter aggregation with EntityClient - @pytest.mark.skip(reason="Thread aggregation filter needs EntityClient support investigation") - def test_longest_per_thread_filter(self, client: TestClient): - """Test filtering to get only longest entry per thread.""" - # Create multiple entries in the same thread with different message counts - for i in range(3): - messages = [{"role": "user", "content": f"msg{j}"} for j in range(i + 1)] - client.post( - "/apis/intake/v2/workspaces/default/entries", - json={ - "external_id": f"chatcmpl-thread-{i}", - "data": {"request": {"model": "gpt-4", "messages": messages}, "response": {"choices": []}}, - "context": {"app": "default/test-app", "task": "chat", "thread_id": "thread_longest"}, - }, - ) - - # Get all entries first (should have 3 from this thread) - all_response = client.get("/apis/intake/v2/workspaces/default/entries") - assert all_response.status_code == 200 - all_response.json() - - # Get longest per thread - filtered_response = client.get("/apis/intake/v2/workspaces/default/entries?filter[longest_per_thread]=true") - assert filtered_response.status_code == 200 - filtered_data = filtered_response.json() - - # Should have only 1 entry from this thread (the longest) - # Count entries with thread_longest - longest_entries = [ - e for e in filtered_data["data"] if e.get("context", {}).get("thread_id") == "thread_longest" - ] - assert len(longest_entries) == 1 - # Should be the one with 3 messages (i=2) - longest_entry = longest_entries[0] - assert longest_entry["external_id"] == "chatcmpl-thread-2" - - def test_create_entry_with_user_id(self, client: TestClient): - """Test creating an entry with user_id.""" - response = client.post( - "/apis/intake/v2/workspaces/default/entries", - json={ - "external_id": "chatcmpl-user-test", - "data": { - "request": {"model": "gpt-4", "messages": [{"role": "user", "content": "Hello"}]}, - "response": {"choices": [{"message": {"role": "assistant", "content": "Hi there"}}]}, - }, - "context": { - "app": "default/test-app", - "task": "chat", - "thread_id": "thread_user_1", - "user_id": "user_12345", - }, - }, - ) - assert response.status_code == 201 - data = response.json() - assert data["context"]["user_id"] == "user_12345" - - @pytest.mark.skip( - reason="Nested JSON filtering requires PostgreSQL JSONB support; SQLite tests use JSON which lacks this functionality" - ) - def test_filter_entries_by_user_id(self, client: TestClient): - """Test filtering entries by user_id.""" - # Create entries for different users - for user_num in [1, 2]: - client.post( - "/apis/intake/v2/workspaces/default/entries", - json={ - "external_id": f"chatcmpl-user-{user_num}", - "data": {"request": {"model": "gpt-4", "messages": []}, "response": {"choices": []}}, - "context": { - "app": "default/test-app", - "task": "chat", - "thread_id": f"thread_{user_num}", - "user_id": f"user_{user_num}", - }, - }, - ) - - # Filter by user_id - response = client.get("/apis/intake/v2/workspaces/default/entries?filter[context][user_id]=user_1") - assert response.status_code == 200 - data = response.json() - assert data["total"] >= 1 - - # Verify all returned entries have the correct user_id - for entry in data["items"]: - assert entry["context"]["user_id"] == "user_1" - - @pytest.mark.skip( - reason="Nested JSON filtering requires PostgreSQL JSONB support; SQLite tests use JSON which lacks this functionality" - ) - def test_filter_entries_by_nonexistent_user_id(self, client: TestClient): - """Test filtering entries by non-existent user_id returns empty results.""" - # Create an entry with a user_id - client.post( - "/apis/intake/v2/workspaces/default/entries", - json={ - "external_id": "chatcmpl-existing-user", - "data": {"request": {"model": "gpt-4", "messages": []}, "response": {"choices": []}}, - "context": { - "app": "default/test-app", - "task": "chat", - "thread_id": "thread_1", - "user_id": "existing_user", - }, - }, - ) - - # Filter by non-existent user_id should return 200 with empty results - response = client.get( - "/apis/intake/v2/workspaces/default/entries?filter[context][user_id]=nonexistent_user_999" - ) - assert response.status_code == 200 - data = response.json() - assert data["total"] == 0 - assert len(data["items"]) == 0 - - def test_create_entry_with_custom_fields(self, client: TestClient): - """Custom fields round-trip through POST and GET.""" - custom = {"experiment": {"id": "job-abc", "model": "gpt-4", "num_attempts": 3}} - response = client.post( - "/apis/intake/v2/workspaces/default/entries", - json={ - "external_id": "chatcmpl-cf", - "data": {"request": {"model": "gpt-4", "messages": []}, "response": {"choices": []}}, - "context": {"app": "default/test-app", "task": "chat"}, - "custom_fields": custom, - }, - ) - assert response.status_code == 201 - assert response.json()["custom_fields"] == custom - - get_response = client.get("/apis/intake/v2/workspaces/default/entries/external:chatcmpl-cf") - assert get_response.status_code == 200 - assert get_response.json()["custom_fields"] == custom - - def test_update_entry_custom_fields(self, client: TestClient): - """PATCH replaces custom_fields with the provided value.""" - client.post( - "/apis/intake/v2/workspaces/default/entries", - json={ - "external_id": "chatcmpl-cf-patch", - "data": {"request": {"model": "gpt-4", "messages": []}, "response": {"choices": []}}, - "context": {"app": "default/test-app", "task": "chat"}, - "custom_fields": {"experiment": {"id": "job-1"}}, - }, - ) - - new_custom = {"experiment": {"id": "job-1", "num_errors": 0, "num_trials": 5}} - patch_response = client.patch( - "/apis/intake/v2/workspaces/default/entries/external:chatcmpl-cf-patch", - json={"custom_fields": new_custom}, - ) - assert patch_response.status_code == 200 - assert patch_response.json()["custom_fields"] == new_custom - - def test_create_entry_with_session_id(self, client: TestClient): - """Entries accept and round-trip context.session_id.""" - response = client.post( - "/apis/intake/v2/workspaces/default/entries", - json={ - "external_id": "chatcmpl-session", - "data": {"request": {"model": "gpt-4", "messages": []}, "response": {"choices": []}}, - "context": { - "app": "default/test-app", - "task": "chat", - "session_id": "harbor-session-xyz", - }, - }, - ) - assert response.status_code == 201 - assert response.json()["context"]["session_id"] == "harbor-session-xyz" - - @pytest.mark.skip( - reason="Nested JSON filtering requires PostgreSQL JSONB support; SQLite tests use JSON which lacks this functionality" - ) - def test_filter_entries_by_session_id(self, client: TestClient): - """Filter entries by session_id (matches the Harbor exporter lookup pattern).""" - for i in range(2): - client.post( - "/apis/intake/v2/workspaces/default/entries", - json={ - "external_id": f"chatcmpl-session-filter-{i}", - "data": {"request": {"model": "gpt-4", "messages": []}, "response": {"choices": []}}, - "context": { - "app": "default/test-app", - "task": "chat", - "session_id": "harbor-session-A", - }, - }, - ) - - response = client.get("/apis/intake/v2/workspaces/default/entries?filter[context][session_id]=harbor-session-A") - assert response.status_code == 200 - data = response.json() - assert len(data["data"]) >= 2 - for entry in data["data"]: - assert entry["context"]["session_id"] == "harbor-session-A" - - def test_add_evaluator_result_event_to_entry(self, client: TestClient): - """Evaluator-result events are accepted, stored, and not synced into user_rating.""" - client.post( - "/apis/intake/v2/workspaces/default/entries", - json={ - "external_id": "chatcmpl-eval", - "data": {"request": {"model": "gpt-4", "messages": []}, "response": {"choices": []}}, - "context": {"app": "default/test-app", "task": "chat"}, - }, - ) - - response = client.post( - "/apis/intake/v2/workspaces/default/entries/external:chatcmpl-eval/events", - json={ - "events": [ - { - "event_type": "evaluator_result", - "name": "harbor.verifier", - "score": 0.875, - "metadata": {"trial_name": "trial-1"}, - } - ] - }, - ) - assert response.status_code == 200 - data = response.json() - eval_events = [e for e in data["events"] if e["event_type"] == "evaluator_result"] - assert len(eval_events) == 1 - assert eval_events[0]["name"] == "harbor.verifier" - assert eval_events[0]["score"] == 0.875 - assert eval_events[0]["metadata"] == {"trial_name": "trial-1"} - # Evaluator results must not bleed into user_rating (that's user_feedback's job). - assert data.get("user_rating") in (None, {}) or all( - data["user_rating"].get(k) is None for k in ("thumb", "rating", "opinion") - ) - - def test_add_evaluator_result_event_with_string_score(self, client: TestClient): - """Evaluator-result score accepts a string label (e.g., 'pass'/'fail').""" - client.post( - "/apis/intake/v2/workspaces/default/entries", - json={ - "external_id": "chatcmpl-eval-str", - "data": {"request": {"model": "gpt-4", "messages": []}, "response": {"choices": []}}, - "context": {"app": "default/test-app", "task": "chat"}, - }, - ) - - response = client.post( - "/apis/intake/v2/workspaces/default/entries/external:chatcmpl-eval-str/events", - json={ - "events": [ - { - "event_type": "evaluator_result", - "name": "auditor.pii_probe", - "score": "pass", - } - ] - }, - ) - assert response.status_code == 200 - eval_events = [e for e in response.json()["events"] if e["event_type"] == "evaluator_result"] - assert eval_events[0]["score"] == "pass" - - def test_create_entry_with_full_usage(self, client: TestClient): - """POST with a fully-populated usage block round-trips through GET.""" - usage_in = { - "model": "gpt-4o", - "started_at": "2026-04-30T15:00:00Z", - "ended_at": "2026-04-30T15:00:01.840000Z", - "latency_ms": 1840, - "cost_usd": 0.0034, - "cost_input_usd": 0.0023, - "cost_output_usd": 0.0011, - "input_tokens": 120, - "output_tokens": 35, - "cached_tokens": 64, - } - response = client.post( - "/apis/intake/v2/workspaces/default/entries", - json={ - "external_id": "chatcmpl-usage-full", - "data": { - "request": {"model": "gpt-4o", "messages": [{"role": "user", "content": "hi"}]}, - "response": {"choices": [{"message": {"role": "assistant", "content": "hello"}}]}, - }, - "usage": usage_in, - "context": {"app": "default/test-app", "task": "chat"}, - }, - ) - assert response.status_code == 201, response.json() - body = response.json() - for key in ( - "model", - "latency_ms", - "cost_usd", - "cost_input_usd", - "cost_output_usd", - "input_tokens", - "output_tokens", - "cached_tokens", - ): - assert body["usage"][key] == usage_in[key] - # Datetimes round-trip as ISO strings — exact format may vary, so parse and compare. - assert datetime.fromisoformat(body["usage"]["started_at"].replace("Z", "+00:00")) == datetime.fromisoformat( - usage_in["started_at"].replace("Z", "+00:00") - ) - assert datetime.fromisoformat(body["usage"]["ended_at"].replace("Z", "+00:00")) == datetime.fromisoformat( - usage_in["ended_at"].replace("Z", "+00:00") - ) - - get_response = client.get("/apis/intake/v2/workspaces/default/entries/external:chatcmpl-usage-full") - assert get_response.status_code == 200 - assert get_response.json()["usage"]["model"] == usage_in["model"] - assert get_response.json()["usage"]["cost_usd"] == usage_in["cost_usd"] - - def test_create_entry_with_partial_usage(self, client: TestClient): - """Usage fields are individually optional — partial blocks are accepted.""" - response = client.post( - "/apis/intake/v2/workspaces/default/entries", - json={ - "external_id": "chatcmpl-usage-partial", - "data": { - "request": {"model": "gpt-4o", "messages": []}, - "response": {"choices": []}, - }, - "usage": {"model": "gpt-4o", "cost_usd": 0.0012}, - "context": {"app": "default/test-app", "task": "chat"}, - }, - ) - assert response.status_code == 201, response.json() - usage = response.json()["usage"] - assert usage["model"] == "gpt-4o" - assert usage["cost_usd"] == 0.0012 - assert usage["latency_ms"] is None - assert usage["input_tokens"] is None - - def test_create_entry_without_usage(self, client: TestClient): - """Usage is optional — entries without it serialize with usage == None.""" - response = client.post( - "/apis/intake/v2/workspaces/default/entries", - json={ - "external_id": "chatcmpl-usage-absent", - "data": { - "request": {"model": "gpt-4o", "messages": []}, - "response": {"choices": []}, - }, - "context": {"app": "default/test-app", "task": "chat"}, - }, - ) - assert response.status_code == 201, response.json() - assert response.json()["usage"] is None - - def test_usage_rejects_negative_values(self, client: TestClient): - """Latency/cost/token fields are constrained ≥ 0.""" - response = client.post( - "/apis/intake/v2/workspaces/default/entries", - json={ - "external_id": "chatcmpl-usage-bad", - "data": { - "request": {"model": "gpt-4o", "messages": []}, - "response": {"choices": []}, - }, - "usage": {"latency_ms": -1}, - "context": {"app": "default/test-app", "task": "chat"}, - }, - ) - assert response.status_code == 422 - - def test_update_entry_usage(self, client: TestClient): - """PATCH updates usage independently from data.""" - client.post( - "/apis/intake/v2/workspaces/default/entries", - json={ - "external_id": "chatcmpl-usage-patch", - "data": { - "request": {"model": "gpt-4o", "messages": []}, - "response": {"choices": []}, - }, - "usage": {"model": "gpt-4o", "cost_usd": 0.001}, - "context": {"app": "default/test-app", "task": "chat"}, - }, - ) - - patch_response = client.patch( - "/apis/intake/v2/workspaces/default/entries/external:chatcmpl-usage-patch", - json={ - "usage": {"model": "gpt-4o", "cost_usd": 0.005, "input_tokens": 200}, - }, - ) - assert patch_response.status_code == 200, patch_response.json() - usage = patch_response.json()["usage"] - assert usage["cost_usd"] == 0.005 - assert usage["input_tokens"] == 200 - - @pytest.mark.skip( - reason="Nested JSON filtering requires PostgreSQL JSONB support; SQLite tests use JSON which lacks this functionality" - ) - def test_filter_entries_by_usage_model(self, client: TestClient): - """Filter entries by usage.model (production / Postgres only).""" - for served in ("gpt-4o", "gpt-4o-mini"): - client.post( - "/apis/intake/v2/workspaces/default/entries", - json={ - "external_id": f"chatcmpl-tm-{served}", - "data": { - "request": {"model": "gpt-4o", "messages": []}, - "response": {"choices": []}, - }, - "usage": {"model": served}, - "context": {"app": "default/test-app", "task": "chat"}, - }, - ) - - response = client.get("/apis/intake/v2/workspaces/default/entries?filter[model]=gpt-4o-mini") - assert response.status_code == 200 - entries = response.json()["data"] - assert len(entries) >= 1 - for entry in entries: - assert entry["usage"]["model"] == "gpt-4o-mini" diff --git a/services/intake/tests/test_export_utils.py b/services/intake/tests/test_export_utils.py deleted file mode 100644 index 8defd5ba70..0000000000 --- a/services/intake/tests/test_export_utils.py +++ /dev/null @@ -1,21 +0,0 @@ -# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. -# SPDX-License-Identifier: Apache-2.0 - -"""Tests for export utility helpers.""" - -import pytest -from nmp.intake.app.utils.datastore import DataStoreClient -from nmp.intake.app.utils.exports import extract_nds_path - - -def test_extract_nds_path_returns_workspace_and_dataset(): - assert extract_nds_path("nds://default/my-dataset") == ("default", "my-dataset") - - -def test_extract_nds_path_error_uses_workspace_language(): - with pytest.raises(ValueError, match="nds://workspace/dataset_name"): - extract_nds_path("nds:///missing-workspace") - - -def test_datastore_client_uses_intake_service_token_by_default(): - assert DataStoreClient().token == "service:intake" diff --git a/services/intake/tests/test_exports.py b/services/intake/tests/test_exports.py deleted file mode 100644 index 0fd52eef2c..0000000000 --- a/services/intake/tests/test_exports.py +++ /dev/null @@ -1,294 +0,0 @@ -# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. -# SPDX-License-Identifier: Apache-2.0 - -"""Tests for export functionality.""" - -import json -import tempfile -from pathlib import Path - -import pytest -from fastapi.testclient import TestClient - - -class TestExportsAPI: - """Tests for exports API endpoints.""" - - @pytest.fixture(autouse=True) - def create_test_data(self, client: TestClient): - """Create test data for export tests.""" - # Create entries with different contexts - for i in range(5): - client.post( - "/apis/intake/v2/workspaces/default/entries", - json={ - "external_id": f"export-entry-{i}", - "workspace": "default", - "data": { - "request": {"model": "gpt-4", "messages": [{"role": "user", "content": f"Question {i}"}]}, - "response": {"choices": [{"message": {"role": "assistant", "content": f"Answer {i}"}}]}, - }, - "context": { - "app": "default/test-app", - "task": "chat", - "thread_id": f"thread_{i % 2}", # 2 threads - }, - }, - ) - - def test_preview_export_entries_mode(self, client: TestClient): - """Test previewing export in entries mode.""" - response = client.post( - "/apis/intake/v2/workspaces/default/export/preview", - json={"config": {"filters": {"workspace": "default"}, "limit": 10}}, - ) - assert response.status_code == 200 - data = response.json() - assert "data" in data - assert "count" in data - assert data["count"] == 5 # All 5 entries - - def test_preview_export_path_workspace_overrides_config_filter(self, client: TestClient): - """Test that preview exports use the workspace from the route.""" - response = client.post( - "/apis/intake/v2/workspaces/default/export/preview", - json={"config": {"filters": {"workspace": "other"}, "limit": 10}}, - ) - assert response.status_code == 200 - data = response.json() - assert data["count"] == 5 - for entry in data["data"]: - assert entry["workspace"] == "default" - - def test_preview_export_with_limit(self, client: TestClient): - """Test previewing export with limit.""" - # Test that limit parameter works correctly - response = client.post( - "/apis/intake/v2/workspaces/default/export/preview", - json={"config": {"filters": {"workspace": "default"}, "limit": 3}}, - ) - assert response.status_code == 200 - data = response.json() - assert "data" in data - assert data["count"] == 3 # Limited to 3 entries - - def test_export_to_local_file(self, client: TestClient): - """Test exporting entries to local file.""" - with tempfile.TemporaryDirectory() as tmpdir: - output_file = Path(tmpdir) / "export.jsonl" - - response = client.post( - "/apis/intake/v2/workspaces/default/export/jobs", - json={ - "output_file_url": f"file://{output_file}", - "config": {"filters": {"workspace": "default"}, "limit": 10}, - }, - ) - assert response.status_code == 200 - job_data = response.json() - assert job_data["status"] == "completed" - assert job_data["status_details"]["entries_count"] == 5 - - # Verify file was created and has correct content - assert output_file.exists() - with output_file.open() as f: - lines = f.readlines() - assert len(lines) == 5 - - # Verify messages array was added - first_entry = json.loads(lines[0]) - assert "messages" in first_entry - assert len(first_entry["messages"]) == 2 # user + assistant - - def test_get_export_job_status(self, client: TestClient): - """Test getting export job status.""" - with tempfile.TemporaryDirectory() as tmpdir: - output_file = Path(tmpdir) / "export.jsonl" - - # Create export job - response = client.post( - "/apis/intake/v2/workspaces/default/export/jobs", - json={ - "output_file_url": f"file://{output_file}", - "config": {"filters": {"workspace": "default"}}, - }, - ) - assert response.status_code == 200 - job_data = response.json() - job_name = job_data["name"] - - # Get job status - status_response = client.get(f"/apis/intake/v2/workspaces/default/export/jobs/{job_name}") - assert status_response.status_code == 200 - status_data = status_response.json() - assert status_data["name"] == job_name - assert status_data["status"] == "completed" - - def test_export_with_filters(self, client: TestClient): - """Test exporting with specific filters (using external_id).""" - with tempfile.TemporaryDirectory() as tmpdir: - output_file = Path(tmpdir) / "export.jsonl" - - # Export only a specific entry by external_id - response = client.post( - "/apis/intake/v2/workspaces/default/export/jobs", - json={ - "output_file_url": f"file://{output_file}", - "config": {"filters": {"workspace": "default", "external_id": "export-entry-0"}}, - }, - ) - assert response.status_code == 200 - job_data = response.json() - # Should export 1 entry with the matching external_id - assert job_data["status_details"]["entries_count"] == 1 - - def test_export_invalid_uri(self, client: TestClient): - """Test that invalid URLs are rejected.""" - response = client.post( - "/apis/intake/v2/workspaces/default/export/jobs", - json={"output_file_url": "invalid://bad/uri", "config": {"filters": {"workspace": "default"}}}, - ) - assert response.status_code == 400 - - def test_list_export_jobs(self, client: TestClient): - """Test listing export jobs with pagination.""" - with tempfile.TemporaryDirectory() as tmpdir: - # Create multiple export jobs - job_names = [] - for i in range(3): - output_file = Path(tmpdir) / f"export_{i}.jsonl" - response = client.post( - "/apis/intake/v2/workspaces/default/export/jobs", - json={ - "output_file_url": f"file://{output_file}", - "config": {"filters": {"workspace": "default"}, "limit": 1}, - }, - ) - assert response.status_code == 200 - job_names.append(response.json()["name"]) - - # List export jobs - response = client.get("/apis/intake/v2/workspaces/default/export/jobs?page=1&page_size=10") - assert response.status_code == 200 - data = response.json() - assert "data" in data - assert "pagination" in data - assert len(data["data"]) == 3 - assert data["pagination"]["total_results"] == 3 - - def test_list_export_jobs_path_workspace_overrides_filter(self, client: TestClient): - """Test that listing export jobs uses the workspace from the route.""" - with tempfile.TemporaryDirectory() as tmpdir: - output_file = Path(tmpdir) / "export.jsonl" - create_response = client.post( - "/apis/intake/v2/workspaces/default/export/jobs", - json={ - "output_file_url": f"file://{output_file}", - "config": {"filters": {"workspace": "other"}, "limit": 1}, - }, - ) - assert create_response.status_code == 200 - job_name = create_response.json()["name"] - - response = client.get("/apis/intake/v2/workspaces/default/export/jobs?filter[workspace]=other") - assert response.status_code == 200 - data = response.json() - - assert any(job["name"] == job_name for job in data["data"]) - for job in data["data"]: - assert job["workspace"] == "default" - - def test_list_export_jobs_with_pagination(self, client: TestClient): - """Test listing export jobs with pagination limits.""" - with tempfile.TemporaryDirectory() as tmpdir: - # Create 5 export jobs - for i in range(5): - output_file = Path(tmpdir) / f"export_{i}.jsonl" - client.post( - "/apis/intake/v2/workspaces/default/export/jobs", - json={ - "output_file_url": f"file://{output_file}", - "config": {"filters": {"workspace": "default"}, "limit": 1}, - }, - ) - - # List with page_size=2 - response = client.get("/apis/intake/v2/workspaces/default/export/jobs?page=1&page_size=2") - assert response.status_code == 200 - data = response.json() - assert len(data["data"]) == 2 - assert data["pagination"]["total_results"] == 5 - assert data["pagination"]["total_pages"] == 3 - - # Get second page - response = client.get("/apis/intake/v2/workspaces/default/export/jobs?page=2&page_size=2") - assert response.status_code == 200 - data = response.json() - assert len(data["data"]) == 2 - - def test_list_export_jobs_filter_by_status(self, client: TestClient): - """Test filtering export jobs by status.""" - with tempfile.TemporaryDirectory() as tmpdir: - # Create export jobs (all will be completed after running) - for i in range(2): - output_file = Path(tmpdir) / f"export_{i}.jsonl" - client.post( - "/apis/intake/v2/workspaces/default/export/jobs", - json={ - "output_file_url": f"file://{output_file}", - "config": {"filters": {"workspace": "default"}, "limit": 1}, - }, - ) - - # Filter by completed status - response = client.get("/apis/intake/v2/workspaces/default/export/jobs?filter[status]=completed") - assert response.status_code == 200 - data = response.json() - # All jobs should be completed (synchronous execution) - for job in data["data"]: - assert job["status"] == "completed" - - def test_list_export_jobs_filter_by_name(self, client: TestClient): - """Test filtering export jobs by name.""" - with tempfile.TemporaryDirectory() as tmpdir: - # Create an export job - output_file = Path(tmpdir) / "export_filter_name.jsonl" - response = client.post( - "/apis/intake/v2/workspaces/default/export/jobs", - json={ - "output_file_url": f"file://{output_file}", - "config": {"filters": {"workspace": "default"}, "limit": 1}, - }, - ) - assert response.status_code == 200 - job_name = response.json()["name"] - - # Filter by exact name - response = client.get(f"/apis/intake/v2/workspaces/default/export/jobs?filter[name]={job_name}") - assert response.status_code == 200 - data = response.json() - assert "data" in data - assert "pagination" in data - assert len(data["data"]) == 1 - assert data["data"][0]["name"] == job_name - - def test_list_export_jobs_with_sort(self, client: TestClient): - """Test sorting export jobs.""" - with tempfile.TemporaryDirectory() as tmpdir: - # Create multiple export jobs - for i in range(3): - output_file = Path(tmpdir) / f"export_sort_{i}.jsonl" - client.post( - "/apis/intake/v2/workspaces/default/export/jobs", - json={ - "output_file_url": f"file://{output_file}", - "config": {"filters": {"workspace": "default"}, "limit": 1}, - }, - ) - - # Sort by created_at descending - response = client.get("/apis/intake/v2/workspaces/default/export/jobs?sort=-created_at") - assert response.status_code == 200 - data = response.json() - assert data["sort"] == "-created_at" - assert len(data["data"]) == 3 diff --git a/services/intake/tests/test_sdk_basic.py b/services/intake/tests/test_sdk_basic.py deleted file mode 100644 index 0542d3271c..0000000000 --- a/services/intake/tests/test_sdk_basic.py +++ /dev/null @@ -1,93 +0,0 @@ -# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. -# SPDX-License-Identifier: Apache-2.0 - -"""Simple test using sdk_client fixture with the actual Nemo SDK. - -TODO(v2): These tests require the nemo_platform SDK client fixture. -See services/core/jobs/tests/conftest.py for the pattern: -1. Create an AsyncClient with ASGITransport pointing to the test app -2. Create AsyncNeMoPlatform with that client -3. Use the SDK methods to interact with the service - -Currently skipped until nemo_platform is added as a test dependency -and the sdk_client fixture is implemented in conftest.py. -""" - -import pytest - - -# TODO(v2): Implement sdk_client fixture following jobs service pattern -# See services/core/jobs/tests/conftest.py for reference -@pytest.mark.skip( - reason="SDK client fixture not yet implemented - see services/core/jobs/tests/conftest.py for pattern" -) -def test_create_app_with_sdk_client(sdk_client): - """Test creating an app using the actual Nemo SDK.""" - # Create app using SDK - sdk_client.intake.apps.create(name="test-app", workspace="default", description="Test app") - - # Retrieve using SDK - app = sdk_client.intake.apps.retrieve(workspace="default", app_name="test-app") - - assert app.name == "test-app" - assert app.workspace == "default" - assert app.description == "Test app" - - -# TODO(v2): Implement sdk_client fixture following jobs service pattern -@pytest.mark.skip( - reason="SDK client fixture not yet implemented - see services/core/jobs/tests/conftest.py for pattern" -) -def test_complete_workflow_with_tasks_and_entries(sdk_client): - """Test complete workflow: create app, tasks, entries, and filtering.""" - - # Create app - sdk_client.intake.apps.create(name="workflow-app", workspace="default", description="Workflow test app") - - # Create two tasks - sdk_client.intake.apps.tasks.create( - workspace="default", app_name="workflow-app", name="chat-task", description="Chat task for testing" - ) - - sdk_client.intake.apps.tasks.create( - workspace="default", app_name="workflow-app", name="completion-task", description="Completion task for testing" - ) - - # Create 3 entries for first task (chat-task) - for i in range(3): - sdk_client.intake.entries.create( - external_id=f"chat-entry-{i}", - workspace="default", - data={ - "request": {"model": "test-model", "messages": [{"role": "user", "content": f"Chat question {i}"}]}, - "response": {"choices": [{"message": {"role": "assistant", "content": f"Chat answer {i}"}}]}, - }, - context={"app": "default/workflow-app", "task": "chat-task"}, - ) - - # Create 3 entries for second task (completion-task) - for i in range(3): - sdk_client.intake.entries.create( - external_id=f"completion-entry-{i}", - workspace="default", - data={ - "request": {"model": "test-model", "messages": [{"role": "user", "content": f"Completion prompt {i}"}]}, - "response": {"choices": [{"message": {"role": "assistant", "content": f"Completion result {i}"}}]}, - }, - context={"app": "default/workflow-app", "task": "completion-task"}, - ) - - # List all entries - all_entries = sdk_client.intake.entries.list(page=1, page_size=20) - assert len(all_entries.data) >= 6 # At least our 6 entries - - # Filter entries by workspace - default_entries = sdk_client.intake.entries.list(page=1, page_size=20, filter={"workspace": "default"}) - assert len(default_entries.data) >= 6 # At least our 6 entries in default workspace - - # Verify we can access entry details - for entry in default_entries.data[:3]: # Check first 3 - assert entry.workspace == "default" - assert entry.context.app == "default/workflow-app" - assert entry.context.task in ["chat-task", "completion-task"] - assert "entry" in entry.external_id diff --git a/services/intake/tests/test_tasks.py b/services/intake/tests/test_tasks.py deleted file mode 100644 index 7051e5afac..0000000000 --- a/services/intake/tests/test_tasks.py +++ /dev/null @@ -1,95 +0,0 @@ -# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. -# SPDX-License-Identifier: Apache-2.0 - -"""Tests for Tasks API endpoints.""" - -import pytest -from fastapi.testclient import TestClient - - -class TestTasksAPI: - """Tests for Tasks API endpoints.""" - - @pytest.fixture(autouse=True) - def create_test_app(self, client: TestClient): - """Create a test app before each test.""" - client.post( - "/apis/intake/v2/workspaces/default/apps", - json={"name": "test-app", "description": "Test application"}, - ) - - def test_create_task(self, client: TestClient): - """Test creating a task under an app.""" - response = client.post( - "/apis/intake/v2/workspaces/default/apps/test-app/tasks", - json={"name": "chat", "description": "Chat task"}, - ) - assert response.status_code == 201 - data = response.json() - assert data["name"] == "chat" - assert data["workspace"] == "default" - assert data["app"] == "default/test-app" - assert data["description"] == "Chat task" - - def test_get_task(self, client: TestClient): - """Test getting a task by name.""" - # Create task - client.post( - "/apis/intake/v2/workspaces/default/apps/test-app/tasks", - json={"name": "chat", "description": "Chat task"}, - ) - - # Get task - response = client.get("/apis/intake/v2/workspaces/default/apps/test-app/tasks/chat") - assert response.status_code == 200 - data = response.json() - assert data["name"] == "chat" - assert data["app"] == "default/test-app" - - def test_list_tasks(self, client: TestClient): - """Test listing tasks for an app.""" - # Create multiple tasks - for i in range(3): - client.post( - "/apis/intake/v2/workspaces/default/apps/test-app/tasks", - json={"name": f"task-{i}", "description": f"Task {i}"}, - ) - - # List tasks - response = client.get("/apis/intake/v2/workspaces/default/apps/test-app/tasks") - assert response.status_code == 200 - data = response.json() - assert len(data["data"]) == 3 - - def test_update_task(self, client: TestClient): - """Test updating a task.""" - # Create task - client.post( - "/apis/intake/v2/workspaces/default/apps/test-app/tasks", - json={"name": "chat", "description": "Original description"}, - ) - - # Update task - response = client.patch( - "/apis/intake/v2/workspaces/default/apps/test-app/tasks/chat", - json={"description": "Updated description"}, - ) - assert response.status_code == 200 - data = response.json() - assert data["description"] == "Updated description" - - def test_delete_task(self, client: TestClient): - """Test deleting a task.""" - # Create task - client.post( - "/apis/intake/v2/workspaces/default/apps/test-app/tasks", - json={"name": "chat", "description": "Chat task"}, - ) - - # Delete task - response = client.delete("/apis/intake/v2/workspaces/default/apps/test-app/tasks/chat") - assert response.status_code == 204 - - # Verify it's deleted - response = client.get("/apis/intake/v2/workspaces/default/apps/test-app/tasks/chat") - assert response.status_code == 404 diff --git a/services/intake/tests/test_values.py b/services/intake/tests/test_values.py deleted file mode 100644 index 8aee83d411..0000000000 --- a/services/intake/tests/test_values.py +++ /dev/null @@ -1,52 +0,0 @@ -# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. -# SPDX-License-Identifier: Apache-2.0 - -"""Tests for Intake value objects.""" - -from datetime import datetime, timezone - -import pytest -from nmp.intake.entities.values import Usage -from pydantic import ValidationError - - -def test_usage_converts_naive_timestamps_to_utc() -> None: - started_at = datetime(2026, 4, 30, 15, 0, 0) - ended_at = datetime(2026, 4, 30, 15, 0, 1, 840000) - - usage = Usage(started_at=started_at, ended_at=ended_at) - - assert usage.started_at == started_at.replace(tzinfo=timezone.utc) - assert usage.ended_at == ended_at.replace(tzinfo=timezone.utc) - - -def test_usage_rejects_reversed_timestamps() -> None: - with pytest.raises(ValidationError, match="ended_at must be greater than or equal to started_at"): - Usage( - started_at=datetime(2026, 4, 30, 15, 0, 2, tzinfo=timezone.utc), - ended_at=datetime(2026, 4, 30, 15, 0, 1, tzinfo=timezone.utc), - ) - - -def test_usage_rejects_cached_tokens_exceeding_input_tokens() -> None: - Usage(input_tokens=10, cached_tokens=10) - - with pytest.raises(ValidationError, match="cached_tokens must be less than or equal to input_tokens"): - Usage(input_tokens=10, cached_tokens=11) - - -def test_usage_json_schema_preserves_optional_field_constraints() -> None: - properties = Usage.model_json_schema()["properties"] - - assert properties["started_at"]["format"] == "date-time" - assert properties["ended_at"]["format"] == "date-time" - for field_name in ( - "latency_ms", - "cost_usd", - "cost_input_usd", - "cost_output_usd", - "input_tokens", - "output_tokens", - "cached_tokens", - ): - assert properties[field_name]["minimum"] == 0 diff --git a/uv.lock b/uv.lock index f0839faea0..b4e46a2dca 100644 --- a/uv.lock +++ b/uv.lock @@ -4250,18 +4250,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/b3/38/89ba8ad64ae25be8de66a6d463314cf1eb366222074cfda9ee839c56a4b4/mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", size = 9979, upload-time = "2022-08-14T12:40:09.779Z" }, ] -[[package]] -name = "mirakuru" -version = "3.0.2" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "psutil", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-12-nemoplatform-cu128') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-12-nemoplatform-cu128' and extra == 'extra-20-nmp-safe-synthesizer-cpu') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-20-nmp-safe-synthesizer-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'darwin' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-12-nemoplatform-cu128') or (sys_platform == 'darwin' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'darwin' and extra == 'extra-12-nemoplatform-cu128' and extra == 'extra-20-nmp-safe-synthesizer-cpu') or (sys_platform == 'darwin' and extra == 'extra-20-nmp-safe-synthesizer-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-12-nemoplatform-cu128') or (sys_platform == 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'linux' and extra == 'extra-12-nemoplatform-cu128' and extra == 'extra-20-nmp-safe-synthesizer-cpu') or (sys_platform == 'linux' and extra == 'extra-20-nmp-safe-synthesizer-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128')" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/53/23/db9034ba28c7d89a540ffb8ca789f70dc12079108ece1cd1762295d5c807/mirakuru-3.0.2.tar.gz", hash = "sha256:21192186a8680ea7567ca68170261df3785768b12962dd19fe8cccab15ad3441", size = 29338, upload-time = "2026-02-11T19:41:15.42Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/49/5f/a3f1a7f1f6e55de9285b03ae7e0d3c2a15e044b6e3f9b53bef5609ca05f2/mirakuru-3.0.2-py3-none-any.whl", hash = "sha256:10e5dac4a8f26872c63e9cdfdc01b775aaa2beb3ced98abc497279d2dc525b8f", size = 27583, upload-time = "2026-02-11T19:41:13.578Z" }, -] - [[package]] name = "mistune" version = "3.2.1" @@ -7594,34 +7582,13 @@ name = "nmp-intake" version = "0.0.1" source = { editable = "services/intake" } dependencies = [ - { name = "alembic", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-12-nemoplatform-cu128') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-12-nemoplatform-cu128' and extra == 'extra-20-nmp-safe-synthesizer-cpu') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-20-nmp-safe-synthesizer-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'darwin' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-12-nemoplatform-cu128') or (sys_platform == 'darwin' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'darwin' and extra == 'extra-12-nemoplatform-cu128' and extra == 'extra-20-nmp-safe-synthesizer-cpu') or (sys_platform == 'darwin' and extra == 'extra-20-nmp-safe-synthesizer-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-12-nemoplatform-cu128') or (sys_platform == 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'linux' and extra == 'extra-12-nemoplatform-cu128' and extra == 'extra-20-nmp-safe-synthesizer-cpu') or (sys_platform == 'linux' and extra == 'extra-20-nmp-safe-synthesizer-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128')" }, - { name = "asyncpg", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-12-nemoplatform-cu128') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-12-nemoplatform-cu128' and extra == 'extra-20-nmp-safe-synthesizer-cpu') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-20-nmp-safe-synthesizer-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'darwin' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-12-nemoplatform-cu128') or (sys_platform == 'darwin' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'darwin' and extra == 'extra-12-nemoplatform-cu128' and extra == 'extra-20-nmp-safe-synthesizer-cpu') or (sys_platform == 'darwin' and extra == 'extra-20-nmp-safe-synthesizer-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-12-nemoplatform-cu128') or (sys_platform == 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'linux' and extra == 'extra-12-nemoplatform-cu128' and extra == 'extra-20-nmp-safe-synthesizer-cpu') or (sys_platform == 'linux' and extra == 'extra-20-nmp-safe-synthesizer-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128')" }, - { name = "base58", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-12-nemoplatform-cu128') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-12-nemoplatform-cu128' and extra == 'extra-20-nmp-safe-synthesizer-cpu') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-20-nmp-safe-synthesizer-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'darwin' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-12-nemoplatform-cu128') or (sys_platform == 'darwin' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'darwin' and extra == 'extra-12-nemoplatform-cu128' and extra == 'extra-20-nmp-safe-synthesizer-cpu') or (sys_platform == 'darwin' and extra == 'extra-20-nmp-safe-synthesizer-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-12-nemoplatform-cu128') or (sys_platform == 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'linux' and extra == 'extra-12-nemoplatform-cu128' and extra == 'extra-20-nmp-safe-synthesizer-cpu') or (sys_platform == 'linux' and extra == 'extra-20-nmp-safe-synthesizer-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128')" }, - { name = "celery", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-12-nemoplatform-cu128') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-12-nemoplatform-cu128' and extra == 'extra-20-nmp-safe-synthesizer-cpu') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-20-nmp-safe-synthesizer-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'darwin' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-12-nemoplatform-cu128') or (sys_platform == 'darwin' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'darwin' and extra == 'extra-12-nemoplatform-cu128' and extra == 'extra-20-nmp-safe-synthesizer-cpu') or (sys_platform == 'darwin' and extra == 'extra-20-nmp-safe-synthesizer-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-12-nemoplatform-cu128') or (sys_platform == 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'linux' and extra == 'extra-12-nemoplatform-cu128' and extra == 'extra-20-nmp-safe-synthesizer-cpu') or (sys_platform == 'linux' and extra == 'extra-20-nmp-safe-synthesizer-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128')" }, { name = "clickhouse-connect", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-12-nemoplatform-cu128') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-12-nemoplatform-cu128' and extra == 'extra-20-nmp-safe-synthesizer-cpu') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-20-nmp-safe-synthesizer-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'darwin' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-12-nemoplatform-cu128') or (sys_platform == 'darwin' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'darwin' and extra == 'extra-12-nemoplatform-cu128' and extra == 'extra-20-nmp-safe-synthesizer-cpu') or (sys_platform == 'darwin' and extra == 'extra-20-nmp-safe-synthesizer-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-12-nemoplatform-cu128') or (sys_platform == 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'linux' and extra == 'extra-12-nemoplatform-cu128' and extra == 'extra-20-nmp-safe-synthesizer-cpu') or (sys_platform == 'linux' and extra == 'extra-20-nmp-safe-synthesizer-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128')" }, { name = "fastapi", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-12-nemoplatform-cu128') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-12-nemoplatform-cu128' and extra == 'extra-20-nmp-safe-synthesizer-cpu') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-20-nmp-safe-synthesizer-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'darwin' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-12-nemoplatform-cu128') or (sys_platform == 'darwin' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'darwin' and extra == 'extra-12-nemoplatform-cu128' and extra == 'extra-20-nmp-safe-synthesizer-cpu') or (sys_platform == 'darwin' and extra == 'extra-20-nmp-safe-synthesizer-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-12-nemoplatform-cu128') or (sys_platform == 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'linux' and extra == 'extra-12-nemoplatform-cu128' and extra == 'extra-20-nmp-safe-synthesizer-cpu') or (sys_platform == 'linux' and extra == 'extra-20-nmp-safe-synthesizer-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128')" }, - { name = "flower", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-12-nemoplatform-cu128') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-12-nemoplatform-cu128' and extra == 'extra-20-nmp-safe-synthesizer-cpu') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-20-nmp-safe-synthesizer-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'darwin' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-12-nemoplatform-cu128') or (sys_platform == 'darwin' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'darwin' and extra == 'extra-12-nemoplatform-cu128' and extra == 'extra-20-nmp-safe-synthesizer-cpu') or (sys_platform == 'darwin' and extra == 'extra-20-nmp-safe-synthesizer-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-12-nemoplatform-cu128') or (sys_platform == 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'linux' and extra == 'extra-12-nemoplatform-cu128' and extra == 'extra-20-nmp-safe-synthesizer-cpu') or (sys_platform == 'linux' and extra == 'extra-20-nmp-safe-synthesizer-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128')" }, - { name = "httpx", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-12-nemoplatform-cu128') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-12-nemoplatform-cu128' and extra == 'extra-20-nmp-safe-synthesizer-cpu') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-20-nmp-safe-synthesizer-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'darwin' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-12-nemoplatform-cu128') or (sys_platform == 'darwin' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'darwin' and extra == 'extra-12-nemoplatform-cu128' and extra == 'extra-20-nmp-safe-synthesizer-cpu') or (sys_platform == 'darwin' and extra == 'extra-20-nmp-safe-synthesizer-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-12-nemoplatform-cu128') or (sys_platform == 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'linux' and extra == 'extra-12-nemoplatform-cu128' and extra == 'extra-20-nmp-safe-synthesizer-cpu') or (sys_platform == 'linux' and extra == 'extra-20-nmp-safe-synthesizer-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128')" }, - { name = "huggingface-hub", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-12-nemoplatform-cu128') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-12-nemoplatform-cu128' and extra == 'extra-20-nmp-safe-synthesizer-cpu') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-20-nmp-safe-synthesizer-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'darwin' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-12-nemoplatform-cu128') or (sys_platform == 'darwin' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'darwin' and extra == 'extra-12-nemoplatform-cu128' and extra == 'extra-20-nmp-safe-synthesizer-cpu') or (sys_platform == 'darwin' and extra == 'extra-20-nmp-safe-synthesizer-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-12-nemoplatform-cu128') or (sys_platform == 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'linux' and extra == 'extra-12-nemoplatform-cu128' and extra == 'extra-20-nmp-safe-synthesizer-cpu') or (sys_platform == 'linux' and extra == 'extra-20-nmp-safe-synthesizer-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128')" }, { name = "nmp-common", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-12-nemoplatform-cu128') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-12-nemoplatform-cu128' and extra == 'extra-20-nmp-safe-synthesizer-cpu') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-20-nmp-safe-synthesizer-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'darwin' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-12-nemoplatform-cu128') or (sys_platform == 'darwin' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'darwin' and extra == 'extra-12-nemoplatform-cu128' and extra == 'extra-20-nmp-safe-synthesizer-cpu') or (sys_platform == 'darwin' and extra == 'extra-20-nmp-safe-synthesizer-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-12-nemoplatform-cu128') or (sys_platform == 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'linux' and extra == 'extra-12-nemoplatform-cu128' and extra == 'extra-20-nmp-safe-synthesizer-cpu') or (sys_platform == 'linux' and extra == 'extra-20-nmp-safe-synthesizer-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128')" }, - { name = "openai", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-12-nemoplatform-cu128') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-12-nemoplatform-cu128' and extra == 'extra-20-nmp-safe-synthesizer-cpu') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-20-nmp-safe-synthesizer-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'darwin' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-12-nemoplatform-cu128') or (sys_platform == 'darwin' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'darwin' and extra == 'extra-12-nemoplatform-cu128' and extra == 'extra-20-nmp-safe-synthesizer-cpu') or (sys_platform == 'darwin' and extra == 'extra-20-nmp-safe-synthesizer-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-12-nemoplatform-cu128') or (sys_platform == 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'linux' and extra == 'extra-12-nemoplatform-cu128' and extra == 'extra-20-nmp-safe-synthesizer-cpu') or (sys_platform == 'linux' and extra == 'extra-20-nmp-safe-synthesizer-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128')" }, - { name = "opentelemetry-api", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-12-nemoplatform-cu128') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-12-nemoplatform-cu128' and extra == 'extra-20-nmp-safe-synthesizer-cpu') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-20-nmp-safe-synthesizer-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'darwin' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-12-nemoplatform-cu128') or (sys_platform == 'darwin' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'darwin' and extra == 'extra-12-nemoplatform-cu128' and extra == 'extra-20-nmp-safe-synthesizer-cpu') or (sys_platform == 'darwin' and extra == 'extra-20-nmp-safe-synthesizer-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-12-nemoplatform-cu128') or (sys_platform == 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'linux' and extra == 'extra-12-nemoplatform-cu128' and extra == 'extra-20-nmp-safe-synthesizer-cpu') or (sys_platform == 'linux' and extra == 'extra-20-nmp-safe-synthesizer-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128')" }, - { name = "opentelemetry-exporter-otlp", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-12-nemoplatform-cu128') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-12-nemoplatform-cu128' and extra == 'extra-20-nmp-safe-synthesizer-cpu') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-20-nmp-safe-synthesizer-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'darwin' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-12-nemoplatform-cu128') or (sys_platform == 'darwin' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'darwin' and extra == 'extra-12-nemoplatform-cu128' and extra == 'extra-20-nmp-safe-synthesizer-cpu') or (sys_platform == 'darwin' and extra == 'extra-20-nmp-safe-synthesizer-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-12-nemoplatform-cu128') or (sys_platform == 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'linux' and extra == 'extra-12-nemoplatform-cu128' and extra == 'extra-20-nmp-safe-synthesizer-cpu') or (sys_platform == 'linux' and extra == 'extra-20-nmp-safe-synthesizer-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128')" }, - { name = "opentelemetry-instrumentation-fastapi", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-12-nemoplatform-cu128') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-12-nemoplatform-cu128' and extra == 'extra-20-nmp-safe-synthesizer-cpu') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-20-nmp-safe-synthesizer-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'darwin' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-12-nemoplatform-cu128') or (sys_platform == 'darwin' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'darwin' and extra == 'extra-12-nemoplatform-cu128' and extra == 'extra-20-nmp-safe-synthesizer-cpu') or (sys_platform == 'darwin' and extra == 'extra-20-nmp-safe-synthesizer-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-12-nemoplatform-cu128') or (sys_platform == 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'linux' and extra == 'extra-12-nemoplatform-cu128' and extra == 'extra-20-nmp-safe-synthesizer-cpu') or (sys_platform == 'linux' and extra == 'extra-20-nmp-safe-synthesizer-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128')" }, - { name = "opentelemetry-instrumentation-openai", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-12-nemoplatform-cu128') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-12-nemoplatform-cu128' and extra == 'extra-20-nmp-safe-synthesizer-cpu') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-20-nmp-safe-synthesizer-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'darwin' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-12-nemoplatform-cu128') or (sys_platform == 'darwin' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'darwin' and extra == 'extra-12-nemoplatform-cu128' and extra == 'extra-20-nmp-safe-synthesizer-cpu') or (sys_platform == 'darwin' and extra == 'extra-20-nmp-safe-synthesizer-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-12-nemoplatform-cu128') or (sys_platform == 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'linux' and extra == 'extra-12-nemoplatform-cu128' and extra == 'extra-20-nmp-safe-synthesizer-cpu') or (sys_platform == 'linux' and extra == 'extra-20-nmp-safe-synthesizer-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128')" }, { name = "opentelemetry-proto", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-12-nemoplatform-cu128') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-12-nemoplatform-cu128' and extra == 'extra-20-nmp-safe-synthesizer-cpu') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-20-nmp-safe-synthesizer-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'darwin' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-12-nemoplatform-cu128') or (sys_platform == 'darwin' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'darwin' and extra == 'extra-12-nemoplatform-cu128' and extra == 'extra-20-nmp-safe-synthesizer-cpu') or (sys_platform == 'darwin' and extra == 'extra-20-nmp-safe-synthesizer-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-12-nemoplatform-cu128') or (sys_platform == 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'linux' and extra == 'extra-12-nemoplatform-cu128' and extra == 'extra-20-nmp-safe-synthesizer-cpu') or (sys_platform == 'linux' and extra == 'extra-20-nmp-safe-synthesizer-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128')" }, - { name = "opentelemetry-sdk", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-12-nemoplatform-cu128') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-12-nemoplatform-cu128' and extra == 'extra-20-nmp-safe-synthesizer-cpu') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-20-nmp-safe-synthesizer-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'darwin' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-12-nemoplatform-cu128') or (sys_platform == 'darwin' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'darwin' and extra == 'extra-12-nemoplatform-cu128' and extra == 'extra-20-nmp-safe-synthesizer-cpu') or (sys_platform == 'darwin' and extra == 'extra-20-nmp-safe-synthesizer-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-12-nemoplatform-cu128') or (sys_platform == 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'linux' and extra == 'extra-12-nemoplatform-cu128' and extra == 'extra-20-nmp-safe-synthesizer-cpu') or (sys_platform == 'linux' and extra == 'extra-20-nmp-safe-synthesizer-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128')" }, - { name = "psycopg2-binary", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-12-nemoplatform-cu128') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-12-nemoplatform-cu128' and extra == 'extra-20-nmp-safe-synthesizer-cpu') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-20-nmp-safe-synthesizer-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'darwin' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-12-nemoplatform-cu128') or (sys_platform == 'darwin' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'darwin' and extra == 'extra-12-nemoplatform-cu128' and extra == 'extra-20-nmp-safe-synthesizer-cpu') or (sys_platform == 'darwin' and extra == 'extra-20-nmp-safe-synthesizer-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-12-nemoplatform-cu128') or (sys_platform == 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'linux' and extra == 'extra-12-nemoplatform-cu128' and extra == 'extra-20-nmp-safe-synthesizer-cpu') or (sys_platform == 'linux' and extra == 'extra-20-nmp-safe-synthesizer-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128')" }, { name = "pydantic", extra = ["email"], marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-12-nemoplatform-cu128') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-12-nemoplatform-cu128' and extra == 'extra-20-nmp-safe-synthesizer-cpu') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-20-nmp-safe-synthesizer-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'darwin' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-12-nemoplatform-cu128') or (sys_platform == 'darwin' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'darwin' and extra == 'extra-12-nemoplatform-cu128' and extra == 'extra-20-nmp-safe-synthesizer-cpu') or (sys_platform == 'darwin' and extra == 'extra-20-nmp-safe-synthesizer-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-12-nemoplatform-cu128') or (sys_platform == 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'linux' and extra == 'extra-12-nemoplatform-cu128' and extra == 'extra-20-nmp-safe-synthesizer-cpu') or (sys_platform == 'linux' and extra == 'extra-20-nmp-safe-synthesizer-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128')" }, { name = "pydantic-settings", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-12-nemoplatform-cu128') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-12-nemoplatform-cu128' and extra == 'extra-20-nmp-safe-synthesizer-cpu') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-20-nmp-safe-synthesizer-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'darwin' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-12-nemoplatform-cu128') or (sys_platform == 'darwin' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'darwin' and extra == 'extra-12-nemoplatform-cu128' and extra == 'extra-20-nmp-safe-synthesizer-cpu') or (sys_platform == 'darwin' and extra == 'extra-20-nmp-safe-synthesizer-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-12-nemoplatform-cu128') or (sys_platform == 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'linux' and extra == 'extra-12-nemoplatform-cu128' and extra == 'extra-20-nmp-safe-synthesizer-cpu') or (sys_platform == 'linux' and extra == 'extra-20-nmp-safe-synthesizer-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128')" }, - { name = "python-dotenv", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-12-nemoplatform-cu128') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-12-nemoplatform-cu128' and extra == 'extra-20-nmp-safe-synthesizer-cpu') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-20-nmp-safe-synthesizer-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'darwin' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-12-nemoplatform-cu128') or (sys_platform == 'darwin' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'darwin' and extra == 'extra-12-nemoplatform-cu128' and extra == 'extra-20-nmp-safe-synthesizer-cpu') or (sys_platform == 'darwin' and extra == 'extra-20-nmp-safe-synthesizer-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-12-nemoplatform-cu128') or (sys_platform == 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'linux' and extra == 'extra-12-nemoplatform-cu128' and extra == 'extra-20-nmp-safe-synthesizer-cpu') or (sys_platform == 'linux' and extra == 'extra-20-nmp-safe-synthesizer-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128')" }, - { name = "pyyaml", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-12-nemoplatform-cu128') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-12-nemoplatform-cu128' and extra == 'extra-20-nmp-safe-synthesizer-cpu') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-20-nmp-safe-synthesizer-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'darwin' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-12-nemoplatform-cu128') or (sys_platform == 'darwin' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'darwin' and extra == 'extra-12-nemoplatform-cu128' and extra == 'extra-20-nmp-safe-synthesizer-cpu') or (sys_platform == 'darwin' and extra == 'extra-20-nmp-safe-synthesizer-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-12-nemoplatform-cu128') or (sys_platform == 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'linux' and extra == 'extra-12-nemoplatform-cu128' and extra == 'extra-20-nmp-safe-synthesizer-cpu') or (sys_platform == 'linux' and extra == 'extra-20-nmp-safe-synthesizer-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128')" }, - { name = "redis", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-12-nemoplatform-cu128') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-12-nemoplatform-cu128' and extra == 'extra-20-nmp-safe-synthesizer-cpu') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-20-nmp-safe-synthesizer-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'darwin' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-12-nemoplatform-cu128') or (sys_platform == 'darwin' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'darwin' and extra == 'extra-12-nemoplatform-cu128' and extra == 'extra-20-nmp-safe-synthesizer-cpu') or (sys_platform == 'darwin' and extra == 'extra-20-nmp-safe-synthesizer-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-12-nemoplatform-cu128') or (sys_platform == 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'linux' and extra == 'extra-12-nemoplatform-cu128' and extra == 'extra-20-nmp-safe-synthesizer-cpu') or (sys_platform == 'linux' and extra == 'extra-20-nmp-safe-synthesizer-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128')" }, - { name = "requests", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-12-nemoplatform-cu128') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-12-nemoplatform-cu128' and extra == 'extra-20-nmp-safe-synthesizer-cpu') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-20-nmp-safe-synthesizer-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'darwin' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-12-nemoplatform-cu128') or (sys_platform == 'darwin' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'darwin' and extra == 'extra-12-nemoplatform-cu128' and extra == 'extra-20-nmp-safe-synthesizer-cpu') or (sys_platform == 'darwin' and extra == 'extra-20-nmp-safe-synthesizer-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-12-nemoplatform-cu128') or (sys_platform == 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'linux' and extra == 'extra-12-nemoplatform-cu128' and extra == 'extra-20-nmp-safe-synthesizer-cpu') or (sys_platform == 'linux' and extra == 'extra-20-nmp-safe-synthesizer-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128')" }, - { name = "sqlalchemy", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-12-nemoplatform-cu128') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-12-nemoplatform-cu128' and extra == 'extra-20-nmp-safe-synthesizer-cpu') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-20-nmp-safe-synthesizer-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'darwin' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-12-nemoplatform-cu128') or (sys_platform == 'darwin' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'darwin' and extra == 'extra-12-nemoplatform-cu128' and extra == 'extra-20-nmp-safe-synthesizer-cpu') or (sys_platform == 'darwin' and extra == 'extra-20-nmp-safe-synthesizer-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-12-nemoplatform-cu128') or (sys_platform == 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'linux' and extra == 'extra-12-nemoplatform-cu128' and extra == 'extra-20-nmp-safe-synthesizer-cpu') or (sys_platform == 'linux' and extra == 'extra-20-nmp-safe-synthesizer-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128')" }, - { name = "types-requests", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-12-nemoplatform-cu128') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-12-nemoplatform-cu128' and extra == 'extra-20-nmp-safe-synthesizer-cpu') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-20-nmp-safe-synthesizer-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'darwin' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-12-nemoplatform-cu128') or (sys_platform == 'darwin' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'darwin' and extra == 'extra-12-nemoplatform-cu128' and extra == 'extra-20-nmp-safe-synthesizer-cpu') or (sys_platform == 'darwin' and extra == 'extra-20-nmp-safe-synthesizer-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-12-nemoplatform-cu128') or (sys_platform == 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'linux' and extra == 'extra-12-nemoplatform-cu128' and extra == 'extra-20-nmp-safe-synthesizer-cpu') or (sys_platform == 'linux' and extra == 'extra-20-nmp-safe-synthesizer-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128')" }, { name = "uvicorn", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-12-nemoplatform-cu128') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-12-nemoplatform-cu128' and extra == 'extra-20-nmp-safe-synthesizer-cpu') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-20-nmp-safe-synthesizer-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'darwin' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-12-nemoplatform-cu128') or (sys_platform == 'darwin' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'darwin' and extra == 'extra-12-nemoplatform-cu128' and extra == 'extra-20-nmp-safe-synthesizer-cpu') or (sys_platform == 'darwin' and extra == 'extra-20-nmp-safe-synthesizer-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-12-nemoplatform-cu128') or (sys_platform == 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'linux' and extra == 'extra-12-nemoplatform-cu128' and extra == 'extra-20-nmp-safe-synthesizer-cpu') or (sys_platform == 'linux' and extra == 'extra-20-nmp-safe-synthesizer-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128')" }, - { name = "watchdog", extra = ["watchmedo"], marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-12-nemoplatform-cu128') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-12-nemoplatform-cu128' and extra == 'extra-20-nmp-safe-synthesizer-cpu') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-20-nmp-safe-synthesizer-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'darwin' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-12-nemoplatform-cu128') or (sys_platform == 'darwin' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'darwin' and extra == 'extra-12-nemoplatform-cu128' and extra == 'extra-20-nmp-safe-synthesizer-cpu') or (sys_platform == 'darwin' and extra == 'extra-20-nmp-safe-synthesizer-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-12-nemoplatform-cu128') or (sys_platform == 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'linux' and extra == 'extra-12-nemoplatform-cu128' and extra == 'extra-20-nmp-safe-synthesizer-cpu') or (sys_platform == 'linux' and extra == 'extra-20-nmp-safe-synthesizer-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128')" }, ] [package.dev-dependencies] @@ -7631,47 +7598,22 @@ dev = [ { name = "mypy", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-12-nemoplatform-cu128') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-12-nemoplatform-cu128' and extra == 'extra-20-nmp-safe-synthesizer-cpu') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-20-nmp-safe-synthesizer-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'darwin' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-12-nemoplatform-cu128') or (sys_platform == 'darwin' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'darwin' and extra == 'extra-12-nemoplatform-cu128' and extra == 'extra-20-nmp-safe-synthesizer-cpu') or (sys_platform == 'darwin' and extra == 'extra-20-nmp-safe-synthesizer-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-12-nemoplatform-cu128') or (sys_platform == 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'linux' and extra == 'extra-12-nemoplatform-cu128' and extra == 'extra-20-nmp-safe-synthesizer-cpu') or (sys_platform == 'linux' and extra == 'extra-20-nmp-safe-synthesizer-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128')" }, { name = "nmp-testing", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-12-nemoplatform-cu128') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-12-nemoplatform-cu128' and extra == 'extra-20-nmp-safe-synthesizer-cpu') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-20-nmp-safe-synthesizer-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'darwin' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-12-nemoplatform-cu128') or (sys_platform == 'darwin' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'darwin' and extra == 'extra-12-nemoplatform-cu128' and extra == 'extra-20-nmp-safe-synthesizer-cpu') or (sys_platform == 'darwin' and extra == 'extra-20-nmp-safe-synthesizer-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-12-nemoplatform-cu128') or (sys_platform == 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'linux' and extra == 'extra-12-nemoplatform-cu128' and extra == 'extra-20-nmp-safe-synthesizer-cpu') or (sys_platform == 'linux' and extra == 'extra-20-nmp-safe-synthesizer-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128')" }, { name = "pre-commit", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-12-nemoplatform-cu128') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-12-nemoplatform-cu128' and extra == 'extra-20-nmp-safe-synthesizer-cpu') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-20-nmp-safe-synthesizer-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'darwin' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-12-nemoplatform-cu128') or (sys_platform == 'darwin' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'darwin' and extra == 'extra-12-nemoplatform-cu128' and extra == 'extra-20-nmp-safe-synthesizer-cpu') or (sys_platform == 'darwin' and extra == 'extra-20-nmp-safe-synthesizer-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-12-nemoplatform-cu128') or (sys_platform == 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'linux' and extra == 'extra-12-nemoplatform-cu128' and extra == 'extra-20-nmp-safe-synthesizer-cpu') or (sys_platform == 'linux' and extra == 'extra-20-nmp-safe-synthesizer-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128')" }, - { name = "psycopg", extra = ["binary", "pool"], marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-12-nemoplatform-cu128') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-12-nemoplatform-cu128' and extra == 'extra-20-nmp-safe-synthesizer-cpu') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-20-nmp-safe-synthesizer-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'darwin' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-12-nemoplatform-cu128') or (sys_platform == 'darwin' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'darwin' and extra == 'extra-12-nemoplatform-cu128' and extra == 'extra-20-nmp-safe-synthesizer-cpu') or (sys_platform == 'darwin' and extra == 'extra-20-nmp-safe-synthesizer-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-12-nemoplatform-cu128') or (sys_platform == 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'linux' and extra == 'extra-12-nemoplatform-cu128' and extra == 'extra-20-nmp-safe-synthesizer-cpu') or (sys_platform == 'linux' and extra == 'extra-20-nmp-safe-synthesizer-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128')" }, - { name = "psycopg2-binary", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-12-nemoplatform-cu128') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-12-nemoplatform-cu128' and extra == 'extra-20-nmp-safe-synthesizer-cpu') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-20-nmp-safe-synthesizer-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'darwin' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-12-nemoplatform-cu128') or (sys_platform == 'darwin' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'darwin' and extra == 'extra-12-nemoplatform-cu128' and extra == 'extra-20-nmp-safe-synthesizer-cpu') or (sys_platform == 'darwin' and extra == 'extra-20-nmp-safe-synthesizer-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-12-nemoplatform-cu128') or (sys_platform == 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'linux' and extra == 'extra-12-nemoplatform-cu128' and extra == 'extra-20-nmp-safe-synthesizer-cpu') or (sys_platform == 'linux' and extra == 'extra-20-nmp-safe-synthesizer-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128')" }, { name = "pytest", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-12-nemoplatform-cu128') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-12-nemoplatform-cu128' and extra == 'extra-20-nmp-safe-synthesizer-cpu') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-20-nmp-safe-synthesizer-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'darwin' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-12-nemoplatform-cu128') or (sys_platform == 'darwin' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'darwin' and extra == 'extra-12-nemoplatform-cu128' and extra == 'extra-20-nmp-safe-synthesizer-cpu') or (sys_platform == 'darwin' and extra == 'extra-20-nmp-safe-synthesizer-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-12-nemoplatform-cu128') or (sys_platform == 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'linux' and extra == 'extra-12-nemoplatform-cu128' and extra == 'extra-20-nmp-safe-synthesizer-cpu') or (sys_platform == 'linux' and extra == 'extra-20-nmp-safe-synthesizer-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128')" }, { name = "pytest-asyncio", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-12-nemoplatform-cu128') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-12-nemoplatform-cu128' and extra == 'extra-20-nmp-safe-synthesizer-cpu') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-20-nmp-safe-synthesizer-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'darwin' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-12-nemoplatform-cu128') or (sys_platform == 'darwin' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'darwin' and extra == 'extra-12-nemoplatform-cu128' and extra == 'extra-20-nmp-safe-synthesizer-cpu') or (sys_platform == 'darwin' and extra == 'extra-20-nmp-safe-synthesizer-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-12-nemoplatform-cu128') or (sys_platform == 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'linux' and extra == 'extra-12-nemoplatform-cu128' and extra == 'extra-20-nmp-safe-synthesizer-cpu') or (sys_platform == 'linux' and extra == 'extra-20-nmp-safe-synthesizer-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128')" }, { name = "pytest-env", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-12-nemoplatform-cu128') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-12-nemoplatform-cu128' and extra == 'extra-20-nmp-safe-synthesizer-cpu') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-20-nmp-safe-synthesizer-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'darwin' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-12-nemoplatform-cu128') or (sys_platform == 'darwin' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'darwin' and extra == 'extra-12-nemoplatform-cu128' and extra == 'extra-20-nmp-safe-synthesizer-cpu') or (sys_platform == 'darwin' and extra == 'extra-20-nmp-safe-synthesizer-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-12-nemoplatform-cu128') or (sys_platform == 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'linux' and extra == 'extra-12-nemoplatform-cu128' and extra == 'extra-20-nmp-safe-synthesizer-cpu') or (sys_platform == 'linux' and extra == 'extra-20-nmp-safe-synthesizer-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128')" }, - { name = "pytest-postgresql", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-12-nemoplatform-cu128') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-12-nemoplatform-cu128' and extra == 'extra-20-nmp-safe-synthesizer-cpu') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-20-nmp-safe-synthesizer-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'darwin' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-12-nemoplatform-cu128') or (sys_platform == 'darwin' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'darwin' and extra == 'extra-12-nemoplatform-cu128' and extra == 'extra-20-nmp-safe-synthesizer-cpu') or (sys_platform == 'darwin' and extra == 'extra-20-nmp-safe-synthesizer-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-12-nemoplatform-cu128') or (sys_platform == 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'linux' and extra == 'extra-12-nemoplatform-cu128' and extra == 'extra-20-nmp-safe-synthesizer-cpu') or (sys_platform == 'linux' and extra == 'extra-20-nmp-safe-synthesizer-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128')" }, { name = "setuptools", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-12-nemoplatform-cu128') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-12-nemoplatform-cu128' and extra == 'extra-20-nmp-safe-synthesizer-cpu') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-20-nmp-safe-synthesizer-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'darwin' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-12-nemoplatform-cu128') or (sys_platform == 'darwin' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'darwin' and extra == 'extra-12-nemoplatform-cu128' and extra == 'extra-20-nmp-safe-synthesizer-cpu') or (sys_platform == 'darwin' and extra == 'extra-20-nmp-safe-synthesizer-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-12-nemoplatform-cu128') or (sys_platform == 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'linux' and extra == 'extra-12-nemoplatform-cu128' and extra == 'extra-20-nmp-safe-synthesizer-cpu') or (sys_platform == 'linux' and extra == 'extra-20-nmp-safe-synthesizer-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128')" }, { name = "testcontainers", extra = ["clickhouse"], marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-12-nemoplatform-cu128') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-12-nemoplatform-cu128' and extra == 'extra-20-nmp-safe-synthesizer-cpu') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-20-nmp-safe-synthesizer-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'darwin' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-12-nemoplatform-cu128') or (sys_platform == 'darwin' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'darwin' and extra == 'extra-12-nemoplatform-cu128' and extra == 'extra-20-nmp-safe-synthesizer-cpu') or (sys_platform == 'darwin' and extra == 'extra-20-nmp-safe-synthesizer-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-12-nemoplatform-cu128') or (sys_platform == 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'linux' and extra == 'extra-12-nemoplatform-cu128' and extra == 'extra-20-nmp-safe-synthesizer-cpu') or (sys_platform == 'linux' and extra == 'extra-20-nmp-safe-synthesizer-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128')" }, - { name = "types-requests", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-12-nemoplatform-cu128') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-12-nemoplatform-cu128' and extra == 'extra-20-nmp-safe-synthesizer-cpu') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-20-nmp-safe-synthesizer-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'darwin' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-12-nemoplatform-cu128') or (sys_platform == 'darwin' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'darwin' and extra == 'extra-12-nemoplatform-cu128' and extra == 'extra-20-nmp-safe-synthesizer-cpu') or (sys_platform == 'darwin' and extra == 'extra-20-nmp-safe-synthesizer-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-12-nemoplatform-cu128') or (sys_platform == 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'linux' and extra == 'extra-12-nemoplatform-cu128' and extra == 'extra-20-nmp-safe-synthesizer-cpu') or (sys_platform == 'linux' and extra == 'extra-20-nmp-safe-synthesizer-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128')" }, ] [package.metadata] requires-dist = [ - { name = "alembic", specifier = ">=1.10.4,<2.0.0" }, - { name = "asyncpg", specifier = ">=0.30.0,<1.0.0" }, - { name = "base58", specifier = ">=2.1.1" }, - { name = "celery", specifier = ">=5.5.3" }, { name = "clickhouse-connect", specifier = ">=0.7,<1.0" }, { name = "fastapi", specifier = ">=0.115.5,<1.0.0" }, - { name = "flower", specifier = ">=2.0.1" }, - { name = "httpx", specifier = ">=0.24.1,<1.0.0" }, - { name = "huggingface-hub", specifier = ">=1.0.1,<2.0.0" }, { name = "nmp-common", editable = "packages/nmp_common" }, - { name = "openai", specifier = ">=1.51.0" }, - { name = "opentelemetry-api", specifier = ">=1.27.0" }, - { name = "opentelemetry-exporter-otlp", specifier = ">=1.27.0" }, - { name = "opentelemetry-instrumentation-fastapi", specifier = ">=0.48b0" }, - { name = "opentelemetry-instrumentation-openai", specifier = ">=0.33.9" }, { name = "opentelemetry-proto", specifier = ">=1.27.0" }, - { name = "opentelemetry-sdk", specifier = ">=1.27.0" }, - { name = "psycopg2-binary", specifier = ">=2.9.10" }, { name = "pydantic", specifier = ">=2.9.2,<3.0.0" }, { name = "pydantic-settings", specifier = ">=2.6.1,<3.0.0" }, - { name = "python-dotenv", specifier = ">=1.0.0,<2.0.0" }, - { name = "pyyaml", specifier = ">=6.0.2,<7.0.0" }, - { name = "redis", specifier = ">=5.0.1" }, - { name = "requests", specifier = ">=2.32.3,<3.0.0" }, - { name = "sqlalchemy", specifier = ">=2.0" }, - { name = "types-requests", specifier = ">=2.32.0.20241016,<3.0.0.0" }, { name = "uvicorn", specifier = ">=0.22.0,<1.0.0" }, - { name = "watchdog", extras = ["watchmedo"], specifier = ">=6.0.0" }, ] [package.metadata.requires-dev] @@ -7681,16 +7623,11 @@ dev = [ { name = "mypy", specifier = ">=1.13.0,<2.0.0" }, { name = "nmp-testing", editable = "packages/nmp_testing" }, { name = "pre-commit", specifier = ">=3.7.1,<4.0.0" }, - { name = "psycopg", extras = ["binary", "pool"], specifier = ">=3.2.4" }, - { name = "psycopg2-binary", specifier = ">=2.9.10" }, { name = "pytest", specifier = ">=9.0.3" }, { name = "pytest-asyncio", specifier = ">=0.21.0,<2.0.0" }, { name = "pytest-env", specifier = ">=1.1.5,<2.0.0" }, - { name = "pytest-postgresql", specifier = ">=6.1.1,<7.0.0" }, { name = "setuptools", specifier = ">=78.1.1" }, { name = "testcontainers", extras = ["clickhouse"], specifier = ">=4.12.0" }, - { name = "testcontainers", extras = ["postgres"], specifier = ">=4.12.0" }, - { name = "types-requests", specifier = ">=2.31.0,<3.0.0" }, ] [[package]] @@ -9203,15 +9140,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/a3/de/e5bf2556ebd6db12590788207575c7c75b1de62f5ddc8b4916b668e04e6b/polling2-0.5.0-py2.py3-none-any.whl", hash = "sha256:ad86d56fbd7502f0856cac2d0109d595c18fa6c7fb12c88cee5e5d16c17286c1", size = 6431, upload-time = "2021-07-19T18:06:53.681Z" }, ] -[[package]] -name = "port-for" -version = "1.0.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/88/a0/80a64e8cc096c7a9d0f546a28994af849b4775afc5e4ee44bf2739a55115/port_for-1.0.0.tar.gz", hash = "sha256:404d161b1b2c82e2f6b31d8646396b4847d02bf5ee10068c92b7263657a14582", size = 21681, upload-time = "2025-09-30T10:22:51.149Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/70/2c/b1faca65b9728b4ac43f0bee4bb9e7294bd0a62cc2ee59fd59403bf575f6/port_for-1.0.0-py3-none-any.whl", hash = "sha256:35a848b98cf4cc075fe80dc49ae5c3a78e3ca345a23bd39bf5252277b4eef5c2", size = 17544, upload-time = "2025-09-30T10:22:49.878Z" }, -] - [[package]] name = "portalocker" version = "3.2.0" @@ -9402,78 +9330,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/8c/c7/7bb2e321574b10df20cbde462a94e2b71d05f9bbda251ef27d104668306a/psutil-7.2.2-cp37-abi3-win_arm64.whl", hash = "sha256:8c233660f575a5a89e6d4cb65d9f938126312bca76d8fe087b947b3a1aaac9ee", size = 134617, upload-time = "2026-01-28T18:15:36.514Z" }, ] -[[package]] -name = "psycopg" -version = "3.3.3" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "typing-extensions", marker = "(python_full_version < '3.13' and platform_machine == 'arm64' and sys_platform == 'darwin') or (python_full_version < '3.13' and platform_machine == 'aarch64' and sys_platform == 'linux') or (python_full_version < '3.13' and platform_machine == 'x86_64' and sys_platform == 'linux') or (python_full_version >= '3.13' and sys_platform == 'darwin' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-12-nemoplatform-cu128') or (python_full_version >= '3.13' and sys_platform == 'darwin' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (python_full_version >= '3.13' and sys_platform == 'darwin' and extra == 'extra-12-nemoplatform-cu128' and extra == 'extra-20-nmp-safe-synthesizer-cpu') or (python_full_version >= '3.13' and sys_platform == 'darwin' and extra == 'extra-20-nmp-safe-synthesizer-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (platform_machine != 'aarch64' and platform_machine != 'x86_64' and sys_platform == 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-12-nemoplatform-cu128') or (platform_machine != 'aarch64' and platform_machine != 'x86_64' and sys_platform == 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (platform_machine != 'aarch64' and platform_machine != 'x86_64' and sys_platform == 'linux' and extra == 'extra-12-nemoplatform-cu128' and extra == 'extra-20-nmp-safe-synthesizer-cpu') or (platform_machine != 'aarch64' and platform_machine != 'x86_64' and sys_platform == 'linux' and extra == 'extra-20-nmp-safe-synthesizer-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (platform_machine != 'arm64' and sys_platform == 'darwin' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-12-nemoplatform-cu128') or (platform_machine != 'arm64' and sys_platform == 'darwin' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (platform_machine != 'arm64' and sys_platform == 'darwin' and extra == 'extra-12-nemoplatform-cu128' and extra == 'extra-20-nmp-safe-synthesizer-cpu') or (platform_machine != 'arm64' and sys_platform == 'darwin' and extra == 'extra-20-nmp-safe-synthesizer-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (platform_machine == 'aarch64' and sys_platform == 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-12-nemoplatform-cu128') or (platform_machine == 'aarch64' and sys_platform == 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (platform_machine == 'aarch64' and sys_platform == 'linux' and extra == 'extra-12-nemoplatform-cu128' and extra == 'extra-20-nmp-safe-synthesizer-cpu') or (platform_machine == 'aarch64' and sys_platform == 'linux' and extra == 'extra-20-nmp-safe-synthesizer-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (platform_machine == 'x86_64' and sys_platform == 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-12-nemoplatform-cu128') or (platform_machine == 'x86_64' and sys_platform == 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (platform_machine == 'x86_64' and sys_platform == 'linux' and extra == 'extra-12-nemoplatform-cu128' and extra == 'extra-20-nmp-safe-synthesizer-cpu') or (platform_machine == 'x86_64' and sys_platform == 'linux' and extra == 'extra-20-nmp-safe-synthesizer-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-12-nemoplatform-cu128') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-12-nemoplatform-cu128' and extra == 'extra-20-nmp-safe-synthesizer-cpu') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-20-nmp-safe-synthesizer-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128')" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/d3/b6/379d0a960f8f435ec78720462fd94c4863e7a31237cf81bf76d0af5883bf/psycopg-3.3.3.tar.gz", hash = "sha256:5e9a47458b3c1583326513b2556a2a9473a1001a56c9efe9e587245b43148dd9", size = 165624, upload-time = "2026-02-18T16:52:16.546Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/c8/5b/181e2e3becb7672b502f0ed7f16ed7352aca7c109cfb94cf3878a9186db9/psycopg-3.3.3-py3-none-any.whl", hash = "sha256:f96525a72bcfade6584ab17e89de415ff360748c766f0106959144dcbb38c698", size = 212768, upload-time = "2026-02-18T16:46:27.365Z" }, -] - -[package.optional-dependencies] -binary = [ - { name = "psycopg-binary", marker = "(implementation_name != 'pypy' and platform_machine == 'arm64' and sys_platform == 'darwin') or (implementation_name != 'pypy' and platform_machine == 'aarch64' and sys_platform == 'linux') or (implementation_name != 'pypy' and platform_machine == 'x86_64' and sys_platform == 'linux') or (implementation_name == 'pypy' and sys_platform == 'darwin' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-12-nemoplatform-cu128') or (implementation_name == 'pypy' and sys_platform == 'darwin' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (implementation_name == 'pypy' and sys_platform == 'darwin' and extra == 'extra-12-nemoplatform-cu128' and extra == 'extra-20-nmp-safe-synthesizer-cpu') or (implementation_name == 'pypy' and sys_platform == 'darwin' and extra == 'extra-20-nmp-safe-synthesizer-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (platform_machine != 'aarch64' and platform_machine != 'x86_64' and sys_platform == 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-12-nemoplatform-cu128') or (platform_machine != 'aarch64' and platform_machine != 'x86_64' and sys_platform == 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (platform_machine != 'aarch64' and platform_machine != 'x86_64' and sys_platform == 'linux' and extra == 'extra-12-nemoplatform-cu128' and extra == 'extra-20-nmp-safe-synthesizer-cpu') or (platform_machine != 'aarch64' and platform_machine != 'x86_64' and sys_platform == 'linux' and extra == 'extra-20-nmp-safe-synthesizer-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (platform_machine != 'arm64' and sys_platform == 'darwin' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-12-nemoplatform-cu128') or (platform_machine != 'arm64' and sys_platform == 'darwin' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (platform_machine != 'arm64' and sys_platform == 'darwin' and extra == 'extra-12-nemoplatform-cu128' and extra == 'extra-20-nmp-safe-synthesizer-cpu') or (platform_machine != 'arm64' and sys_platform == 'darwin' and extra == 'extra-20-nmp-safe-synthesizer-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (platform_machine == 'aarch64' and sys_platform == 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-12-nemoplatform-cu128') or (platform_machine == 'aarch64' and sys_platform == 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (platform_machine == 'aarch64' and sys_platform == 'linux' and extra == 'extra-12-nemoplatform-cu128' and extra == 'extra-20-nmp-safe-synthesizer-cpu') or (platform_machine == 'aarch64' and sys_platform == 'linux' and extra == 'extra-20-nmp-safe-synthesizer-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (platform_machine == 'x86_64' and sys_platform == 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-12-nemoplatform-cu128') or (platform_machine == 'x86_64' and sys_platform == 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (platform_machine == 'x86_64' and sys_platform == 'linux' and extra == 'extra-12-nemoplatform-cu128' and extra == 'extra-20-nmp-safe-synthesizer-cpu') or (platform_machine == 'x86_64' and sys_platform == 'linux' and extra == 'extra-20-nmp-safe-synthesizer-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-12-nemoplatform-cu128') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-12-nemoplatform-cu128' and extra == 'extra-20-nmp-safe-synthesizer-cpu') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-20-nmp-safe-synthesizer-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128')" }, -] -pool = [ - { name = "psycopg-pool", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-12-nemoplatform-cu128') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-12-nemoplatform-cu128' and extra == 'extra-20-nmp-safe-synthesizer-cpu') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-20-nmp-safe-synthesizer-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'darwin' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-12-nemoplatform-cu128') or (sys_platform == 'darwin' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'darwin' and extra == 'extra-12-nemoplatform-cu128' and extra == 'extra-20-nmp-safe-synthesizer-cpu') or (sys_platform == 'darwin' and extra == 'extra-20-nmp-safe-synthesizer-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-12-nemoplatform-cu128') or (sys_platform == 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'linux' and extra == 'extra-12-nemoplatform-cu128' and extra == 'extra-20-nmp-safe-synthesizer-cpu') or (sys_platform == 'linux' and extra == 'extra-20-nmp-safe-synthesizer-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128')" }, -] - -[[package]] -name = "psycopg-binary" -version = "3.3.3" -source = { registry = "https://pypi.org/simple" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/be/c0/b389119dd754483d316805260f3e73cdcad97925839107cc7a296f6132b1/psycopg_binary-3.3.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a89bb9ee11177b2995d87186b1d9fa892d8ea725e85eab28c6525e4cc14ee048", size = 4609740, upload-time = "2026-02-18T16:47:51.093Z" }, - { url = "https://files.pythonhosted.org/packages/cf/e3/9976eef20f61840285174d360da4c820a311ab39d6b82fa09fbb545be825/psycopg_binary-3.3.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:9f7d0cf072c6fbac3795b08c98ef9ea013f11db609659dcfc6b1f6cc31f9e181", size = 4676837, upload-time = "2026-02-18T16:47:55.523Z" }, - { url = "https://files.pythonhosted.org/packages/9f/f2/d28ba2f7404fd7f68d41e8a11df86313bd646258244cb12a8dd83b868a97/psycopg_binary-3.3.3-cp311-cp311-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:90eecd93073922f085967f3ed3a98ba8c325cbbc8c1a204e300282abd2369e13", size = 5497070, upload-time = "2026-02-18T16:47:59.929Z" }, - { url = "https://files.pythonhosted.org/packages/de/2f/6c5c54b815edeb30a281cfcea96dc93b3bb6be939aea022f00cab7aa1420/psycopg_binary-3.3.3-cp311-cp311-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:dac7ee2f88b4d7bb12837989ca354c38d400eeb21bce3b73dac02622f0a3c8d6", size = 5172410, upload-time = "2026-02-18T16:48:05.665Z" }, - { url = "https://files.pythonhosted.org/packages/51/75/8206c7008b57de03c1ada46bd3110cc3743f3fd9ed52031c4601401d766d/psycopg_binary-3.3.3-cp311-cp311-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b62cf8784eb6d35beaee1056d54caf94ec6ecf2b7552395e305518ab61eb8fd2", size = 6763408, upload-time = "2026-02-18T16:48:13.541Z" }, - { url = "https://files.pythonhosted.org/packages/d4/5a/ea1641a1e6c8c8b3454b0fcb43c3045133a8b703e6e824fae134088e63bd/psycopg_binary-3.3.3-cp311-cp311-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:a39f34c9b18e8f6794cca17bfbcd64572ca2482318db644268049f8c738f35a6", size = 5006255, upload-time = "2026-02-18T16:48:22.176Z" }, - { url = "https://files.pythonhosted.org/packages/aa/fb/538df099bf55ae1637d52d7ccb6b9620b535a40f4c733897ac2b7bb9e14c/psycopg_binary-3.3.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:883d68d48ca9ff3cb3d10c5fdebea02c79b48eecacdddbf7cce6e7cdbdc216b8", size = 4532694, upload-time = "2026-02-18T16:48:27.338Z" }, - { url = "https://files.pythonhosted.org/packages/a1/d1/00780c0e187ea3c13dfc53bd7060654b2232cd30df562aac91a5f1c545ac/psycopg_binary-3.3.3-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:cab7bc3d288d37a80aa8c0820033250c95e40b1c2b5c57cf59827b19c2a8b69d", size = 4222833, upload-time = "2026-02-18T16:48:31.221Z" }, - { url = "https://files.pythonhosted.org/packages/7a/34/a07f1ff713c51d64dc9f19f2c32be80299a2055d5d109d5853662b922cb4/psycopg_binary-3.3.3-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:56c767007ca959ca32f796b42379fc7e1ae2ed085d29f20b05b3fc394f3715cc", size = 3952818, upload-time = "2026-02-18T16:48:35.869Z" }, - { url = "https://files.pythonhosted.org/packages/d3/67/d33f268a7759b4445f3c9b5a181039b01af8c8263c865c1be7a6444d4749/psycopg_binary-3.3.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:da2f331a01af232259a21573a01338530c6016dcfad74626c01330535bcd8628", size = 4258061, upload-time = "2026-02-18T16:48:41.365Z" }, - { url = "https://files.pythonhosted.org/packages/b4/3b/0d8d2c5e8e29ccc07d28c8af38445d9d9abcd238d590186cac82ee71fc84/psycopg_binary-3.3.3-cp311-cp311-win_amd64.whl", hash = "sha256:19f93235ece6dbfc4036b5e4f6d8b13f0b8f2b3eeb8b0bd2936d406991bcdd40", size = 3558915, upload-time = "2026-02-18T16:48:46.679Z" }, - { url = "https://files.pythonhosted.org/packages/90/15/021be5c0cbc5b7c1ab46e91cc3434eb42569f79a0592e67b8d25e66d844d/psycopg_binary-3.3.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:6698dbab5bcef8fdb570fc9d35fd9ac52041771bfcfe6fd0fc5f5c4e36f1e99d", size = 4591170, upload-time = "2026-02-18T16:48:55.594Z" }, - { url = "https://files.pythonhosted.org/packages/f1/54/a60211c346c9a2f8c6b272b5f2bbe21f6e11800ce7f61e99ba75cf8b63e1/psycopg_binary-3.3.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:329ff393441e75f10b673ae99ab45276887993d49e65f141da20d915c05aafd8", size = 4670009, upload-time = "2026-02-18T16:49:03.608Z" }, - { url = "https://files.pythonhosted.org/packages/c1/53/ac7c18671347c553362aadbf65f92786eef9540676ca24114cc02f5be405/psycopg_binary-3.3.3-cp312-cp312-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:eb072949b8ebf4082ae24289a2b0fd724da9adc8f22743409d6fd718ddb379df", size = 5469735, upload-time = "2026-02-18T16:49:10.128Z" }, - { url = "https://files.pythonhosted.org/packages/7f/c3/4f4e040902b82a344eff1c736cde2f2720f127fe939c7e7565706f96dd44/psycopg_binary-3.3.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:263a24f39f26e19ed7fc982d7859a36f17841b05bebad3eb47bb9cd2dd785351", size = 5152919, upload-time = "2026-02-18T16:49:16.335Z" }, - { url = "https://files.pythonhosted.org/packages/0c/e7/d929679c6a5c212bcf738806c7c89f5b3d0919f2e1685a0e08d6ff877945/psycopg_binary-3.3.3-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5152d50798c2fa5bd9b68ec68eb68a1b71b95126c1d70adaa1a08cd5eefdc23d", size = 6738785, upload-time = "2026-02-18T16:49:22.687Z" }, - { url = "https://files.pythonhosted.org/packages/69/b0/09703aeb69a9443d232d7b5318d58742e8ca51ff79f90ffe6b88f1db45e7/psycopg_binary-3.3.3-cp312-cp312-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:9d6a1e56dd267848edb824dbeb08cf5bac649e02ee0b03ba883ba3f4f0bd54f2", size = 4979008, upload-time = "2026-02-18T16:49:27.313Z" }, - { url = "https://files.pythonhosted.org/packages/cc/a6/e662558b793c6e13a7473b970fee327d635270e41eded3090ef14045a6a5/psycopg_binary-3.3.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:73eaaf4bb04709f545606c1db2f65f4000e8a04cdbf3e00d165a23004692093e", size = 4508255, upload-time = "2026-02-18T16:49:31.575Z" }, - { url = "https://files.pythonhosted.org/packages/5f/7f/0f8b2e1d5e0093921b6f324a948a5c740c1447fbb45e97acaf50241d0f39/psycopg_binary-3.3.3-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:162e5675efb4704192411eaf8e00d07f7960b679cd3306e7efb120bb8d9456cc", size = 4189166, upload-time = "2026-02-18T16:49:35.801Z" }, - { url = "https://files.pythonhosted.org/packages/92/ec/ce2e91c33bc8d10b00c87e2f6b0fb570641a6a60042d6a9ae35658a3a797/psycopg_binary-3.3.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:fab6b5e37715885c69f5d091f6ff229be71e235f272ebaa35158d5a46fd548a0", size = 3924544, upload-time = "2026-02-18T16:49:41.129Z" }, - { url = "https://files.pythonhosted.org/packages/c5/2f/7718141485f73a924205af60041c392938852aa447a94c8cbd222ff389a1/psycopg_binary-3.3.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:a4aab31bd6d1057f287c96c0effca3a25584eb9cc702f282ecb96ded7814e830", size = 4235297, upload-time = "2026-02-18T16:49:46.726Z" }, - { url = "https://files.pythonhosted.org/packages/57/f9/1add717e2643a003bbde31b1b220172e64fbc0cb09f06429820c9173f7fc/psycopg_binary-3.3.3-cp312-cp312-win_amd64.whl", hash = "sha256:59aa31fe11a0e1d1bcc2ce37ed35fe2ac84cd65bb9036d049b1a1c39064d0f14", size = 3547659, upload-time = "2026-02-18T16:49:52.999Z" }, - { url = "https://files.pythonhosted.org/packages/03/0a/cac9fdf1df16a269ba0e5f0f06cac61f826c94cadb39df028cdfe19d3a33/psycopg_binary-3.3.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:05f32239aec25c5fb15f7948cffdc2dc0dac098e48b80a140e4ba32b572a2e7d", size = 4590414, upload-time = "2026-02-18T16:50:01.441Z" }, - { url = "https://files.pythonhosted.org/packages/9c/c0/d8f8508fbf440edbc0099b1abff33003cd80c9e66eb3a1e78834e3fb4fb9/psycopg_binary-3.3.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:7c84f9d214f2d1de2fafebc17fa68ac3f6561a59e291553dfc45ad299f4898c1", size = 4669021, upload-time = "2026-02-18T16:50:08.803Z" }, - { url = "https://files.pythonhosted.org/packages/04/05/097016b77e343b4568feddf12c72171fc513acef9a4214d21b9478569068/psycopg_binary-3.3.3-cp313-cp313-manylinux2014_ppc64le.manylinux_2_17_ppc64le.whl", hash = "sha256:e77957d2ba17cada11be09a5066d93026cdb61ada7c8893101d7fe1c6e1f3925", size = 5467453, upload-time = "2026-02-18T16:50:14.985Z" }, - { url = "https://files.pythonhosted.org/packages/91/23/73244e5feb55b5ca109cede6e97f32ef45189f0fdac4c80d75c99862729d/psycopg_binary-3.3.3-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:42961609ac07c232a427da7c87a468d3c82fee6762c220f38e37cfdacb2b178d", size = 5151135, upload-time = "2026-02-18T16:50:24.82Z" }, - { url = "https://files.pythonhosted.org/packages/11/49/5309473b9803b207682095201d8708bbc7842ddf3f192488a69204e36455/psycopg_binary-3.3.3-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ae07a3114313dd91fce686cab2f4c44af094398519af0e0f854bc707e1aeedf1", size = 6737315, upload-time = "2026-02-18T16:50:35.106Z" }, - { url = "https://files.pythonhosted.org/packages/d4/5d/03abe74ef34d460b33c4d9662bf6ec1dd38888324323c1a1752133c10377/psycopg_binary-3.3.3-cp313-cp313-manylinux_2_38_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:d257c58d7b36a621dcce1d01476ad8b60f12d80eb1406aee4cf796f88b2ae482", size = 4979783, upload-time = "2026-02-18T16:50:42.067Z" }, - { url = "https://files.pythonhosted.org/packages/f0/6c/3fbf8e604e15f2f3752900434046c00c90bb8764305a1b81112bff30ba24/psycopg_binary-3.3.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:07c7211f9327d522c9c47560cae00a4ecf6687f4e02d779d035dd3177b41cb12", size = 4509023, upload-time = "2026-02-18T16:50:50.116Z" }, - { url = "https://files.pythonhosted.org/packages/9c/6b/1a06b43b7c7af756c80b67eac8bfaa51d77e68635a8a8d246e4f0bb7604a/psycopg_binary-3.3.3-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:8e7e9eca9b363dbedeceeadd8be97149d2499081f3c52d141d7cd1f395a91f83", size = 4185874, upload-time = "2026-02-18T16:50:55.97Z" }, - { url = "https://files.pythonhosted.org/packages/2b/d3/bf49e3dcaadba510170c8d111e5e69e5ae3f981c1554c5bb71c75ce354bb/psycopg_binary-3.3.3-cp313-cp313-musllinux_1_2_riscv64.whl", hash = "sha256:cb85b1d5702877c16f28d7b92ba030c1f49ebcc9b87d03d8c10bf45a2f1c7508", size = 3925668, upload-time = "2026-02-18T16:51:03.299Z" }, - { url = "https://files.pythonhosted.org/packages/f8/92/0aac830ed6a944fe334404e1687a074e4215630725753f0e3e9a9a595b62/psycopg_binary-3.3.3-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4d4606c84d04b80f9138d72f1e28c6c02dc5ae0c7b8f3f8aaf89c681ce1cd1b1", size = 4234973, upload-time = "2026-02-18T16:51:09.097Z" }, - { url = "https://files.pythonhosted.org/packages/2e/96/102244653ee5a143ece5afe33f00f52fe64e389dfce8dbc87580c6d70d3d/psycopg_binary-3.3.3-cp313-cp313-win_amd64.whl", hash = "sha256:74eae563166ebf74e8d950ff359be037b85723d99ca83f57d9b244a871d6c13b", size = 3551342, upload-time = "2026-02-18T16:51:13.892Z" }, -] - -[[package]] -name = "psycopg-pool" -version = "3.3.0" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "typing-extensions", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-12-nemoplatform-cu128') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-12-nemoplatform-cu128' and extra == 'extra-20-nmp-safe-synthesizer-cpu') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-20-nmp-safe-synthesizer-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'darwin' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-12-nemoplatform-cu128') or (sys_platform == 'darwin' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'darwin' and extra == 'extra-12-nemoplatform-cu128' and extra == 'extra-20-nmp-safe-synthesizer-cpu') or (sys_platform == 'darwin' and extra == 'extra-20-nmp-safe-synthesizer-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-12-nemoplatform-cu128') or (sys_platform == 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'linux' and extra == 'extra-12-nemoplatform-cu128' and extra == 'extra-20-nmp-safe-synthesizer-cpu') or (sys_platform == 'linux' and extra == 'extra-20-nmp-safe-synthesizer-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128')" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/56/9a/9470d013d0d50af0da9c4251614aeb3c1823635cab3edc211e3839db0bcf/psycopg_pool-3.3.0.tar.gz", hash = "sha256:fa115eb2860bd88fce1717d75611f41490dec6135efb619611142b24da3f6db5", size = 31606, upload-time = "2025-12-01T11:34:33.11Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/e7/c3/26b8a0908a9db249de3b4169692e1c7c19048a9bc41a4d3209cee7dbb758/psycopg_pool-3.3.0-py3-none-any.whl", hash = "sha256:2e44329155c410b5e8666372db44276a8b1ebd8c90f1c3026ebba40d4bc81063", size = 39995, upload-time = "2025-12-01T11:34:29.761Z" }, -] - [[package]] name = "psycopg2-binary" version = "2.9.11" @@ -9996,22 +9852,6 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/5a/cc/06253936f4a7fa2e0f48dfe6d851d9c56df896a9ab09ac019d70b760619c/pytest_mock-3.15.1-py3-none-any.whl", hash = "sha256:0a25e2eb88fe5168d535041d09a4529a188176ae608a6d249ee65abc0949630d", size = 10095, upload-time = "2025-09-16T16:37:25.734Z" }, ] -[[package]] -name = "pytest-postgresql" -version = "6.1.1" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "mirakuru", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-12-nemoplatform-cu128') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-12-nemoplatform-cu128' and extra == 'extra-20-nmp-safe-synthesizer-cpu') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-20-nmp-safe-synthesizer-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'darwin' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-12-nemoplatform-cu128') or (sys_platform == 'darwin' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'darwin' and extra == 'extra-12-nemoplatform-cu128' and extra == 'extra-20-nmp-safe-synthesizer-cpu') or (sys_platform == 'darwin' and extra == 'extra-20-nmp-safe-synthesizer-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-12-nemoplatform-cu128') or (sys_platform == 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'linux' and extra == 'extra-12-nemoplatform-cu128' and extra == 'extra-20-nmp-safe-synthesizer-cpu') or (sys_platform == 'linux' and extra == 'extra-20-nmp-safe-synthesizer-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128')" }, - { name = "port-for", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-12-nemoplatform-cu128') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-12-nemoplatform-cu128' and extra == 'extra-20-nmp-safe-synthesizer-cpu') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-20-nmp-safe-synthesizer-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'darwin' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-12-nemoplatform-cu128') or (sys_platform == 'darwin' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'darwin' and extra == 'extra-12-nemoplatform-cu128' and extra == 'extra-20-nmp-safe-synthesizer-cpu') or (sys_platform == 'darwin' and extra == 'extra-20-nmp-safe-synthesizer-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-12-nemoplatform-cu128') or (sys_platform == 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'linux' and extra == 'extra-12-nemoplatform-cu128' and extra == 'extra-20-nmp-safe-synthesizer-cpu') or (sys_platform == 'linux' and extra == 'extra-20-nmp-safe-synthesizer-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128')" }, - { name = "psycopg", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-12-nemoplatform-cu128') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-12-nemoplatform-cu128' and extra == 'extra-20-nmp-safe-synthesizer-cpu') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-20-nmp-safe-synthesizer-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'darwin' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-12-nemoplatform-cu128') or (sys_platform == 'darwin' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'darwin' and extra == 'extra-12-nemoplatform-cu128' and extra == 'extra-20-nmp-safe-synthesizer-cpu') or (sys_platform == 'darwin' and extra == 'extra-20-nmp-safe-synthesizer-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-12-nemoplatform-cu128') or (sys_platform == 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'linux' and extra == 'extra-12-nemoplatform-cu128' and extra == 'extra-20-nmp-safe-synthesizer-cpu') or (sys_platform == 'linux' and extra == 'extra-20-nmp-safe-synthesizer-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128')" }, - { name = "pytest", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-12-nemoplatform-cu128') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-12-nemoplatform-cu128' and extra == 'extra-20-nmp-safe-synthesizer-cpu') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-20-nmp-safe-synthesizer-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'darwin' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-12-nemoplatform-cu128') or (sys_platform == 'darwin' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'darwin' and extra == 'extra-12-nemoplatform-cu128' and extra == 'extra-20-nmp-safe-synthesizer-cpu') or (sys_platform == 'darwin' and extra == 'extra-20-nmp-safe-synthesizer-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-12-nemoplatform-cu128') or (sys_platform == 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'linux' and extra == 'extra-12-nemoplatform-cu128' and extra == 'extra-20-nmp-safe-synthesizer-cpu') or (sys_platform == 'linux' and extra == 'extra-20-nmp-safe-synthesizer-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128')" }, - { name = "setuptools", marker = "(platform_machine == 'arm64' and sys_platform == 'darwin') or (platform_machine == 'aarch64' and sys_platform == 'linux') or (platform_machine == 'x86_64' and sys_platform == 'linux') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-12-nemoplatform-cu128') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-12-nemoplatform-cu128' and extra == 'extra-20-nmp-safe-synthesizer-cpu') or (sys_platform != 'darwin' and sys_platform != 'linux' and extra == 'extra-20-nmp-safe-synthesizer-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'darwin' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-12-nemoplatform-cu128') or (sys_platform == 'darwin' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'darwin' and extra == 'extra-12-nemoplatform-cu128' and extra == 'extra-20-nmp-safe-synthesizer-cpu') or (sys_platform == 'darwin' and extra == 'extra-20-nmp-safe-synthesizer-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-12-nemoplatform-cu128') or (sys_platform == 'linux' and extra == 'extra-12-nemoplatform-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128') or (sys_platform == 'linux' and extra == 'extra-12-nemoplatform-cu128' and extra == 'extra-20-nmp-safe-synthesizer-cpu') or (sys_platform == 'linux' and extra == 'extra-20-nmp-safe-synthesizer-cpu' and extra == 'extra-20-nmp-safe-synthesizer-cu128')" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/88/51/d56a56fd852f0e3da5d001cb90815c793f904e387495e63e22ea90d2de1d/pytest_postgresql-6.1.1.tar.gz", hash = "sha256:f996637367e6aecebba1349da52eea95340bdb434c90e4b79739e62c656056e2", size = 47500, upload-time = "2024-09-05T09:12:03.431Z" } -wheels = [ - { url = "https://files.pythonhosted.org/packages/db/c8/ca523b7dac9253cbfd60466aa0993b4fe5c558844585e7a0c0ad21d00bec/pytest_postgresql-6.1.1-py3-none-any.whl", hash = "sha256:bd4c0970d25685ac3d34d42263fcbfbf134bf02d22519fce7e1ccf4122d8b99a", size = 40529, upload-time = "2024-09-05T09:12:01.539Z" }, -] - [[package]] name = "pytest-rerunfailures" version = "16.1" From 3da66a719c53726ff928e262b68361e56c2cb054 Mon Sep 17 00:00:00 2001 From: Brian Newsom Date: Thu, 28 May 2026 10:24:03 -0600 Subject: [PATCH 2/4] chore: clean up health check readiness Signed-off-by: Brian Newsom --- .../src/nmp/intake/api/v2/health/__init__.py | 4 -- .../src/nmp/intake/api/v2/health/endpoints.py | 59 ------------------- services/intake/src/nmp/intake/service.py | 12 +--- .../spans/test_clickhouse_bootstrap.py | 4 +- .../intake/tests/test_clickhouse_startup.py | 6 +- 5 files changed, 7 insertions(+), 78 deletions(-) delete mode 100644 services/intake/src/nmp/intake/api/v2/health/__init__.py delete mode 100644 services/intake/src/nmp/intake/api/v2/health/endpoints.py diff --git a/services/intake/src/nmp/intake/api/v2/health/__init__.py b/services/intake/src/nmp/intake/api/v2/health/__init__.py deleted file mode 100644 index ace9aefe69..0000000000 --- a/services/intake/src/nmp/intake/api/v2/health/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. -# SPDX-License-Identifier: Apache-2.0 - -"""Health check endpoints.""" diff --git a/services/intake/src/nmp/intake/api/v2/health/endpoints.py b/services/intake/src/nmp/intake/api/v2/health/endpoints.py deleted file mode 100644 index 574d271b0e..0000000000 --- a/services/intake/src/nmp/intake/api/v2/health/endpoints.py +++ /dev/null @@ -1,59 +0,0 @@ -# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. -# SPDX-License-Identifier: Apache-2.0 - -"""Health check endpoints.""" - -from fastapi import APIRouter, status - -router = APIRouter() - -API_TAG = "Health Checks" - - -@router.get( - "/health/live", - tags=[API_TAG], - status_code=status.HTTP_200_OK, - summary="Perform a simple liveness check to verify the server is running.", -) -@router.get( - "/v1/health/live", - tags=[API_TAG], - status_code=status.HTTP_200_OK, - summary="Perform a simple liveness check to verify the server is running.", -) -async def health_live() -> dict: - """ - Health check endpoint to verify the status of the application. - """ - return {"status": "healthy"} - - -@router.get( - "/health/ready", - tags=[API_TAG], - status_code=status.HTTP_200_OK, - summary="Perform a readiness check to verify the server is able/ready to serve requests.", -) -@router.get( - "/v1/health/ready", - tags=[API_TAG], - status_code=status.HTTP_200_OK, - summary="Perform a readiness check to verify the server is able/ready to serve requests.", -) -async def health_ready() -> dict: - """ - Health check endpoint to verify the status of the application. - """ - return {"status": "ready"} - - -@router.get( - "/health", - tags=[API_TAG], - status_code=status.HTTP_200_OK, - summary="Unified health endpoint to check if server is alive and ready to serve requests.", -) -async def health_overall() -> dict: - """Unified health endpoint.""" - return {"status": "ready"} diff --git a/services/intake/src/nmp/intake/service.py b/services/intake/src/nmp/intake/service.py index 4d5f84a4d8..7c487fa5a5 100644 --- a/services/intake/src/nmp/intake/service.py +++ b/services/intake/src/nmp/intake/service.py @@ -66,7 +66,7 @@ async def on_startup(self) -> None: self.clickhouse_client = ClickHouseSpanClient(ClickHouseSettings.from_config(cfg)) logger.warning( "ClickHouse schema setup was not run during Intake startup; " - "readiness checks and trace endpoints will initialize ClickHouse on first use", + "trace endpoints will initialize ClickHouse on first use and return 503 until it is reachable", extra={ "service": self.name, "clickhouse_url": cfg.clickhouse_config.url, @@ -85,12 +85,4 @@ async def on_shutdown(self) -> None: await super().on_shutdown() async def is_ready(self) -> bool: - if not self._ready or self.clickhouse_client is None: - return False - - try: - await self.clickhouse_client.query("SELECT 1") - except Exception as exc: - logger.warning("ClickHouse readiness check failed: %s", exc, extra={"service": self.name}) - return False - return True + return self._ready diff --git a/services/intake/tests/integration/spans/test_clickhouse_bootstrap.py b/services/intake/tests/integration/spans/test_clickhouse_bootstrap.py index 441447077a..ca34f33ea6 100644 --- a/services/intake/tests/integration/spans/test_clickhouse_bootstrap.py +++ b/services/intake/tests/integration/spans/test_clickhouse_bootstrap.py @@ -29,13 +29,13 @@ def test_clickhouse_bootstrap_is_idempotent(clickhouse_client: ClickHouseSpanCli ] -def test_intake_service_readiness_bootstraps_service_owned_clickhouse(client: TestClient, run_async): +def test_intake_service_readiness_does_not_bootstrap_service_owned_clickhouse(client: TestClient, run_async): app = cast(FastAPI, client.app) service = cast(IntakeService, app.state.intake_service) assert service.clickhouse_client is not None assert run_async(service.is_ready()) is True - assert service.clickhouse_client._bootstrapped is True + assert service.clickhouse_client._bootstrapped is False response = client.get("/apis/intake/v2/workspaces/default/spans") diff --git a/services/intake/tests/test_clickhouse_startup.py b/services/intake/tests/test_clickhouse_startup.py index f4a5bcceea..190f56d901 100644 --- a/services/intake/tests/test_clickhouse_startup.py +++ b/services/intake/tests/test_clickhouse_startup.py @@ -11,7 +11,7 @@ from nmp.intake.service import IntakeService -def test_intake_not_ready_when_clickhouse_is_unavailable( +def test_intake_ready_when_clickhouse_is_unavailable( caplog: pytest.LogCaptureFixture, ) -> None: intake_config = IntakeConfig( @@ -33,6 +33,6 @@ async def check_readiness() -> bool: finally: await service.on_shutdown() - assert asyncio.run(check_readiness()) is False + assert asyncio.run(check_readiness()) is True assert any("ClickHouse schema setup was not run during Intake startup" in record.message for record in caplog.records) - assert any("ClickHouse readiness check failed" in record.message for record in caplog.records) + assert not any("ClickHouse readiness check failed" in record.message for record in caplog.records) From 7044b8b984b3f7e53c8dfe9af49e893e83566cc9 Mon Sep 17 00:00:00 2001 From: Brian Newsom Date: Thu, 28 May 2026 10:50:51 -0600 Subject: [PATCH 3/4] chore: fix stainless Signed-off-by: Brian Newsom --- docs/cli/reference.md | 2 +- packages/nemo_platform/pyproject.toml | 21 - .../cli/commands/api/intake/__init__.py | 14 +- .../cli/commands/api/intake/apps/__init__.py | 326 -- .../cli/commands/api/intake/apps/tasks.py | 340 -- .../commands/api/intake/entries/__init__.py | 445 --- .../cli/commands/api/intake/entries/events.py | 118 - .../commands/api/intake/exports/__init__.py | 63 - .../cli/commands/api/intake/exports/jobs.py | 245 -- .../cli/commands/api/models/__init__.py | 2 +- .../cli/commands/api/projects.py | 2 +- .../nemo-platform/.nmpcontext/openapi.yaml | 2874 ++--------------- .../nemo-platform/.nmpcontext/stainless.yaml | 93 +- .../cli/commands/api/intake/__init__.py | 14 +- .../cli/commands/api/intake/apps/__init__.py | 326 -- .../cli/commands/api/intake/apps/tasks.py | 340 -- .../commands/api/intake/entries/__init__.py | 445 --- .../cli/commands/api/intake/entries/events.py | 118 - .../commands/api/intake/exports/__init__.py | 63 - .../cli/commands/api/intake/exports/jobs.py | 245 -- .../cli/commands/api/models/__init__.py | 2 +- .../cli/commands/api/projects.py | 2 +- .../resources/intake/__init__.py | 42 - .../src/nemo_platform/resources/intake/api.md | 135 +- .../resources/intake/apps/__init__.py | 48 - .../resources/intake/apps/apps.py | 718 ---- .../resources/intake/apps/tasks.py | 731 ----- .../resources/intake/entries/__init__.py | 48 - .../resources/intake/entries/entries.py | 855 ----- .../resources/intake/entries/events.py | 320 -- .../resources/intake/exports/__init__.py | 48 - .../resources/intake/exports/exports.py | 229 -- .../resources/intake/exports/jobs.py | 464 --- .../intake/ingest/chat_completions.py | 53 +- .../nemo_platform/resources/intake/intake.py | 96 - .../nemo_platform/types/intake/__init__.py | 44 - .../src/nemo_platform/types/intake/app.py | 51 - .../types/intake/app_create_params.py | 38 - .../types/intake/app_filter_param.py | 46 - .../types/intake/app_list_params.py | 44 - .../types/intake/app_patch_params.py | 35 - .../types/intake/app_sort_field.py | 22 - .../types/intake/apps/__init__.py | 26 - .../nemo_platform/types/intake/apps/task.py | 54 - .../types/intake/apps/task_create_params.py | 40 - .../types/intake/apps/task_filter_param.py | 49 - .../types/intake/apps/task_list_params.py | 44 - .../types/intake/apps/task_sort_field.py | 22 - .../types/intake/apps/tasks_page.py | 37 - .../nemo_platform/types/intake/apps_page.py | 37 - .../types/intake/entries/__init__.py | 20 - .../intake/entries/event_create_params.py | 40 - .../src/nemo_platform/types/intake/entry.py | 98 - .../types/intake/entry_context.py | 79 - .../intake/entry_context_filter_param.py | 41 - .../types/intake/entry_context_param.py | 82 - .../types/intake/entry_create_params.py | 85 - .../nemo_platform/types/intake/entry_data.py | 48 - .../types/intake/entry_data_param.py | 51 - .../types/intake/entry_filter_param.py | 85 - .../types/intake/entry_list_params.py | 47 - .../types/intake/entry_patch_params.py | 79 - .../types/intake/entry_sort_field.py | 22 - .../nemo_platform/types/intake/entrys_page.py | 37 - .../types/intake/evaluator_result_event.py | 67 - .../intake/evaluator_result_event_param.py | 69 - .../types/intake/export_config_param.py | 44 - .../types/intake/export_config_param_param.py | 45 - .../types/intake/export_preview_response.py | 39 - .../types/intake/exports/__init__.py | 28 - .../types/intake/exports/export_config.py | 44 - .../types/intake/exports/export_job.py | 76 - .../intake/exports/export_job_filter_param.py | 50 - .../intake/exports/export_job_sort_field.py | 22 - .../types/intake/exports/export_jobs_page.py | 37 - .../intake/exports/export_status_details.py | 35 - .../types/intake/exports/job_create_params.py | 40 - .../types/intake/exports/job_list_params.py | 46 - .../types/intake/exports/job_status.py | 22 - .../types/intake/flexible_entry_request.py | 63 - .../intake/flexible_entry_request_param.py | 51 - .../types/intake/flexible_entry_response.py | 62 - .../intake/flexible_entry_response_param.py | 49 - .../types/intake/flexible_message.py | 56 - .../types/intake/flexible_message_param.py | 43 - .../types/intake/ingest/__init__.py | 8 + ...aptured_chat_completions_request_param.py} | 20 +- ...ptured_chat_completions_response_param.py} | 12 +- .../captured_chat_message_param.py} | 17 +- .../ingest/chat_completion_create_params.py | 28 +- .../chat_message_role.py} | 4 +- .../types/intake/reviewer_annotation_event.py | 95 - .../intake/reviewer_annotation_event_param.py | 97 - .../types/intake/thumb_direction.py | 22 - .../src/nemo_platform/types/intake/usage.py | 65 - .../nemo_platform/types/intake/usage_param.py | 68 - .../types/intake/user_action_event.py | 65 - .../types/intake/user_action_event_param.py | 68 - .../types/intake/user_feedback_event.py | 84 - .../types/intake/user_feedback_event_param.py | 86 - .../nemo_platform/types/intake/user_rating.py | 68 - .../types/intake/user_rating_param.py | 70 - .../api_resources/intake/apps/__init__.py | 16 - .../api_resources/intake/apps/test_tasks.py | 751 ----- .../api_resources/intake/entries/__init__.py | 16 - .../intake/entries/test_events.py | 281 -- .../api_resources/intake/exports/__init__.py | 16 - .../api_resources/intake/exports/test_jobs.py | 413 --- .../intake/ingest/test_chat_completions.py | 4 +- .../tests/api_resources/intake/test_apps.py | 629 ---- .../api_resources/intake/test_entries.py | 945 ------ .../api_resources/intake/test_exports.py | 159 - sdk/stainless.yaml | 4 + .../nmp/core/auth/assets/static-authz.yaml | 137 - .../intake/tests/test_clickhouse_startup.py | 4 +- third_party/licenses.jsonl | 15 - third_party/osv-licenses.json | 158 +- third_party/requirements-main.txt | 115 +- uv.lock | 236 -- 119 files changed, 268 insertions(+), 16786 deletions(-) delete mode 100644 packages/nemo_platform_ext/src/nemo_platform_ext/cli/commands/api/intake/apps/__init__.py delete mode 100644 packages/nemo_platform_ext/src/nemo_platform_ext/cli/commands/api/intake/apps/tasks.py delete mode 100644 packages/nemo_platform_ext/src/nemo_platform_ext/cli/commands/api/intake/entries/__init__.py delete mode 100644 packages/nemo_platform_ext/src/nemo_platform_ext/cli/commands/api/intake/entries/events.py delete mode 100644 packages/nemo_platform_ext/src/nemo_platform_ext/cli/commands/api/intake/exports/__init__.py delete mode 100644 packages/nemo_platform_ext/src/nemo_platform_ext/cli/commands/api/intake/exports/jobs.py delete mode 100644 sdk/python/nemo-platform/src/nemo_platform/cli/commands/api/intake/apps/__init__.py delete mode 100644 sdk/python/nemo-platform/src/nemo_platform/cli/commands/api/intake/apps/tasks.py delete mode 100644 sdk/python/nemo-platform/src/nemo_platform/cli/commands/api/intake/entries/__init__.py delete mode 100644 sdk/python/nemo-platform/src/nemo_platform/cli/commands/api/intake/entries/events.py delete mode 100644 sdk/python/nemo-platform/src/nemo_platform/cli/commands/api/intake/exports/__init__.py delete mode 100644 sdk/python/nemo-platform/src/nemo_platform/cli/commands/api/intake/exports/jobs.py delete mode 100644 sdk/python/nemo-platform/src/nemo_platform/resources/intake/apps/__init__.py delete mode 100644 sdk/python/nemo-platform/src/nemo_platform/resources/intake/apps/apps.py delete mode 100644 sdk/python/nemo-platform/src/nemo_platform/resources/intake/apps/tasks.py delete mode 100644 sdk/python/nemo-platform/src/nemo_platform/resources/intake/entries/__init__.py delete mode 100644 sdk/python/nemo-platform/src/nemo_platform/resources/intake/entries/entries.py delete mode 100644 sdk/python/nemo-platform/src/nemo_platform/resources/intake/entries/events.py delete mode 100644 sdk/python/nemo-platform/src/nemo_platform/resources/intake/exports/__init__.py delete mode 100644 sdk/python/nemo-platform/src/nemo_platform/resources/intake/exports/exports.py delete mode 100644 sdk/python/nemo-platform/src/nemo_platform/resources/intake/exports/jobs.py delete mode 100644 sdk/python/nemo-platform/src/nemo_platform/types/intake/app.py delete mode 100644 sdk/python/nemo-platform/src/nemo_platform/types/intake/app_create_params.py delete mode 100644 sdk/python/nemo-platform/src/nemo_platform/types/intake/app_filter_param.py delete mode 100644 sdk/python/nemo-platform/src/nemo_platform/types/intake/app_list_params.py delete mode 100644 sdk/python/nemo-platform/src/nemo_platform/types/intake/app_patch_params.py delete mode 100644 sdk/python/nemo-platform/src/nemo_platform/types/intake/app_sort_field.py delete mode 100644 sdk/python/nemo-platform/src/nemo_platform/types/intake/apps/__init__.py delete mode 100644 sdk/python/nemo-platform/src/nemo_platform/types/intake/apps/task.py delete mode 100644 sdk/python/nemo-platform/src/nemo_platform/types/intake/apps/task_create_params.py delete mode 100644 sdk/python/nemo-platform/src/nemo_platform/types/intake/apps/task_filter_param.py delete mode 100644 sdk/python/nemo-platform/src/nemo_platform/types/intake/apps/task_list_params.py delete mode 100644 sdk/python/nemo-platform/src/nemo_platform/types/intake/apps/task_sort_field.py delete mode 100644 sdk/python/nemo-platform/src/nemo_platform/types/intake/apps/tasks_page.py delete mode 100644 sdk/python/nemo-platform/src/nemo_platform/types/intake/apps_page.py delete mode 100644 sdk/python/nemo-platform/src/nemo_platform/types/intake/entries/__init__.py delete mode 100644 sdk/python/nemo-platform/src/nemo_platform/types/intake/entries/event_create_params.py delete mode 100644 sdk/python/nemo-platform/src/nemo_platform/types/intake/entry.py delete mode 100644 sdk/python/nemo-platform/src/nemo_platform/types/intake/entry_context.py delete mode 100644 sdk/python/nemo-platform/src/nemo_platform/types/intake/entry_context_filter_param.py delete mode 100644 sdk/python/nemo-platform/src/nemo_platform/types/intake/entry_context_param.py delete mode 100644 sdk/python/nemo-platform/src/nemo_platform/types/intake/entry_create_params.py delete mode 100644 sdk/python/nemo-platform/src/nemo_platform/types/intake/entry_data.py delete mode 100644 sdk/python/nemo-platform/src/nemo_platform/types/intake/entry_data_param.py delete mode 100644 sdk/python/nemo-platform/src/nemo_platform/types/intake/entry_filter_param.py delete mode 100644 sdk/python/nemo-platform/src/nemo_platform/types/intake/entry_list_params.py delete mode 100644 sdk/python/nemo-platform/src/nemo_platform/types/intake/entry_patch_params.py delete mode 100644 sdk/python/nemo-platform/src/nemo_platform/types/intake/entry_sort_field.py delete mode 100644 sdk/python/nemo-platform/src/nemo_platform/types/intake/entrys_page.py delete mode 100644 sdk/python/nemo-platform/src/nemo_platform/types/intake/evaluator_result_event.py delete mode 100644 sdk/python/nemo-platform/src/nemo_platform/types/intake/evaluator_result_event_param.py delete mode 100644 sdk/python/nemo-platform/src/nemo_platform/types/intake/export_config_param.py delete mode 100644 sdk/python/nemo-platform/src/nemo_platform/types/intake/export_config_param_param.py delete mode 100644 sdk/python/nemo-platform/src/nemo_platform/types/intake/export_preview_response.py delete mode 100644 sdk/python/nemo-platform/src/nemo_platform/types/intake/exports/__init__.py delete mode 100644 sdk/python/nemo-platform/src/nemo_platform/types/intake/exports/export_config.py delete mode 100644 sdk/python/nemo-platform/src/nemo_platform/types/intake/exports/export_job.py delete mode 100644 sdk/python/nemo-platform/src/nemo_platform/types/intake/exports/export_job_filter_param.py delete mode 100644 sdk/python/nemo-platform/src/nemo_platform/types/intake/exports/export_job_sort_field.py delete mode 100644 sdk/python/nemo-platform/src/nemo_platform/types/intake/exports/export_jobs_page.py delete mode 100644 sdk/python/nemo-platform/src/nemo_platform/types/intake/exports/export_status_details.py delete mode 100644 sdk/python/nemo-platform/src/nemo_platform/types/intake/exports/job_create_params.py delete mode 100644 sdk/python/nemo-platform/src/nemo_platform/types/intake/exports/job_list_params.py delete mode 100644 sdk/python/nemo-platform/src/nemo_platform/types/intake/exports/job_status.py delete mode 100644 sdk/python/nemo-platform/src/nemo_platform/types/intake/flexible_entry_request.py delete mode 100644 sdk/python/nemo-platform/src/nemo_platform/types/intake/flexible_entry_request_param.py delete mode 100644 sdk/python/nemo-platform/src/nemo_platform/types/intake/flexible_entry_response.py delete mode 100644 sdk/python/nemo-platform/src/nemo_platform/types/intake/flexible_entry_response_param.py delete mode 100644 sdk/python/nemo-platform/src/nemo_platform/types/intake/flexible_message.py delete mode 100644 sdk/python/nemo-platform/src/nemo_platform/types/intake/flexible_message_param.py rename sdk/python/nemo-platform/src/nemo_platform/types/intake/{apps/task_patch_params.py => ingest/captured_chat_completions_request_param.py} (63%) rename sdk/python/nemo-platform/src/nemo_platform/types/intake/{entry_user_rating_filter_param.py => ingest/captured_chat_completions_response_param.py} (72%) rename sdk/python/nemo-platform/src/nemo_platform/types/intake/{export_preview_params.py => ingest/captured_chat_message_param.py} (68%) rename sdk/python/nemo-platform/src/nemo_platform/types/intake/{message_role.py => ingest/chat_message_role.py} (86%) delete mode 100644 sdk/python/nemo-platform/src/nemo_platform/types/intake/reviewer_annotation_event.py delete mode 100644 sdk/python/nemo-platform/src/nemo_platform/types/intake/reviewer_annotation_event_param.py delete mode 100644 sdk/python/nemo-platform/src/nemo_platform/types/intake/thumb_direction.py delete mode 100644 sdk/python/nemo-platform/src/nemo_platform/types/intake/usage.py delete mode 100644 sdk/python/nemo-platform/src/nemo_platform/types/intake/usage_param.py delete mode 100644 sdk/python/nemo-platform/src/nemo_platform/types/intake/user_action_event.py delete mode 100644 sdk/python/nemo-platform/src/nemo_platform/types/intake/user_action_event_param.py delete mode 100644 sdk/python/nemo-platform/src/nemo_platform/types/intake/user_feedback_event.py delete mode 100644 sdk/python/nemo-platform/src/nemo_platform/types/intake/user_feedback_event_param.py delete mode 100644 sdk/python/nemo-platform/src/nemo_platform/types/intake/user_rating.py delete mode 100644 sdk/python/nemo-platform/src/nemo_platform/types/intake/user_rating_param.py delete mode 100644 sdk/python/nemo-platform/tests/api_resources/intake/apps/__init__.py delete mode 100644 sdk/python/nemo-platform/tests/api_resources/intake/apps/test_tasks.py delete mode 100644 sdk/python/nemo-platform/tests/api_resources/intake/entries/__init__.py delete mode 100644 sdk/python/nemo-platform/tests/api_resources/intake/entries/test_events.py delete mode 100644 sdk/python/nemo-platform/tests/api_resources/intake/exports/__init__.py delete mode 100644 sdk/python/nemo-platform/tests/api_resources/intake/exports/test_jobs.py delete mode 100644 sdk/python/nemo-platform/tests/api_resources/intake/test_apps.py delete mode 100644 sdk/python/nemo-platform/tests/api_resources/intake/test_entries.py delete mode 100644 sdk/python/nemo-platform/tests/api_resources/intake/test_exports.py diff --git a/docs/cli/reference.md b/docs/cli/reference.md index ab01c887db..4640883b45 100644 --- a/docs/cli/reference.md +++ b/docs/cli/reference.md @@ -4159,7 +4159,7 @@ nemo models list [OPTIONS] * `--workspace` * `--page `: Page number. * `--page-size `: Page size. -* `--sort `: The field to sort by. To sort in decreasing order, use `-` in front of the field name. [possible values: created_at, -created_at, name, -name, updated_at, -updated_at] +* `--sort `: The field to sort by. To sort in decreasing order, use `-` in front of the field name. [possible values: name, -name, created_at, -created_at, updated_at, -updated_at] * `--verbose`: Whether to include full spec details * `--all-pages`: Fetch all pages diff --git a/packages/nemo_platform/pyproject.toml b/packages/nemo_platform/pyproject.toml index f3e040db25..0596ef6e88 100644 --- a/packages/nemo_platform/pyproject.toml +++ b/packages/nemo_platform/pyproject.toml @@ -227,30 +227,9 @@ inference-gateway-service = [ intake-service = [ "fastapi>=0.115.5, <1.0.0", "uvicorn>=0.22.0, <1.0.0", - "sqlalchemy>=2.0", - "alembic>=1.10.4, <2.0.0", "pydantic>=2.9.2, <3.0.0", "pydantic-settings>=2.6.1, <3.0.0", - "asyncpg>=0.30.0, <1.0.0", - "python-dotenv>=1.0.0, <2.0.0", - "httpx>=0.24.1, <1.0.0", - "huggingface-hub>=1.0.1, <2.0.0", - "openai>=1.51.0", - "requests>=2.32.3, <3.0.0", - "types-requests>=2.32.0.20241016, <3.0.0.0", - "pyyaml>=6.0.2, <7.0.0", - "base58>=2.1.1", "nmp-common", - "opentelemetry-api>=1.27.0", - "opentelemetry-instrumentation-fastapi>=0.48b0", - "opentelemetry-sdk>=1.27.0", - "opentelemetry-exporter-otlp>=1.27.0", - "opentelemetry-instrumentation-openai>=0.33.9", - "psycopg2-binary>=2.9.10", - "celery>=5.5.3", - "redis>=5.0.1", - "flower>=2.0.1", - "watchdog[watchmedo]>=6.0.0", "clickhouse-connect>=0.7,<1.0", "opentelemetry-proto>=1.27.0", ] diff --git a/packages/nemo_platform_ext/src/nemo_platform_ext/cli/commands/api/intake/__init__.py b/packages/nemo_platform_ext/src/nemo_platform_ext/cli/commands/api/intake/__init__.py index 8cdb5e74c4..4d6b11ad1e 100644 --- a/packages/nemo_platform_ext/src/nemo_platform_ext/cli/commands/api/intake/__init__.py +++ b/packages/nemo_platform_ext/src/nemo_platform_ext/cli/commands/api/intake/__init__.py @@ -4,25 +4,13 @@ # NOTE: This file is auto-generated from __future__ import annotations -from nemo_platform_ext.cli.commands.api.intake import ( - annotations, - apps, - entries, - evaluator_results, - exports, - ingest, - spans, - traces, -) +from nemo_platform_ext.cli.commands.api.intake import annotations, evaluator_results, ingest, spans, traces from nemo_platform_ext.cli.core.help_formatter import create_typer_app app = create_typer_app(name="intake", help="Intake operations") app.add_typer(annotations.app, name="annotations") -app.add_typer(apps.app, name="apps") -app.add_typer(entries.app, name="entries") app.add_typer(evaluator_results.app, name="evaluator-results") -app.add_typer(exports.app, name="exports") app.add_typer(ingest.app, name="ingest") app.add_typer(spans.app, name="spans") app.add_typer(traces.app, name="traces") diff --git a/packages/nemo_platform_ext/src/nemo_platform_ext/cli/commands/api/intake/apps/__init__.py b/packages/nemo_platform_ext/src/nemo_platform_ext/cli/commands/api/intake/apps/__init__.py deleted file mode 100644 index 510893850d..0000000000 --- a/packages/nemo_platform_ext/src/nemo_platform_ext/cli/commands/api/intake/apps/__init__.py +++ /dev/null @@ -1,326 +0,0 @@ -# SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. -# SPDX-License-Identifier: Apache-2.0 - -# NOTE: This file is auto-generated -from __future__ import annotations - -from typing import Annotated, Literal - -import typer - -from nemo_platform_ext.cli.commands.api.intake.apps import tasks -from nemo_platform_ext.cli.core.api import build_kwargs, merge_filter_dict -from nemo_platform_ext.cli.core.code_generator import handle_code_generation -from nemo_platform_ext.cli.core.context import CLIContext -from nemo_platform_ext.cli.core.errors import handle_errors -from nemo_platform_ext.cli.core.formatters import Column, check_output_columns_with_format, format_output -from nemo_platform_ext.cli.core.help_formatter import collect_warnings, create_typer_app -from nemo_platform_ext.cli.core.pagination import PaginationType, fetch_all_pages, warn_if_more_pages -from nemo_platform_ext.cli.core.stdin_utils import read_data_input_with_flags, validate_required_fields -from nemo_platform_ext.cli.core.stdin_utils import read_payload as read_payload -from nemo_platform_ext.cli.core.types import ( - EntityOutputFormatOption, - ListOutputFormatOption, - NoTruncateOption, - OutputColumnsOption, -) - -app = create_typer_app(name="apps", help="Manage apps") - -app.add_typer(tasks.app, name="tasks") - - -@app.command("create") -@collect_warnings -@handle_errors -def create_apps( - ctx: typer.Context, - name: Annotated[str | None, typer.Argument(help="App name (unique within workspace) (required)")] = None, - workspace: Annotated[str | None, typer.Option("--workspace")] = None, - description: Annotated[str | None, typer.Option("--description", help="App description")] = None, - locked: Annotated[ - bool | None, - typer.Option( - "--locked", help="If true, this record cannot be automatically updated when entries are ingested." - ), - ] = None, - project: Annotated[ - str | None, typer.Option("--project", help="The name of the project associated with this app") - ] = None, - exist_ok: Annotated[ - bool | None, - typer.Option( - "--exist-ok", help="Do not raise an error if the resource already exists. Returns the existing resource." - ), - ] = None, - input_file: Annotated[ - str | None, - typer.Option("--input-file", help="Path to JSON file (use '-' for stdin)", rich_help_panel="Input Options"), - ] = None, - input_data: Annotated[ - str | None, - typer.Option("--input-data", help="Input data for the request (JSON or YAML)", rich_help_panel="Input Options"), - ] = None, - output_format: EntityOutputFormatOption = None, -) -> None: - """Create a new app. - - [bold red]Required fields:[/] name - - [green]Examples:[/] - nemo intake apps create --input-file config.json - nemo intake apps create --input-data '{"name": "value"}' - echo '{"json": "data"}' | nemo intake apps create --input-file - - nemo intake apps create --