From 25ca3203b92079f2d03722b1b110befbb05985ca Mon Sep 17 00:00:00 2001 From: Lucas Jiang <2862605953@qq.com> Date: Sat, 16 May 2026 15:01:05 +0800 Subject: [PATCH 1/2] feat(text-embedding): add text embedding function with Ollama Extracted from lucas/text-embedding branch and adapted to new dynamic registry architecture. Changes: - Add functions/text-embedding with handler + tests - Add Ollama k8s deployment and model pull job - Update k8s config with TEXT_EMBEDDING_DRY_RUN and OLLAMA env vars - Fix test to use inline jest.mock() for @agentic-kit/ollama Co-Authored-By: Claude Opus 4.5 --- .../text-embedding/__tests__/handler.test.ts | 107 ++++++++ functions/text-embedding/handler.json | 10 + functions/text-embedding/handler.ts | 56 +++++ k8s/overlays/local-simple/config.yaml | 5 + .../local-simple/ollama-pull-job.yaml | 26 ++ k8s/overlays/local-simple/ollama.yaml | 53 ++++ pnpm-lock.yaml | 235 ++++++++++++++++++ skaffold.yaml | 61 ++++- 8 files changed, 550 insertions(+), 3 deletions(-) create mode 100644 functions/text-embedding/__tests__/handler.test.ts create mode 100644 functions/text-embedding/handler.json create mode 100644 functions/text-embedding/handler.ts create mode 100644 k8s/overlays/local-simple/ollama-pull-job.yaml create mode 100644 k8s/overlays/local-simple/ollama.yaml diff --git a/functions/text-embedding/__tests__/handler.test.ts b/functions/text-embedding/__tests__/handler.test.ts new file mode 100644 index 0000000..e174b2e --- /dev/null +++ b/functions/text-embedding/__tests__/handler.test.ts @@ -0,0 +1,107 @@ +import { createMockContext } from '../../../tests/helpers/mock-context'; + +const mockGenerateEmbedding = jest.fn(); + +jest.mock('@agentic-kit/ollama', () => { + return { + __esModule: true, + default: class OllamaClient { + constructor() {} + async generateEmbedding(text: string, model: string) { + return mockGenerateEmbedding(text, model); + } + }, + }; +}); + +const loadHandler = () => { + const mod = require('../handler'); + return mod.default ?? mod; +}; + +describe('text-embedding handler', () => { + beforeEach(() => { + mockGenerateEmbedding.mockClear(); + mockGenerateEmbedding.mockResolvedValue([0.1, 0.2, 0.3, 0.4, 0.5]); + }); + + it('should generate embedding for valid text', async () => { + const mockEmbedding = [0.1, 0.2, 0.3, 0.4, 0.5]; + mockGenerateEmbedding.mockResolvedValue(mockEmbedding); + + const handler = loadHandler(); + const result = await handler( + { text: 'Hello world' }, + createMockContext({ env: { OLLAMA_URL: 'http://localhost:11434' } }) + ); + + expect(result).toEqual({ + embedding: mockEmbedding, + dimensions: 5, + model: 'nomic-embed-text:latest', + }); + }); + + it('should use custom model when provided', async () => { + const mockEmbedding = [0.1, 0.2, 0.3]; + mockGenerateEmbedding.mockResolvedValue(mockEmbedding); + + const handler = loadHandler(); + const result = await handler( + { text: 'Test', model: 'mxbai-embed-large' }, + createMockContext() + ); + + expect(result.model).toBe('mxbai-embed-large'); + expect(mockGenerateEmbedding).toHaveBeenCalledWith('Test', 'mxbai-embed-large'); + }); + + it('should use EMBEDDING_MODEL env var as default', async () => { + const mockEmbedding = [0.1, 0.2]; + mockGenerateEmbedding.mockResolvedValue(mockEmbedding); + + const handler = loadHandler(); + const result = await handler( + { text: 'Test' }, + createMockContext({ env: { EMBEDDING_MODEL: 'all-minilm' } }) + ); + + expect(result.model).toBe('all-minilm'); + }); + + it('should throw error when text is missing', async () => { + const handler = loadHandler(); + await expect( + handler({}, createMockContext()) + ).rejects.toThrow('Missing required param: text'); + }); + + it('should throw error when text is not a string', async () => { + const handler = loadHandler(); + await expect( + handler({ text: 123 }, createMockContext()) + ).rejects.toThrow('Missing required param: text'); + }); + + it('should throw error when text is empty', async () => { + const handler = loadHandler(); + await expect( + handler({ text: '' }, createMockContext()) + ).rejects.toThrow('Missing required param: text'); + }); + + it('should return mock embedding in DRY_RUN mode', async () => { + const handler = loadHandler(); + const result = await handler( + { text: 'Test' }, + createMockContext({ env: { TEXT_EMBEDDING_DRY_RUN: 'true' } }) + ); + + expect(result).toEqual({ + embedding: Array(768).fill(0), + dimensions: 768, + model: 'dry-run', + }); + expect(mockGenerateEmbedding).not.toHaveBeenCalled(); + }); +}); diff --git a/functions/text-embedding/handler.json b/functions/text-embedding/handler.json new file mode 100644 index 0000000..dcdeec3 --- /dev/null +++ b/functions/text-embedding/handler.json @@ -0,0 +1,10 @@ +{ + "name": "text-embedding", + "version": "1.0.0", + "type": "node-graphql", + "port": 8084, + "description": "Generate text embeddings using Ollama", + "dependencies": { + "@agentic-kit/ollama": "^1.0.3" + } +} diff --git a/functions/text-embedding/handler.ts b/functions/text-embedding/handler.ts new file mode 100644 index 0000000..5eac041 --- /dev/null +++ b/functions/text-embedding/handler.ts @@ -0,0 +1,56 @@ +import type { FunctionHandler } from '@constructive-io/fn-runtime'; +import OllamaClient from '@agentic-kit/ollama'; + +interface Params { + text: string; + model?: string; +} + +interface Result { + embedding: number[]; + dimensions: number; + model: string; +} + +const handler: FunctionHandler = async (params, context) => { + const { log, env } = context; + + const { text, model } = params; + + if (!text || typeof text !== 'string') { + throw new Error('Missing required param: text'); + } + + // DRY_RUN mode for CI testing - skip actual Ollama call + if (env.TEXT_EMBEDDING_DRY_RUN === 'true') { + log.info('[text-embedding] DRY_RUN mode, returning mock embedding'); + return { + embedding: Array(768).fill(0), + dimensions: 768, + model: 'dry-run', + }; + } + + const ollamaUrl = env.OLLAMA_URL || 'http://localhost:11434'; + const embeddingModel = model || env.EMBEDDING_MODEL || 'nomic-embed-text:latest'; + + log.info('[text-embedding] Generating embedding', { + textLength: text.length, + model: embeddingModel, + }); + + const ollama = new OllamaClient(ollamaUrl); + const embedding = await ollama.generateEmbedding(text, embeddingModel); + + log.info('[text-embedding] Complete', { + dimensions: embedding.length, + }); + + return { + embedding, + dimensions: embedding.length, + model: embeddingModel, + }; +}; + +export default handler; diff --git a/k8s/overlays/local-simple/config.yaml b/k8s/overlays/local-simple/config.yaml index c54f2c0..9030d82 100644 --- a/k8s/overlays/local-simple/config.yaml +++ b/k8s/overlays/local-simple/config.yaml @@ -24,3 +24,8 @@ data: SEND_EMAIL_DRY_RUN: "true" SEND_VERIFICATION_LINK_DRY_RUN: "true" + TEXT_EMBEDDING_DRY_RUN: "true" + + # AI/Embedding settings + OLLAMA_URL: "http://ollama.constructive-functions.svc.cluster.local:11434" + EMBEDDING_MODEL: "nomic-embed-text:latest" diff --git a/k8s/overlays/local-simple/ollama-pull-job.yaml b/k8s/overlays/local-simple/ollama-pull-job.yaml new file mode 100644 index 0000000..0856662 --- /dev/null +++ b/k8s/overlays/local-simple/ollama-pull-job.yaml @@ -0,0 +1,26 @@ +apiVersion: batch/v1 +kind: Job +metadata: + name: ollama-pull-model +spec: + ttlSecondsAfterFinished: 300 + template: + spec: + restartPolicy: OnFailure + initContainers: + - name: wait-for-ollama + image: busybox:1.36 + command: ['sh', '-c', 'until nc -z ollama 11434; do echo "Waiting for Ollama..."; sleep 2; done'] + containers: + - name: pull-model + image: curlimages/curl:8.5.0 + command: + - sh + - -c + - | + echo "Pulling nomic-embed-text model..." + curl -X POST http://ollama:11434/api/pull \ + -H "Content-Type: application/json" \ + -d '{"name": "nomic-embed-text:latest"}' \ + --max-time 600 + echo "Model pull complete" diff --git a/k8s/overlays/local-simple/ollama.yaml b/k8s/overlays/local-simple/ollama.yaml new file mode 100644 index 0000000..9a4bc62 --- /dev/null +++ b/k8s/overlays/local-simple/ollama.yaml @@ -0,0 +1,53 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: ollama + labels: + app: ollama +spec: + replicas: 1 + selector: + matchLabels: + app: ollama + template: + metadata: + labels: + app: ollama + spec: + containers: + - name: ollama + image: ollama/ollama:latest + ports: + - name: http + containerPort: 11434 + env: + - name: OLLAMA_HOST + value: "0.0.0.0" + resources: + requests: + cpu: "100m" + memory: "512Mi" + limits: + cpu: "1000m" + memory: "2Gi" + volumeMounts: + - name: ollama-data + mountPath: /root/.ollama + volumes: + - name: ollama-data + emptyDir: {} +--- +apiVersion: v1 +kind: Service +metadata: + name: ollama + labels: + app: ollama +spec: + type: ClusterIP + selector: + app: ollama + ports: + - name: http + port: 11434 + targetPort: http diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 661e94a..eccc67e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -101,6 +101,77 @@ importers: specifier: ^5.1.6 version: 5.9.3 + generated/send-email-link: + dependencies: + '@constructive-io/fn-runtime': + specifier: workspace:^ + version: link:../../packages/fn-runtime + '@constructive-io/postmaster': + specifier: ^1.5.2 + version: 1.6.2 + '@launchql/mjml': + specifier: 0.1.1 + version: 0.1.1(@babel/core@7.28.5)(react-dom@16.14.0(react@16.14.0))(react-is@18.3.1)(react@16.14.0) + '@launchql/styled-email': + specifier: 0.1.0 + version: 0.1.0(@babel/core@7.28.5)(react-dom@16.14.0(react@16.14.0))(react-is@18.3.1)(react@16.14.0) + '@pgpmjs/env': + specifier: ^2.15.3 + version: 2.17.0 + '@pgpmjs/logger': + specifier: ^2.4.3 + version: 2.5.2 + graphql-request: + specifier: ^7.1.2 + version: 7.4.0(graphql@16.12.0) + graphql-tag: + specifier: ^2.12.6 + version: 2.12.6(graphql@16.12.0) + simple-smtp-server: + specifier: ^0.7.3 + version: 0.7.3 + devDependencies: + '@types/node': + specifier: ^22.10.4 + version: 22.19.3 + makage: + specifier: ^0.1.10 + version: 0.1.12 + typescript: + specifier: ^5.1.6 + version: 5.9.3 + + generated/send-sms: + dependencies: + '@constructive-io/fn-runtime': + specifier: workspace:^ + version: link:../../packages/fn-runtime + '@pgpmjs/env': + specifier: ^2.15.3 + version: 2.17.0 + '@pgpmjs/logger': + specifier: ^2.4.3 + version: 2.5.2 + graphql-request: + specifier: ^7.1.2 + version: 7.4.0(graphql@16.12.0) + graphql-tag: + specifier: ^2.12.6 + version: 2.12.6(graphql@16.12.0) + twilio: + specifier: ^5.5.0 + version: 5.13.1 + devDependencies: + '@types/node': + specifier: ^22.10.4 + version: 22.19.3 + makage: + specifier: ^0.1.10 + version: 0.1.12 + typescript: + specifier: ^5.1.6 + version: 5.9.3 + generated/send-verification-link: dependencies: '@constructive-io/fn-runtime': @@ -141,6 +212,34 @@ importers: specifier: ^5.1.6 version: 5.9.3 + generated/simple-email: + dependencies: + '@constructive-io/fn-runtime': + specifier: workspace:^ + version: link:../../packages/fn-runtime + '@constructive-io/postmaster': + specifier: ^1.5.2 + version: 1.6.2 + '@pgpmjs/env': + specifier: ^2.15.3 + version: 2.17.0 + '@pgpmjs/logger': + specifier: ^2.4.3 + version: 2.5.2 + simple-smtp-server: + specifier: ^0.7.3 + version: 0.7.3 + devDependencies: + '@types/node': + specifier: ^22.10.4 + version: 22.19.3 + makage: + specifier: ^0.1.10 + version: 0.1.12 + typescript: + specifier: ^5.1.6 + version: 5.9.3 + job/server: dependencies: '@constructive-io/job-pg': @@ -1381,6 +1480,10 @@ packages: engines: {node: '>=0.4.0'} hasBin: true + agent-base@6.0.2: + resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==} + engines: {node: '>= 6.0.0'} + ajv@6.12.6: resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} @@ -1530,6 +1633,9 @@ packages: bser@2.1.1: resolution: {integrity: sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==} + buffer-equal-constant-time@1.0.1: + resolution: {integrity: sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==} + buffer-from@1.1.2: resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} @@ -1708,6 +1814,9 @@ packages: resolution: {integrity: sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==} engines: {node: '>= 6'} + dayjs@1.11.20: + resolution: {integrity: sha512-YbwwqR/uYpeoP4pu043q+LTDLFBLApUP6VxRihdfNTqu4ubqMlGDLd6ErXhEgsyvY0K6nCs7nggYumAN+9uEuQ==} + debug@4.4.3: resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} engines: {node: '>=6.0'} @@ -1804,6 +1913,9 @@ packages: eastasianwidth@0.2.0: resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} + ecdsa-sig-formatter@1.0.11: + resolution: {integrity: sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==} + editorconfig@1.0.4: resolution: {integrity: sha512-L9Qe08KWTlqYMVvMcTIvMAdl1cDUubzRNYL+WfA4bLDMHe4nemKkpmYzkznE1FwLKu0EEmy6obgQKzMJrg4x9Q==} engines: {node: '>=14'} @@ -2211,6 +2323,10 @@ packages: resolution: {integrity: sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==} engines: {node: '>= 0.8'} + https-proxy-agent@5.0.1: + resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==} + engines: {node: '>= 6'} + human-signals@2.1.0: resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} engines: {node: '>=10.17.0'} @@ -2627,11 +2743,21 @@ packages: engines: {node: '>=6'} hasBin: true + jsonwebtoken@9.0.3: + resolution: {integrity: sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g==} + engines: {node: '>=12', npm: '>=6'} + juice@7.0.0: resolution: {integrity: sha512-AjKQX31KKN+uJs+zaf+GW8mBO/f/0NqSh2moTMyvwBY+4/lXIYTU8D8I2h6BAV3Xnz6GGsbalUyFqbYMe+Vh+Q==} engines: {node: '>=10.0.0'} hasBin: true + jwa@2.0.1: + resolution: {integrity: sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==} + + jws@4.0.1: + resolution: {integrity: sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==} + keyv@4.5.4: resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} @@ -2658,12 +2784,33 @@ packages: resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} engines: {node: '>=10'} + lodash.includes@4.3.0: + resolution: {integrity: sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==} + + lodash.isboolean@3.0.3: + resolution: {integrity: sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==} + + lodash.isinteger@4.0.4: + resolution: {integrity: sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==} + + lodash.isnumber@3.0.3: + resolution: {integrity: sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==} + + lodash.isplainobject@4.0.6: + resolution: {integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==} + + lodash.isstring@4.0.1: + resolution: {integrity: sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==} + lodash.memoize@4.1.2: resolution: {integrity: sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==} lodash.merge@4.6.2: resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + lodash.once@4.1.1: + resolution: {integrity: sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==} + lodash@4.17.21: resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} @@ -3262,6 +3409,10 @@ packages: scheduler@0.19.1: resolution: {integrity: sha512-n/zwRWRYSUj0/3g/otKDRPMh6qv2SYMWNq85IEa8iZyAv8od9zDYpGSnpBEjNgcMNq6Scbu5KfIPxNF72R/2EA==} + scmp@2.1.0: + resolution: {integrity: sha512-o/mRQGk9Rcer/jEEw/yw4mwo3EU/NvYvp577/Btqrym9Qy5/MdWGBqipbALgd2lrdWTJ5/gqDusxfnQBxOxT2Q==} + deprecated: Just use Node.js's crypto.timingSafeEqual() + semver@6.3.1: resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} hasBin: true @@ -3486,6 +3637,10 @@ packages: engines: {node: '>=18.0.0'} hasBin: true + twilio@5.13.1: + resolution: {integrity: sha512-sT+PkhptF4Mf7t8eXFFvPQx4w5VHnBIPXbltGPMFRe+R2GxfRdMuFbuNA/cEm0aQR6LFQOn33+fhClg+TjRVqQ==} + engines: {node: '>=14.0'} + type-check@0.4.0: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} engines: {node: '>= 0.8.0'} @@ -3630,6 +3785,10 @@ packages: resolution: {integrity: sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + xmlbuilder@13.0.2: + resolution: {integrity: sha512-Eux0i2QdDYKbdbA6AM6xE4m6ZTZr4G4xF9kahI2ukSEMCzwce2eX9WlTI5J3s+NU7hpasFsr8hWIONae7LluAQ==} + engines: {node: '>=6.0'} + xtend@4.0.2: resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} engines: {node: '>=0.4'} @@ -4865,6 +5024,12 @@ snapshots: acorn@8.15.0: {} + agent-base@6.0.2: + dependencies: + debug: 4.4.3(supports-color@5.5.0) + transitivePeerDependencies: + - supports-color + ajv@6.12.6: dependencies: fast-deep-equal: 3.1.3 @@ -5075,6 +5240,8 @@ snapshots: dependencies: node-int64: 0.4.0 + buffer-equal-constant-time@1.0.1: {} + buffer-from@1.1.2: {} bytes@3.1.2: {} @@ -5272,6 +5439,8 @@ snapshots: css-what@6.2.2: {} + dayjs@1.11.20: {} + debug@4.4.3(supports-color@5.5.0): dependencies: ms: 2.1.3 @@ -5366,6 +5535,10 @@ snapshots: eastasianwidth@0.2.0: {} + ecdsa-sig-formatter@1.0.11: + dependencies: + safe-buffer: 5.2.1 + editorconfig@1.0.4: dependencies: '@one-ini/wasm': 0.1.1 @@ -5850,6 +6023,13 @@ snapshots: statuses: 2.0.2 toidentifier: 1.0.1 + https-proxy-agent@5.0.1: + dependencies: + agent-base: 6.0.2 + debug: 4.4.3(supports-color@5.5.0) + transitivePeerDependencies: + - supports-color + human-signals@2.1.0: {} iconv-lite@0.6.3: @@ -6633,6 +6813,19 @@ snapshots: json5@2.2.3: {} + jsonwebtoken@9.0.3: + dependencies: + jws: 4.0.1 + lodash.includes: 4.3.0 + lodash.isboolean: 3.0.3 + lodash.isinteger: 4.0.4 + lodash.isnumber: 3.0.3 + lodash.isplainobject: 4.0.6 + lodash.isstring: 4.0.1 + lodash.once: 4.1.1 + ms: 2.1.3 + semver: 7.7.3 + juice@7.0.0: dependencies: cheerio: 1.1.2 @@ -6643,6 +6836,17 @@ snapshots: transitivePeerDependencies: - encoding + jwa@2.0.1: + dependencies: + buffer-equal-constant-time: 1.0.1 + ecdsa-sig-formatter: 1.0.11 + safe-buffer: 5.2.1 + + jws@4.0.1: + dependencies: + jwa: 2.0.1 + safe-buffer: 5.2.1 + keyv@4.5.4: dependencies: json-buffer: 3.0.1 @@ -6666,10 +6870,24 @@ snapshots: dependencies: p-locate: 5.0.0 + lodash.includes@4.3.0: {} + + lodash.isboolean@3.0.3: {} + + lodash.isinteger@4.0.4: {} + + lodash.isnumber@3.0.3: {} + + lodash.isplainobject@4.0.6: {} + + lodash.isstring@4.0.1: {} + lodash.memoize@4.1.2: {} lodash.merge@4.6.2: {} + lodash.once@4.1.1: {} + lodash@4.17.21: {} long-timeout@0.1.1: {} @@ -7412,6 +7630,8 @@ snapshots: loose-envify: 1.4.0 object-assign: 4.1.1 + scmp@2.1.0: {} + semver@6.3.1: {} semver@7.7.3: {} @@ -7677,6 +7897,19 @@ snapshots: optionalDependencies: fsevents: 2.3.3 + twilio@5.13.1: + dependencies: + axios: 1.13.5 + dayjs: 1.11.20 + https-proxy-agent: 5.0.1 + jsonwebtoken: 9.0.3 + qs: 6.14.1 + scmp: 2.1.0 + xmlbuilder: 13.0.2 + transitivePeerDependencies: + - debug + - supports-color + type-check@0.4.0: dependencies: prelude-ls: 1.2.1 @@ -7839,6 +8072,8 @@ snapshots: imurmurhash: 0.1.4 signal-exit: 4.1.0 + xmlbuilder@13.0.2: {} + xtend@4.0.2: {} y18n@4.0.3: {} diff --git a/skaffold.yaml b/skaffold.yaml index 32d0dc7..61fb7f1 100644 --- a/skaffold.yaml +++ b/skaffold.yaml @@ -89,7 +89,7 @@ profiles: resourceName: python-example namespace: constructive-functions port: 80 - localPort: 8084 + localPort: 8085 - resourceType: service resourceName: knative-job-service namespace: constructive-functions @@ -193,6 +193,50 @@ profiles: namespace: constructive-functions port: 3000 localPort: 3002 + - name: text-embedding + build: + artifacts: + - image: constructive-functions + context: . + docker: + dockerfile: Dockerfile.dev + sync: + manual: + - src: 'functions/**/*.ts' + dest: /usr/src/app + local: + push: false + manifests: + kustomize: + paths: + - k8s/overlays/local-simple + rawYaml: + - generated/text-embedding/k8s/local-deployment.yaml + - generated/text-embedding/k8s/functions-configmap.yaml + deploy: + kubectl: + defaultNamespace: constructive-functions + portForward: + - resourceType: service + resourceName: text-embedding + namespace: constructive-functions + port: 80 + localPort: 8084 + - resourceType: service + resourceName: knative-job-service + namespace: constructive-functions + port: 8080 + localPort: 8080 + - resourceType: service + resourceName: postgres + namespace: constructive-functions + port: 5432 + localPort: 5432 + - resourceType: service + resourceName: constructive-server + namespace: constructive-functions + port: 3000 + localPort: 3002 # All functions together. - name: local-simple @@ -227,6 +271,7 @@ profiles: - generated/python-example/k8s/local-deployment.yaml - generated/send-email/k8s/local-deployment.yaml - generated/send-verification-link/k8s/local-deployment.yaml + - generated/text-embedding/k8s/local-deployment.yaml - generated/functions-configmap.yaml deploy: kubectl: @@ -241,7 +286,7 @@ profiles: resourceName: python-example namespace: constructive-functions port: 80 - localPort: 8084 + localPort: 8085 - resourceType: service resourceName: send-email namespace: constructive-functions @@ -252,6 +297,11 @@ profiles: namespace: constructive-functions port: 80 localPort: 8082 + - resourceType: service + resourceName: text-embedding + namespace: constructive-functions + port: 80 + localPort: 8084 - resourceType: service resourceName: knative-job-service namespace: constructive-functions @@ -296,7 +346,7 @@ profiles: resourceName: python-example namespace: constructive-functions port: 80 - localPort: 8084 + localPort: 8085 - resourceType: service resourceName: send-email namespace: constructive-functions @@ -307,6 +357,11 @@ profiles: namespace: constructive-functions port: 80 localPort: 8082 + - resourceType: service + resourceName: text-embedding + namespace: constructive-functions + port: 80 + localPort: 8084 - resourceType: service resourceName: knative-job-service namespace: constructive-functions From 88ed26edc9188029322329eb00c72e7c65dec401 Mon Sep 17 00:00:00 2001 From: Lucas Jiang <2862605953@qq.com> Date: Sat, 16 May 2026 15:05:45 +0800 Subject: [PATCH 2/2] fix: update lockfile for text-embedding generated package --- pnpm-lock.yaml | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index eccc67e..671a4ba 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -240,6 +240,25 @@ importers: specifier: ^5.1.6 version: 5.9.3 + generated/text-embedding: + dependencies: + '@agentic-kit/ollama': + specifier: ^1.0.3 + version: 1.2.0 + '@constructive-io/fn-runtime': + specifier: workspace:^ + version: link:../../packages/fn-runtime + devDependencies: + '@types/node': + specifier: ^22.10.4 + version: 22.19.3 + makage: + specifier: ^0.1.10 + version: 0.1.12 + typescript: + specifier: ^5.1.6 + version: 5.9.3 + job/server: dependencies: '@constructive-io/job-pg': @@ -512,6 +531,9 @@ packages: 12factor-env@1.6.2: resolution: {integrity: sha512-U4EO6sy9Cc6h1ST3hhLD2rc2s4LERxProove3XZ52rMq2rTo5uTKWNKwD2OYDUwqNij+p5SgjmpPO6L/Gqtizw==} + '@agentic-kit/ollama@1.2.0': + resolution: {integrity: sha512-MWVawKqphgs6Dq2FdWGvILw/72Eqg1EEUQAaACxX+CvQJTF4ArtNdTbQCLfPM8kk0l3JESI2DBHSccckj6pmag==} + '@babel/code-frame@7.27.1': resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==} engines: {node: '>=6.9.0'} @@ -3837,6 +3859,8 @@ snapshots: dependencies: envalid: 8.1.1 + '@agentic-kit/ollama@1.2.0': {} + '@babel/code-frame@7.27.1': dependencies: '@babel/helper-validator-identifier': 7.28.5