{"id":454346,"date":"2025-04-01T21:01:34","date_gmt":"2025-04-01T21:01:34","guid":{"rendered":"http:\/\/savepearlharbor.com\/?p=454346"},"modified":"-0001-11-30T00:00:00","modified_gmt":"-0001-11-29T21:00:00","slug":"","status":"publish","type":"post","link":"https:\/\/savepearlharbor.com\/?p=454346","title":{"rendered":"<span>\u0411\u0430\u043b\u0430\u043d\u0441\u0438\u0440\u043e\u0432\u043a\u0430 \u043d\u0430\u0433\u0440\u0443\u0437\u043a\u0438 LLM \u0447\u0435\u0440\u0435\u0437 Nginx<\/span>"},"content":{"rendered":"<div><!--[--><!--]--><\/div>\n<div id=\"post-content-body\">\n<div>\n<div class=\"article-formatted-body article-formatted-body article-formatted-body_version-2\">\n<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<blockquote>\n<p>\u0418\u0441\u0445\u043e\u0434\u043d\u044b\u0439 \u043a\u043e\u0434, \u0440\u0430\u0437\u043e\u0431\u0440\u0430\u043d\u043d\u044b\u0439 \u0432 \u0441\u0442\u0430\u0442\u044c\u0435, <a href=\"https:\/\/github.com\/tripolskypetr\/agent-swarm-kit\/tree\/master\/demo\/nginx-balancer-chat\" rel=\"noopener noreferrer nofollow\">\u043e\u043f\u0443\u0431\u043b\u0438\u043a\u043e\u0432\u0430\u043d \u0432 \u044d\u0442\u043e\u043c \u0440\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u0438<\/a><\/p>\n<\/blockquote>\n<p>\u0412 \u0438\u043d\u0442\u0435\u0440\u043d\u0435\u0442\u0435 \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u0435\u0442 \u043c\u043d\u043e\u0436\u0435\u0441\u0442\u0432\u043e \u043f\u0440\u0438\u043c\u0435\u0440\u043e\u0432, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u044e\u0442 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c ChatGPT 3.5 \u0431\u0435\u0437 \u0438\u043d\u0441\u0442\u0440\u0443\u043c\u0435\u043d\u0442\u043e\u0432 \u043a \u0442\u0435\u043b\u0435\u0433\u0440\u0430\u043c \u0431\u043e\u0442\u0443. \u041e\u0434\u043d\u0430\u043a\u043e, \u043a\u043e\u0433\u0434\u0430 \u0440\u0435\u0447\u044c \u0437\u0430\u0445\u043e\u0434\u0438\u0442 \u043e \u0431\u043e\u043b\u044c\u0448\u043e\u043c \u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u0435 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0435\u0439, \u043d\u0435 \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u044e\u0442 \u043f\u0440\u0438\u043c\u0435\u0440\u043e\u0432 \u0440\u0430\u0441\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u0438\u044f \u043d\u0430\u0433\u0440\u0443\u0437\u043a\u0438 \u043f\u043e \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u0438\u043c \u043f\u0440\u043e\u0446\u0435\u0441\u0441\u0430\u043c: \u0432\u0441\u0435 \u0442\u0443\u0442\u043e\u0440\u0438\u0430\u043b\u044b \u0432 \u0438\u043d\u0442\u0435\u0440\u043d\u0435\u0442\u0435 \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u044e\u0442 \u043c\u043e\u043d\u043e\u043b\u0438\u0442 \u0441 \u043e\u0434\u043d\u043e\u0439 \u0440\u0435\u043f\u043b\u0438\u043a\u043e\u0439<\/p>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/d69\/6be\/48f\/d696be48fa6cf4826ae7b3c03c08a7c7.png\" alt=\"https:\/\/github.com\/telegraf\/telegraf\/issues\/423\" title=\"https:\/\/github.com\/telegraf\/telegraf\/issues\/423\" width=\"728\" height=\"649\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/d69\/6be\/48f\/d696be48fa6cf4826ae7b3c03c08a7c7.png\"\/><\/p>\n<div><figcaption><a href=\"https:\/\/github.com\/telegraf\/telegraf\/issues\/423\" rel=\"noopener noreferrer nofollow\">https:\/\/github.com\/telegraf\/telegraf\/issues\/423<\/a><\/figcaption><\/div>\n<\/figure>\n<p>\u0422\u0430\u043a \u0436\u0435, \u043d\u0430 \u043f\u0440\u0430\u043a\u0442\u0438\u043a\u0435 \u0440\u0430\u0431\u043e\u0442\u044b \u0441 NodeJS, \u044f \u0441\u0442\u0430\u043b\u043a\u0438\u0432\u0430\u043b\u0441\u044f \u0441 \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u043e\u0439, \u043a\u043e\u0433\u0434\u0430 \u043c\u043d\u043e\u0433\u043e <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/JavaScript\/Reference\/Global_Objects\/Promise\" rel=\"noopener noreferrer nofollow\">Promise \u0432 \u0441\u0442\u0430\u0442\u0443\u0441\u0435 pending<\/a> \u0437\u0430\u043c\u0435\u0434\u043b\u044f\u044e\u0442 \u0440\u0430\u0431\u043e\u0442\u0443 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u043d\u0430\u0433\u0440\u0443\u0437\u043a\u043e\u0439 \u043d\u0430 \u0441\u0431\u043e\u0440\u0449\u0438\u043a \u043c\u0443\u0441\u043e\u0440\u0430. <a href=\"https:\/\/platform.openai.com\/docs\/guides\/function-calling\" rel=\"noopener noreferrer nofollow\">\u0414\u043e\u0431\u0430\u0432\u0438\u0432 \u0441\u0442\u043e\u0440\u043e\u043d\u043d\u0438\u0435 \u0438\u043d\u0441\u0442\u0440\u0443\u043c\u0435\u043d\u0442\u044b<\/a> (\u0434\u0430\u0432 ChatGPT \u0432\u044b\u0437\u044b\u0432\u0430\u0442\u044c \u0432\u043d\u0435\u0448\u043d\u0438\u0438 \u0444\u0443\u043d\u043a\u0446\u0438\u0438 \u0441 \u0437\u0430\u043f\u0440\u043e\u0441\u0430\u043c\u0438 \u043a \u0431\u0430\u0437\u0435 \u0434\u0430\u043d\u043d\u044b\u0445) \u043f\u043e\u0442\u0440\u0435\u0431\u0443\u0435\u0442\u0441\u044f \u0434\u0443\u043c\u0430\u0442\u044c \u043a\u0430\u043a \u043c\u0438\u043d\u0438\u043c\u0443\u043c \u043e \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u0438 \u0440\u0435\u043f\u043b\u0438\u043a \u043c\u043e\u043d\u043e\u043b\u0438\u0442\u0430, \u0447\u0442\u043e\u0431\u044b \u0440\u0430\u0437\u0434\u0443\u0432\u0430\u043d\u0438\u044f \u043e\u0447\u0435\u0440\u0435\u0434\u0438 \u043e\u0436\u0438\u0434\u0430\u043d\u0438\u044f \u0434\u0430\u043d\u043d\u044b\u0445 \u0438\u0437 \u0431\u0430\u0437\u044b<\/p>\n<h2>\u0410\u0440\u0445\u0438\u0442\u0435\u043a\u0442\u0443\u0440\u0430 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f<\/h2>\n<p>\u0414\u043b\u044f \u0431\u0430\u043b\u0430\u043d\u0441\u0438\u0440\u043e\u0432\u043a\u0438 \u043d\u0430\u0433\u0440\u0443\u0437\u043a\u0438 \u0441\u0430\u043c\u044b\u043c \u043e\u0447\u0435\u0432\u0438\u0434\u043d\u044b\u043c \u0438\u043d\u0441\u0442\u0440\u0443\u043c\u0435\u043d\u0442\u043e\u043c \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f <a href=\"https:\/\/codeforgeek.com\/load-balancing-nodejs-servers-nginx\/\" rel=\"noopener noreferrer nofollow\">Nginx upstream<\/a> &#8212;  \u0438\u043d\u0441\u0442\u0440\u0443\u043c\u0435\u043d\u0442 \u043f\u043e\u043b\u0443\u0447\u0430\u0435\u0442 WebSocket \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u0435 \u043d\u043e\u0432\u043e\u0433\u043e \u043a\u043b\u0438\u0435\u043d\u0442\u0430 \u043d\u0430 \u043f\u043e\u0440\u0442 80 \u0438 \u0432 \u043f\u043e\u0440\u044f\u0434\u043a\u0435 \u043e\u0447\u0435\u0440\u0435\u0434\u0438 \u043f\u0440\u043e\u043a\u0441\u0438\u0440\u0443\u0435\u0442 \u0435\u0433\u043e \u043d\u0430 8081, 8082, &#8230;, 8085 \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e \u043e\u0442 \u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u0430 \u0440\u0435\u043f\u043b\u0438\u043a. \u0415\u0441\u043b\u0438 \u043a\u043b\u0438\u0435\u043d\u0442 \u043d\u0435 \u043f\u0440\u043e\u044f\u0432\u043b\u044f\u0435\u0442 \u0430\u043a\u0442\u0438\u0432\u043d\u043e\u0441\u0442\u044c 15 \u043c\u0438\u043d\u0443\u0442, \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u0435 \u043f\u0440\u0438\u0440\u044b\u0432\u0430\u0435\u0442\u0441\u044f, \u0435\u0441\u043b\u0438 \u0431\u0443\u0434\u0435\u0442 \u043d\u043e\u0432\u043e\u0435 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435, \u043e\u043d\u043e \u0441\u043e\u0437\u0434\u0430\u0441\u0442\u0441\u044f \u0437\u0430\u043d\u043e\u0433\u043e.<\/p>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/035\/48f\/724\/03548f724f9102c0a90e8450fb62c7af.png\" alt=\"https:\/\/nginx.org\/en\/docs\/http\/ngx_http_upstream_module.html\" title=\"https:\/\/nginx.org\/en\/docs\/http\/ngx_http_upstream_module.html\" width=\"951\" height=\"421\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/035\/48f\/724\/03548f724f9102c0a90e8450fb62c7af.png\"\/><\/p>\n<div><figcaption><a href=\"https:\/\/nginx.org\/en\/docs\/http\/ngx_http_upstream_module.html\" rel=\"noopener noreferrer nofollow\">https:\/\/nginx.org\/en\/docs\/http\/ngx_http_upstream_module.html<\/a><\/figcaption><\/div>\n<\/figure>\n<p>\u0420\u0435\u043f\u043b\u0438\u043a\u0438 \u0441\u043e\u0445\u0440\u0430\u043d\u044f\u044e\u0442 \u0438\u0441\u0442\u043e\u0440\u0438\u044e \u043f\u0435\u0440\u0435\u043f\u0438\u0441\u043a\u0438 \u0432 Redis, \u0447\u0442\u043e \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u0432\u043e\u0441\u0441\u043e\u0437\u0434\u0430\u0442\u044c \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442 \u043d\u0435 \u0441\u043c\u043e\u0442\u0440\u044f \u043d\u0430 \u0442\u043e, \u0447\u0442\u043e \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f \u043e\u0431\u0440\u0430\u0431\u0430\u0442\u044b\u0432\u0430\u0435\u0442 \u043d\u043e\u0432\u044b\u0439 \u043f\u0440\u043e\u0446\u0435\u0441\u0441. \u0421\u043e\u0437\u0434\u0430\u0432\u0430\u0442\u044c \u0440\u0435\u043f\u043b\u0438\u043a\u0438 \u0431\u0443\u0434\u0435\u0442 <a href=\"https:\/\/pm2.io\/\" rel=\"noopener noreferrer nofollow\">PM2 <\/a>&#8212; \u043d\u0430\u0438\u0431\u043e\u043b\u0435\u0435 \u043d\u0430\u0442\u0438\u0432\u043d\u044b\u0439 \u0441\u043f\u043e\u0441\u043e\u0431 \u0434\u043b\u044f \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0439 \u0441\u0442\u0435\u043a\u0430 NodeJS<\/p>\n<figure class=\"full-width\"><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/getpro\/habr\/upload_files\/95c\/4ac\/783\/95c4ac7836cd109ba2b5709e95cc8474.png\" alt=\"https:\/\/pm2.io\/docs\/plus\/overview\/\" title=\"https:\/\/pm2.io\/docs\/plus\/overview\/\" width=\"2172\" height=\"1016\" data-src=\"https:\/\/habrastorage.org\/getpro\/habr\/upload_files\/95c\/4ac\/783\/95c4ac7836cd109ba2b5709e95cc8474.png\"\/><\/p>\n<div><figcaption><a href=\"https:\/\/pm2.io\/docs\/plus\/overview\/\" rel=\"noopener noreferrer nofollow\">https:\/\/pm2.io\/docs\/plus\/overview\/<\/a><\/figcaption><\/div>\n<\/figure>\n<p>\u0422\u0430\u043a \u0436\u0435, \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044f PM2, \u0437\u0430 40$ \u0432 \u043c\u0435\u0441\u044f\u0446 \u043c\u043e\u0436\u043d\u043e \u043a\u0443\u043f\u0438\u0442\u044c \u0433\u043e\u0442\u043e\u0432\u043e\u0435 \u0443\u0432\u0435\u0434\u043e\u043c\u043b\u0435\u043d\u0438\u0435 \u043e \u0438\u043d\u0446\u0438\u0434\u0435\u043d\u0442\u0430\u0445 \u043f\u043e Slack \/ Email, \u043c\u043e\u043d\u0438\u0442\u043e\u0440\u0438\u043d\u0433 \u0441\u0435\u0440\u0432\u0435\u0440\u0430 \u043f\u043e \u0441\u0442\u0430\u043d\u0434\u0430\u0440\u0442\u043d\u044b\u043c \u043c\u0435\u0442\u0440\u0438\u043a\u0430\u043c \u0442\u0430\u043a\u0438\u043c \u043a\u0430\u043a CPUs: cores, hardware threads, virtual threads, Memory: capacity, Network interfaces, Storage devices: I\/O, capacity, \u0432\u0440\u0435\u043c\u044f \u043e\u0442\u043a\u043b\u0438\u043a\u0430 \u0438 \u0442\u0434. \u042d\u0442\u043e \u044d\u043a\u043e\u043d\u043e\u043c\u0438\u0442 \u0434\u0435\u043d\u044c\u0433\u0438 \u043d\u0430 Dev Ops \u0434\u043e \u0441\u0430\u043c\u043e\u043e\u043a\u0443\u043f\u0430\u0435\u043c\u043e\u0441\u0442\u0438 \u043f\u0440\u043e\u0435\u043a\u0442\u0430.<\/p>\n<h2>\u0424\u0430\u0439\u043b\u044b \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u0438<\/h2>\n<p>\u0427\u0442\u043e\u0431\u044b \u043d\u0435 \u0441\u043f\u0435\u0446\u0438\u0430\u043b\u0438\u0437\u0438\u0440\u043e\u0432\u0430\u0442\u044c Linux \u043d\u0430 \u043c\u0430\u0448\u0438\u043d\u0435 \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a\u0430 \u043f\u043e\u0434 \u043e\u0434\u0438\u043d \u043f\u0440\u043e\u0435\u043a\u0442, \u043e\u0431\u0435\u0440\u043d\u0435\u043c Nginx \u0432 Docker. \u0414\u043b\u044f \u044d\u0442\u043e\u0433\u043e, \u043d\u0430\u043f\u0438\u0448\u0435\u043c <code>docker-compose.yaml<\/code><\/p>\n<pre><code class=\"yaml\">version: '3.8'  services:   nginx:     image: nginx:1.27.4     ports:       - \"80:80\"     extra_hosts:       - \"host.docker.internal:host-gateway\"     volumes:       - .\/config\/nginx.conf:\/etc\/nginx\/nginx.conf:ro       - .\/logs\/nginx:\/var\/log\/nginx <\/code><\/pre>\n<p>\u0418 \u0441\u043e\u0437\u0434\u0430\u0434\u0438\u043c \u0441\u043e\u043f\u0443\u0442\u0441\u0442\u0432\u0443\u044e\u0449\u0438\u0439 <code>.\/config\/nginx.config<\/code> \u0441 \u043f\u0435\u0440\u0435\u0447\u0438\u0441\u043b\u0435\u043d\u0438\u0435\u043c \u0440\u0435\u043f\u043b\u0438\u043a<\/p>\n<pre><code class=\"python\">user nginx; worker_processes auto;  error_log \/var\/log\/nginx\/error.log warn; pid \/var\/run\/nginx.pid;  events {     worker_connections 1024;     use epoll;     multi_accept on; }  http {     include \/etc\/nginx\/mime.types;     default_type application\/octet-stream;      log_format main '$remote_addr - $remote_user [$time_local] \"$request\" '                     '$status $body_bytes_sent \"$http_referer\" '                     '\"$http_user_agent\" \"$http_x_forwarded_for\"';      access_log \/var\/log\/nginx\/access.log main;      upstream local_websocket_servers {         server host.docker.internal:8081;  # Using host.docker.internal from hosts shared from host machine         server host.docker.internal:8082;         server host.docker.internal:8083;         server host.docker.internal:8084;         server host.docker.internal:8085;         least_conn;     }      map $http_upgrade $connection_upgrade {         default upgrade;         ''      close;     }      server {         listen 80;         server_name localhost;          location \/ {             proxy_pass http:\/\/local_websocket_servers$is_args$args;                          # WebSocket-specific headers             proxy_http_version 1.1;             proxy_set_header Upgrade $http_upgrade;             proxy_set_header Connection $connection_upgrade;                          # Preserve original headers and connection details             proxy_set_header Host $host;             proxy_set_header X-Real-IP $remote_addr;             proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;             proxy_set_header X-Forwarded-Proto $scheme;              # Close upstream if client disconnects             proxy_ignore_client_abort on;                          # Long-lived connection settings             proxy_read_timeout 86400s;             proxy_send_timeout 86400s;                          # Buffer and performance settings             proxy_buffer_size 128k;             proxy_buffers 4 256k;             proxy_busy_buffers_size 256k;         }     } } <\/code><\/pre>\n<p>\u0414\u043b\u044f \u0437\u0430\u043f\u0443\u0441\u043a\u0430 \u0440\u0435\u043f\u043b\u0438\u043a \u0447\u0435\u0440\u0435\u0437 pm2 \u0441\u043e\u0437\u0434\u0430\u0434\u0438\u043c <code>pm2.config.cjs<\/code>. <a href=\"https:\/\/github.com\/tripolskypetr\/agent-swarm-kit\/blob\/master\/demo\/nginx-balancer-chat\/package.json\" rel=\"noopener noreferrer nofollow\">\u041f\u043e \u0441\u0441\u044b\u043b\u043a\u0435 \u043b\u0435\u0436\u0438\u0442 package.json<\/a> \u0441 \u0441\u043a\u0440\u0438\u043f\u0442\u0430\u043c\u0438 \u0434\u043b\u044f \u0437\u0430\u043f\u0443\u0441\u043a\u0430 \u043f\u0440\u043e\u0435\u043a\u0442\u0430 \u0447\u0435\u0440\u0435\u0437 <code>npm start<\/code><\/p>\n<pre><code class=\"javascript\">const path = require(\"path\"); const os = require(\"os\"); const dotenv = require('dotenv');  const readConfig = () =&gt; dotenv.parse(\".\/.env\");  const getPath = (unixPath) =&gt; {   return path.resolve(unixPath.replace('~', os.homedir())); };  const createBun = (index) =&gt; ({   name: `bun-ws-${index}`,   script: \".\/src\/server.ts\",   interpreter: getPath(\"~\/.bun\/bin\/bun\"),   args: [\"--server\", `--port=808${index}`],   out_file: `.\/logs\/pm2\/bun-ws-${index}-out.log`,   error_file: `.\/logs\/pm2\/bun-ws-${index}-error.log`,   log_date_format: \"YYYY-MM-DD HH:mm:ss\",   merge_logs: true,   env: readConfig(), });  module.exports = {   apps: [     \/*     {       name: \"bun-ws-1\",       script: \".\/src\/server.ts\",       interpreter: getPath(\"~\/.bun\/bin\/bun\"),       args: [\"--server\", \"--port=8081\"],       out_file: \".\/logs\/pm2\/bun-ws-1-out.log\",       error_file: \".\/logs\/pm2\/bun-ws-1-error.log\",       log_date_format: \"YYYY-MM-DD HH:mm:ss\",       merge_logs: true,     },     *\/     createBun(1),     createBun(2),     createBun(3),     createBun(4),     createBun(5),   ] } <\/code><\/pre>\n<p>\u041a\u0430\u043a \u0432\u0438\u0434\u043d\u043e, \u043d\u043e\u043c\u0435\u0440 \u043f\u043e\u0440\u0442\u0430 \u0434\u043b\u044f \u043e\u0440\u043a\u0435\u0441\u0442\u0440\u0430\u0446\u0438\u0438 \u043c\u044b \u043f\u0435\u0440\u0435\u0434\u0430\u0435\u043c \u0447\u0435\u0440\u0435\u0437 \u0430\u0440\u0433\u0443\u043c\u0435\u043d\u0442\u044b \u043a\u043e\u043c\u0430\u043d\u0434\u043d\u043e\u0439 \u0441\u0442\u0440\u043e\u043a\u0438. \u041d\u0430\u0438\u0431\u043e\u043b\u0435\u0435 \u0443\u0434\u043e\u0431\u043d\u043e, \u0442\u0430\u043a \u043a\u0430\u043a \u0444\u0430\u0439\u043b <code>.env<\/code> \u043e\u0441\u0442\u0430\u0435\u0442\u0441\u044f \u0441\u0442\u0430\u0442\u0438\u0447\u043d\u044b\u043c \u0431\u0435\u0437 \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u044f \u0432 \u0440\u0430\u043d\u0442\u0430\u0439\u043c\u0435. \u0414\u043b\u044f \u0437\u0430\u043f\u0443\u0441\u043a\u0430 \u043f\u0440\u043e\u0435\u043a\u0442\u0430 \u043c\u044b \u0438\u0441\u043f\u043e\u043b\u044c\u0443\u0435\u043c <a href=\"https:\/\/bun.sh\/\" rel=\"noopener noreferrer nofollow\">Bun <\/a>&#8212; \u0443\u0441\u043a\u043e\u0440\u0435\u043d\u043d\u044b\u0439 \u0430\u043d\u0430\u043b\u043e\u0433 NodeJS \u043f\u043e \u0441\u043a\u043e\u0440\u043e\u0441\u0442\u0438 \u0441\u043e\u043f\u043e\u0441\u0442\u0430\u0432\u0438\u043c\u044b\u0439 \u0441 Golang<\/p>\n<h3>\u0420\u043e\u0439 \u0430\u0433\u0435\u043d\u0442\u043e\u0432<\/h3>\n<p>\u0410\u0433\u0435\u043d\u0442 &#8212; \u0430\u043d\u0430\u043b\u043e\u0433 \u0441\u0446\u0435\u043d\u044b \u0432 telegraf, \u043c\u043e\u0434\u0435\u043b\u044c LLM, \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044f\u0449\u0430\u044f \u0438\u0437\u043e\u043b\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0439 system prompt. \u0422\u0435\u043a\u0443\u0449\u0438\u0439 \u0430\u0433\u0435\u043d\u0442 \u0432 Swarm \u043c\u043e\u0436\u0435\u0442 \u0431\u044b\u0442\u044c \u0438\u0437\u043c\u0435\u043d\u0435\u043d \u0447\u0435\u0440\u0435\u0437 \u0432\u044b\u0437\u043e\u0432 \u0444\u0443\u043d\u043a\u0446\u0438\u0438 <code>changeToAgent <\/code>&#8212; \u0430\u043d\u0430\u043b\u043e\u0433 \u043d\u0430\u0432\u0438\u0433\u0430\u0446\u0438\u0438 \u043f\u043e \u0441\u0446\u0435\u043d\u0430\u043c \u0432 \u0442\u0435\u043b\u0435\u0433\u0440\u0430\u043c \u0431\u043e\u0442\u0435 \u043f\u043e\u0441\u043b\u0435 \u043a\u043b\u0438\u043a\u0430 \u043f\u043e \u043a\u043d\u043e\u043f\u043a\u0435<\/p>\n<pre><code class=\"typescript\">import { Adapter, addAgent, addCompletion, addSwarm } from \"agent-swarm-kit\"; import { OpenAI } from \"openai\";  export const OPENAI_COMPLETION = addCompletion({   completionName: \"openai_completion\",   getCompletion: Adapter.fromOpenAI(new OpenAI({ apiKey: process.env.OPENAI_API_KEY })) });  export const TEST_AGENT = addAgent({   docDescription: \"This agent operates within the nginx-balancer-chat project as a test agent, utilizing the OpenaiCompletion to inform users about the actual server port of one of 5 chat instances running on different ports and upstreamed by Nginx to port 80, extracting the port details from the chat history\u2019s system message.\",   agentName: \"test_agent\",   completion: OPENAI_COMPLETION,   prompt: `You are a test agent for Nginx Upstream. Tell user the server port from the chat history (system message)`,   dependsOn: [], });  export const TEST_SWARM = addSwarm({   docDescription: \"This swarm serves as the core structure for the nginx-balancer-chat project, managing a single TestAgent as both the sole member and default agent to handle user interactions, leveraging the CohereCompletion to report the specific port of one of 5 upstreamed chat instances balanced by Nginx to port 80.\",   swarmName: \"test_swarm\",   agentList: [TEST_AGENT],   defaultAgent: TEST_AGENT, }); <\/code><\/pre>\n<p>\u041a\u043e\u0434 \u044d\u0442\u043e\u0433\u043e \u043f\u0440\u0438\u043c\u0435\u0440\u0430 \u0437\u0430\u043f\u0440\u043e\u0433\u0440\u0430\u043c\u043c\u0438\u0440\u043e\u0432\u0430\u043d \u0442\u0430\u043a, \u0447\u0442\u043e\u0431\u044b LLM \u043c\u043e\u0434\u0435\u043b\u044c \u043d\u0430\u0437\u0432\u0430\u043b\u0430, \u0441 \u043a\u0430\u043a\u043e\u0433\u043e \u043f\u043e\u0440\u0442\u0430 \u0431\u044b\u043b \u043f\u0440\u043e\u043a\u0441\u0438\u0440\u043e\u0432\u0430\u043d \u0437\u0430\u043f\u0440\u043e\u0441 WebSocket. \u0412\u043c\u0435\u0441\u0442\u043e <code>OPENAI_COMPLETION <\/code>\u0434\u043b\u044f \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u0430\u0433\u0435\u043d\u0442\u0430 \u043f\u043e \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u0438 \u043c\u043e\u0436\u043d\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c <code>LMStudio<\/code>, <code>Ollama<\/code>, <code>Cohere<\/code>, \u0431\u043e\u043b\u0435\u0435 \u043f\u043e\u0434\u0440\u043e\u0431\u043d\u0435\u0435 \u0432 \u0440\u0435\u043f\u043e<\/p>\n<pre><code class=\"typescript\">import { Chat, getAgentName, Schema, History } from \"agent-swarm-kit\"; import type { ServerWebSocket } from \"bun\"; import { parseArgs } from \"util\"; import { TEST_SWARM } from \".\/lib\/swarm\";  declare function parseInt(value: unknown): number;  type WebSocketData = {   clientId: string; };  const { values } = parseArgs({   args: process.argv,   options: {     server: {       type: \"boolean\",     },     port: {       type: \"string\",     },   },   strict: true,   allowPositionals: true, });   History.useHistoryCallbacks({   getSystemPrompt: () =&gt; [     `The server port is ${SERVER_PORT}. Tell him that port ASAP`   ] });  const SERVER_PORT = parseInt(values.port);  if (isNaN(SERVER_PORT)) {   throw new Error(`Server port is not a number: ${values.port}`); }  if (values.server) {   Bun.serve({     fetch(req, server) {       const clientId = new URL(req.url).searchParams.get(\"clientId\")!;       if (!clientId) {         return new Response(\"Invalid clientId\", { status: 500 });       }       console.log(`Connected clientId=${clientId} port=${SERVER_PORT}`);       server.upgrade&lt;WebSocketData&gt;(req, {         data: {           clientId,         },       });     },     websocket: {       async open(ws: ServerWebSocket&lt;WebSocketData&gt;) {         await Chat.beginChat(ws.data.clientId, TEST_SWARM);         await Schema.writeSessionMemory(ws.data.clientId, { port: SERVER_PORT });       },       async message(ws: ServerWebSocket&lt;WebSocketData&gt;, message: string) {         const answer = await Chat.sendMessage(ws.data.clientId, message, TEST_SWARM);         ws.send(           JSON.stringify({             data: answer,             agentName: await getAgentName(ws.data.clientId),           })         );       },       async close(ws: ServerWebSocket&lt;WebSocketData&gt;) {         console.log(`Disconnected clientId=${ws.data.clientId} port=${SERVER_PORT}`);         await Chat.dispose(ws.data.clientId, TEST_SWARM);       },     },     port: SERVER_PORT,   }); }  console.log(`Server listening http:\/\/localhost:${SERVER_PORT}`) <\/code><\/pre>\n<p>\u0415\u0441\u043b\u0438 \u0438\u043d\u0442\u0435\u0440\u0435\u0441\u0443\u0435\u0442 \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0430 \u0447\u0430\u0442\u043e\u0432 \u0441 \u0431\u043e\u043b\u0435\u0435 \u0447\u0435\u043c \u043e\u0434\u043d\u0438\u043c \u0430\u0433\u0435\u043d\u0442\u043e\u043c, <a href=\"https:\/\/agent-swarm.github.io\/documents\/docs_getting-started.html\" rel=\"noopener noreferrer nofollow\">\u043f\u043e\u0441\u043c\u043e\u0442\u0440\u0438\u0442\u0435 \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430\u0446\u0438\u044f<\/a><\/p>\n<h2>\u0421\u043f\u0430\u0441\u0438\u0431\u043e \u0437\u0430 \u0432\u043d\u0438\u043c\u0430\u043d\u0438\u0435!<\/h2>\n<\/p>\n<\/div>\n<\/div>\n<\/div>\n<p><!----><!----><\/div>\n<p><!----><!----><br \/> \u0441\u0441\u044b\u043b\u043a\u0430 \u043d\u0430 \u043e\u0440\u0438\u0433\u0438\u043d\u0430\u043b \u0441\u0442\u0430\u0442\u044c\u0438 <a href=\"https:\/\/habr.com\/ru\/articles\/896222\/\"> https:\/\/habr.com\/ru\/articles\/896222\/<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<div><!--[--><!--]--><\/div>\n<div id=\"post-content-body\">\n<div>\n<div class=\"article-formatted-body article-formatted-body article-formatted-body_version-2\">\n<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<blockquote>\n<p>\u0418\u0441\u0445\u043e\u0434\u043d\u044b\u0439 \u043a\u043e\u0434, \u0440\u0430\u0437\u043e\u0431\u0440\u0430\u043d\u043d\u044b\u0439 \u0432 \u0441\u0442\u0430\u0442\u044c\u0435, <a href=\"https:\/\/github.com\/tripolskypetr\/agent-swarm-kit\/tree\/master\/demo\/nginx-balancer-chat\" rel=\"noopener noreferrer nofollow\">\u043e\u043f\u0443\u0431\u043b\u0438\u043a\u043e\u0432\u0430\u043d \u0432 \u044d\u0442\u043e\u043c \u0440\u0435\u043f\u043e\u0437\u0438\u0442\u043e\u0440\u0438\u0438<\/a><\/p>\n<\/blockquote>\n<p>\u0412 \u0438\u043d\u0442\u0435\u0440\u043d\u0435\u0442\u0435 \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u0435\u0442 \u043c\u043d\u043e\u0436\u0435\u0441\u0442\u0432\u043e \u043f\u0440\u0438\u043c\u0435\u0440\u043e\u0432, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u044e\u0442 \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c ChatGPT 3.5 \u0431\u0435\u0437 \u0438\u043d\u0441\u0442\u0440\u0443\u043c\u0435\u043d\u0442\u043e\u0432 \u043a \u0442\u0435\u043b\u0435\u0433\u0440\u0430\u043c \u0431\u043e\u0442\u0443. \u041e\u0434\u043d\u0430\u043a\u043e, \u043a\u043e\u0433\u0434\u0430 \u0440\u0435\u0447\u044c \u0437\u0430\u0445\u043e\u0434\u0438\u0442 \u043e \u0431\u043e\u043b\u044c\u0448\u043e\u043c \u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u0435 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0435\u0439, \u043d\u0435 \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u044e\u0442 \u043f\u0440\u0438\u043c\u0435\u0440\u043e\u0432 \u0440\u0430\u0441\u043f\u0440\u0435\u0434\u0435\u043b\u0435\u043d\u0438\u044f \u043d\u0430\u0433\u0440\u0443\u0437\u043a\u0438 \u043f\u043e \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u0438\u043c \u043f\u0440\u043e\u0446\u0435\u0441\u0441\u0430\u043c: \u0432\u0441\u0435 \u0442\u0443\u0442\u043e\u0440\u0438\u0430\u043b\u044b \u0432 \u0438\u043d\u0442\u0435\u0440\u043d\u0435\u0442\u0435 \u0437\u0430\u043f\u0443\u0441\u043a\u0430\u044e\u0442 \u043c\u043e\u043d\u043e\u043b\u0438\u0442 \u0441 \u043e\u0434\u043d\u043e\u0439 \u0440\u0435\u043f\u043b\u0438\u043a\u043e\u0439<\/p>\n<figure class=\"full-width\">\n<div><figcaption><a href=\"https:\/\/github.com\/telegraf\/telegraf\/issues\/423\" rel=\"noopener noreferrer nofollow\">https:\/\/github.com\/telegraf\/telegraf\/issues\/423<\/a><\/figcaption><\/div>\n<\/figure>\n<p>\u0422\u0430\u043a \u0436\u0435, \u043d\u0430 \u043f\u0440\u0430\u043a\u0442\u0438\u043a\u0435 \u0440\u0430\u0431\u043e\u0442\u044b \u0441 NodeJS, \u044f \u0441\u0442\u0430\u043b\u043a\u0438\u0432\u0430\u043b\u0441\u044f \u0441 \u043f\u0440\u043e\u0431\u043b\u0435\u043c\u043e\u0439, \u043a\u043e\u0433\u0434\u0430 \u043c\u043d\u043e\u0433\u043e <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Web\/JavaScript\/Reference\/Global_Objects\/Promise\" rel=\"noopener noreferrer nofollow\">Promise \u0432 \u0441\u0442\u0430\u0442\u0443\u0441\u0435 pending<\/a> \u0437\u0430\u043c\u0435\u0434\u043b\u044f\u044e\u0442 \u0440\u0430\u0431\u043e\u0442\u0443 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f \u043d\u0430\u0433\u0440\u0443\u0437\u043a\u043e\u0439 \u043d\u0430 \u0441\u0431\u043e\u0440\u0449\u0438\u043a \u043c\u0443\u0441\u043e\u0440\u0430. <a href=\"https:\/\/platform.openai.com\/docs\/guides\/function-calling\" rel=\"noopener noreferrer nofollow\">\u0414\u043e\u0431\u0430\u0432\u0438\u0432 \u0441\u0442\u043e\u0440\u043e\u043d\u043d\u0438\u0435 \u0438\u043d\u0441\u0442\u0440\u0443\u043c\u0435\u043d\u0442\u044b<\/a> (\u0434\u0430\u0432 ChatGPT \u0432\u044b\u0437\u044b\u0432\u0430\u0442\u044c \u0432\u043d\u0435\u0448\u043d\u0438\u0438 \u0444\u0443\u043d\u043a\u0446\u0438\u0438 \u0441 \u0437\u0430\u043f\u0440\u043e\u0441\u0430\u043c\u0438 \u043a \u0431\u0430\u0437\u0435 \u0434\u0430\u043d\u043d\u044b\u0445) \u043f\u043e\u0442\u0440\u0435\u0431\u0443\u0435\u0442\u0441\u044f \u0434\u0443\u043c\u0430\u0442\u044c \u043a\u0430\u043a \u043c\u0438\u043d\u0438\u043c\u0443\u043c \u043e \u0441\u043e\u0437\u0434\u0430\u043d\u0438\u0438 \u0440\u0435\u043f\u043b\u0438\u043a \u043c\u043e\u043d\u043e\u043b\u0438\u0442\u0430, \u0447\u0442\u043e\u0431\u044b \u0440\u0430\u0437\u0434\u0443\u0432\u0430\u043d\u0438\u044f \u043e\u0447\u0435\u0440\u0435\u0434\u0438 \u043e\u0436\u0438\u0434\u0430\u043d\u0438\u044f \u0434\u0430\u043d\u043d\u044b\u0445 \u0438\u0437 \u0431\u0430\u0437\u044b<\/p>\n<h2>\u0410\u0440\u0445\u0438\u0442\u0435\u043a\u0442\u0443\u0440\u0430 \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u044f<\/h2>\n<p>\u0414\u043b\u044f \u0431\u0430\u043b\u0430\u043d\u0441\u0438\u0440\u043e\u0432\u043a\u0438 \u043d\u0430\u0433\u0440\u0443\u0437\u043a\u0438 \u0441\u0430\u043c\u044b\u043c \u043e\u0447\u0435\u0432\u0438\u0434\u043d\u044b\u043c \u0438\u043d\u0441\u0442\u0440\u0443\u043c\u0435\u043d\u0442\u043e\u043c \u044f\u0432\u043b\u044f\u0435\u0442\u0441\u044f <a href=\"https:\/\/codeforgeek.com\/load-balancing-nodejs-servers-nginx\/\" rel=\"noopener noreferrer nofollow\">Nginx upstream<\/a> &#8212;  \u0438\u043d\u0441\u0442\u0440\u0443\u043c\u0435\u043d\u0442 \u043f\u043e\u043b\u0443\u0447\u0430\u0435\u0442 WebSocket \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u0435 \u043d\u043e\u0432\u043e\u0433\u043e \u043a\u043b\u0438\u0435\u043d\u0442\u0430 \u043d\u0430 \u043f\u043e\u0440\u0442 80 \u0438 \u0432 \u043f\u043e\u0440\u044f\u0434\u043a\u0435 \u043e\u0447\u0435\u0440\u0435\u0434\u0438 \u043f\u0440\u043e\u043a\u0441\u0438\u0440\u0443\u0435\u0442 \u0435\u0433\u043e \u043d\u0430 8081, 8082, &#8230;, 8085 \u0437\u0430\u0432\u0438\u0441\u0438\u043c\u043e \u043e\u0442 \u043a\u043e\u043b\u0438\u0447\u0435\u0441\u0442\u0432\u0430 \u0440\u0435\u043f\u043b\u0438\u043a. \u0415\u0441\u043b\u0438 \u043a\u043b\u0438\u0435\u043d\u0442 \u043d\u0435 \u043f\u0440\u043e\u044f\u0432\u043b\u044f\u0435\u0442 \u0430\u043a\u0442\u0438\u0432\u043d\u043e\u0441\u0442\u044c 15 \u043c\u0438\u043d\u0443\u0442, \u0441\u043e\u0435\u0434\u0438\u043d\u0435\u043d\u0438\u0435 \u043f\u0440\u0438\u0440\u044b\u0432\u0430\u0435\u0442\u0441\u044f, \u0435\u0441\u043b\u0438 \u0431\u0443\u0434\u0435\u0442 \u043d\u043e\u0432\u043e\u0435 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435, \u043e\u043d\u043e \u0441\u043e\u0437\u0434\u0430\u0441\u0442\u0441\u044f \u0437\u0430\u043d\u043e\u0433\u043e.<\/p>\n<figure class=\"full-width\">\n<div><figcaption><a href=\"https:\/\/nginx.org\/en\/docs\/http\/ngx_http_upstream_module.html\" rel=\"noopener noreferrer nofollow\">https:\/\/nginx.org\/en\/docs\/http\/ngx_http_upstream_module.html<\/a><\/figcaption><\/div>\n<\/figure>\n<p>\u0420\u0435\u043f\u043b\u0438\u043a\u0438 \u0441\u043e\u0445\u0440\u0430\u043d\u044f\u044e\u0442 \u0438\u0441\u0442\u043e\u0440\u0438\u044e \u043f\u0435\u0440\u0435\u043f\u0438\u0441\u043a\u0438 \u0432 Redis, \u0447\u0442\u043e \u043f\u043e\u0437\u0432\u043e\u043b\u044f\u0435\u0442 \u0432\u043e\u0441\u0441\u043e\u0437\u0434\u0430\u0442\u044c \u043a\u043e\u043d\u0442\u0435\u043a\u0441\u0442 \u043d\u0435 \u0441\u043c\u043e\u0442\u0440\u044f \u043d\u0430 \u0442\u043e, \u0447\u0442\u043e \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u044f \u043e\u0431\u0440\u0430\u0431\u0430\u0442\u044b\u0432\u0430\u0435\u0442 \u043d\u043e\u0432\u044b\u0439 \u043f\u0440\u043e\u0446\u0435\u0441\u0441. \u0421\u043e\u0437\u0434\u0430\u0432\u0430\u0442\u044c \u0440\u0435\u043f\u043b\u0438\u043a\u0438 \u0431\u0443\u0434\u0435\u0442 <a href=\"https:\/\/pm2.io\/\" rel=\"noopener noreferrer nofollow\">PM2 <\/a>&#8212; \u043d\u0430\u0438\u0431\u043e\u043b\u0435\u0435 \u043d\u0430\u0442\u0438\u0432\u043d\u044b\u0439 \u0441\u043f\u043e\u0441\u043e\u0431 \u0434\u043b\u044f \u043f\u0440\u0438\u043b\u043e\u0436\u0435\u043d\u0438\u0439 \u0441\u0442\u0435\u043a\u0430 NodeJS<\/p>\n<figure class=\"full-width\">\n<div><figcaption><a href=\"https:\/\/pm2.io\/docs\/plus\/overview\/\" rel=\"noopener noreferrer nofollow\">https:\/\/pm2.io\/docs\/plus\/overview\/<\/a><\/figcaption><\/div>\n<\/figure>\n<p>\u0422\u0430\u043a \u0436\u0435, \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044f PM2, \u0437\u0430 40$ \u0432 \u043c\u0435\u0441\u044f\u0446 \u043c\u043e\u0436\u043d\u043e \u043a\u0443\u043f\u0438\u0442\u044c \u0433\u043e\u0442\u043e\u0432\u043e\u0435 \u0443\u0432\u0435\u0434\u043e\u043c\u043b\u0435\u043d\u0438\u0435 \u043e \u0438\u043d\u0446\u0438\u0434\u0435\u043d\u0442\u0430\u0445 \u043f\u043e Slack \/ Email, \u043c\u043e\u043d\u0438\u0442\u043e\u0440\u0438\u043d\u0433 \u0441\u0435\u0440\u0432\u0435\u0440\u0430 \u043f\u043e \u0441\u0442\u0430\u043d\u0434\u0430\u0440\u0442\u043d\u044b\u043c \u043c\u0435\u0442\u0440\u0438\u043a\u0430\u043c \u0442\u0430\u043a\u0438\u043c \u043a\u0430\u043a CPUs: cores, hardware threads, virtual threads, Memory: capacity, Network interfaces, Storage devices: I\/O, capacity, \u0432\u0440\u0435\u043c\u044f \u043e\u0442\u043a\u043b\u0438\u043a\u0430 \u0438 \u0442\u0434. \u042d\u0442\u043e \u044d\u043a\u043e\u043d\u043e\u043c\u0438\u0442 \u0434\u0435\u043d\u044c\u0433\u0438 \u043d\u0430 Dev Ops \u0434\u043e \u0441\u0430\u043c\u043e\u043e\u043a\u0443\u043f\u0430\u0435\u043c\u043e\u0441\u0442\u0438 \u043f\u0440\u043e\u0435\u043a\u0442\u0430.<\/p>\n<h2>\u0424\u0430\u0439\u043b\u044b \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u0438<\/h2>\n<p>\u0427\u0442\u043e\u0431\u044b \u043d\u0435 \u0441\u043f\u0435\u0446\u0438\u0430\u043b\u0438\u0437\u0438\u0440\u043e\u0432\u0430\u0442\u044c Linux \u043d\u0430 \u043c\u0430\u0448\u0438\u043d\u0435 \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a\u0430 \u043f\u043e\u0434 \u043e\u0434\u0438\u043d \u043f\u0440\u043e\u0435\u043a\u0442, \u043e\u0431\u0435\u0440\u043d\u0435\u043c Nginx \u0432 Docker. \u0414\u043b\u044f \u044d\u0442\u043e\u0433\u043e, \u043d\u0430\u043f\u0438\u0448\u0435\u043c <code>docker-compose.yaml<\/code><\/p>\n<pre><code class=\"yaml\">version: '3.8'  services:   nginx:     image: nginx:1.27.4     ports:       - \"80:80\"     extra_hosts:       - \"host.docker.internal:host-gateway\"     volumes:       - .\/config\/nginx.conf:\/etc\/nginx\/nginx.conf:ro       - .\/logs\/nginx:\/var\/log\/nginx <\/code><\/pre>\n<p>\u0418 \u0441\u043e\u0437\u0434\u0430\u0434\u0438\u043c \u0441\u043e\u043f\u0443\u0442\u0441\u0442\u0432\u0443\u044e\u0449\u0438\u0439 <code>.\/config\/nginx.config<\/code> \u0441 \u043f\u0435\u0440\u0435\u0447\u0438\u0441\u043b\u0435\u043d\u0438\u0435\u043c \u0440\u0435\u043f\u043b\u0438\u043a<\/p>\n<pre><code class=\"python\">user nginx; worker_processes auto;  error_log \/var\/log\/nginx\/error.log warn; pid \/var\/run\/nginx.pid;  events {     worker_connections 1024;     use epoll;     multi_accept on; }  http {     include \/etc\/nginx\/mime.types;     default_type application\/octet-stream;      log_format main '$remote_addr - $remote_user [$time_local] \"$request\" '                     '$status $body_bytes_sent \"$http_referer\" '                     '\"$http_user_agent\" \"$http_x_forwarded_for\"';      access_log \/var\/log\/nginx\/access.log main;      upstream local_websocket_servers {         server host.docker.internal:8081;  # Using host.docker.internal from hosts shared from host machine         server host.docker.internal:8082;         server host.docker.internal:8083;         server host.docker.internal:8084;         server host.docker.internal:8085;         least_conn;     }      map $http_upgrade $connection_upgrade {         default upgrade;         ''      close;     }      server {         listen 80;         server_name localhost;          location \/ {             proxy_pass http:\/\/local_websocket_servers$is_args$args;                          # WebSocket-specific headers             proxy_http_version 1.1;             proxy_set_header Upgrade $http_upgrade;             proxy_set_header Connection $connection_upgrade;                          # Preserve original headers and connection details             proxy_set_header Host $host;             proxy_set_header X-Real-IP $remote_addr;             proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;             proxy_set_header X-Forwarded-Proto $scheme;              # Close upstream if client disconnects             proxy_ignore_client_abort on;                          # Long-lived connection settings             proxy_read_timeout 86400s;             proxy_send_timeout 86400s;                          # Buffer and performance settings             proxy_buffer_size 128k;             proxy_buffers 4 256k;             proxy_busy_buffers_size 256k;         }     } } <\/code><\/pre>\n<p>\u0414\u043b\u044f \u0437\u0430\u043f\u0443\u0441\u043a\u0430 \u0440\u0435\u043f\u043b\u0438\u043a \u0447\u0435\u0440\u0435\u0437 pm2 \u0441\u043e\u0437\u0434\u0430\u0434\u0438\u043c <code>pm2.config.cjs<\/code>. <a href=\"https:\/\/github.com\/tripolskypetr\/agent-swarm-kit\/blob\/master\/demo\/nginx-balancer-chat\/package.json\" rel=\"noopener noreferrer nofollow\">\u041f\u043e \u0441\u0441\u044b\u043b\u043a\u0435 \u043b\u0435\u0436\u0438\u0442 package.json<\/a> \u0441 \u0441\u043a\u0440\u0438\u043f\u0442\u0430\u043c\u0438 \u0434\u043b\u044f \u0437\u0430\u043f\u0443\u0441\u043a\u0430 \u043f\u0440\u043e\u0435\u043a\u0442\u0430 \u0447\u0435\u0440\u0435\u0437 <code>npm start<\/code><\/p>\n<pre><code class=\"javascript\">const path = require(\"path\"); const os = require(\"os\"); const dotenv = require('dotenv');  const readConfig = () =&gt; dotenv.parse(\".\/.env\");  const getPath = (unixPath) =&gt; {   return path.resolve(unixPath.replace('~', os.homedir())); };  const createBun = (index) =&gt; ({   name: `bun-ws-${index}`,   script: \".\/src\/server.ts\",   interpreter: getPath(\"~\/.bun\/bin\/bun\"),   args: [\"--server\", `--port=808${index}`],   out_file: `.\/logs\/pm2\/bun-ws-${index}-out.log`,   error_file: `.\/logs\/pm2\/bun-ws-${index}-error.log`,   log_date_format: \"YYYY-MM-DD HH:mm:ss\",   merge_logs: true,   env: readConfig(), });  module.exports = {   apps: [     \/*     {       name: \"bun-ws-1\",       script: \".\/src\/server.ts\",       interpreter: getPath(\"~\/.bun\/bin\/bun\"),       args: [\"--server\", \"--port=8081\"],       out_file: \".\/logs\/pm2\/bun-ws-1-out.log\",       error_file: \".\/logs\/pm2\/bun-ws-1-error.log\",       log_date_format: \"YYYY-MM-DD HH:mm:ss\",       merge_logs: true,     },     *\/     createBun(1),     createBun(2),     createBun(3),     createBun(4),     createBun(5),   ] } <\/code><\/pre>\n<p>\u041a\u0430\u043a \u0432\u0438\u0434\u043d\u043e, \u043d\u043e\u043c\u0435\u0440 \u043f\u043e\u0440\u0442\u0430 \u0434\u043b\u044f \u043e\u0440\u043a\u0435\u0441\u0442\u0440\u0430\u0446\u0438\u0438 \u043c\u044b \u043f\u0435\u0440\u0435\u0434\u0430\u0435\u043c \u0447\u0435\u0440\u0435\u0437 \u0430\u0440\u0433\u0443\u043c\u0435\u043d\u0442\u044b \u043a\u043e\u043c\u0430\u043d\u0434\u043d\u043e\u0439 \u0441\u0442\u0440\u043e\u043a\u0438. \u041d\u0430\u0438\u0431\u043e\u043b\u0435\u0435 \u0443\u0434\u043e\u0431\u043d\u043e, \u0442\u0430\u043a \u043a\u0430\u043a \u0444\u0430\u0439\u043b <code>.env<\/code> \u043e\u0441\u0442\u0430\u0435\u0442\u0441\u044f \u0441\u0442\u0430\u0442\u0438\u0447\u043d\u044b\u043c \u0431\u0435\u0437 \u0438\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u044f \u0432 \u0440\u0430\u043d\u0442\u0430\u0439\u043c\u0435. \u0414\u043b\u044f \u0437\u0430\u043f\u0443\u0441\u043a\u0430 \u043f\u0440\u043e\u0435\u043a\u0442\u0430 \u043c\u044b \u0438\u0441\u043f\u043e\u043b\u044c\u0443\u0435\u043c <a href=\"https:\/\/bun.sh\/\" rel=\"noopener noreferrer nofollow\">Bun <\/a>&#8212; \u0443\u0441\u043a\u043e\u0440\u0435\u043d\u043d\u044b\u0439 \u0430\u043d\u0430\u043b\u043e\u0433 NodeJS \u043f\u043e \u0441\u043a\u043e\u0440\u043e\u0441\u0442\u0438 \u0441\u043e\u043f\u043e\u0441\u0442\u0430\u0432\u0438\u043c\u044b\u0439 \u0441 Golang<\/p>\n<h3>\u0420\u043e\u0439 \u0430\u0433\u0435\u043d\u0442\u043e\u0432<\/h3>\n<p>\u0410\u0433\u0435\u043d\u0442 &#8212; \u0430\u043d\u0430\u043b\u043e\u0433 \u0441\u0446\u0435\u043d\u044b \u0432 telegraf, \u043c\u043e\u0434\u0435\u043b\u044c LLM, \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u044f\u0449\u0430\u044f \u0438\u0437\u043e\u043b\u0438\u0440\u043e\u0432\u0430\u043d\u043d\u044b\u0439 system prompt. \u0422\u0435\u043a\u0443\u0449\u0438\u0439 \u0430\u0433\u0435\u043d\u0442 \u0432 Swarm \u043c\u043e\u0436\u0435\u0442 \u0431\u044b\u0442\u044c \u0438\u0437\u043c\u0435\u043d\u0435\u043d \u0447\u0435\u0440\u0435\u0437 \u0432\u044b\u0437\u043e\u0432 \u0444\u0443\u043d\u043a\u0446\u0438\u0438 <code>changeToAgent <\/code>&#8212; \u0430\u043d\u0430\u043b\u043e\u0433 \u043d\u0430\u0432\u0438\u0433\u0430\u0446\u0438\u0438 \u043f\u043e \u0441\u0446\u0435\u043d\u0430\u043c \u0432 \u0442\u0435\u043b\u0435\u0433\u0440\u0430\u043c \u0431\u043e\u0442\u0435 \u043f\u043e\u0441\u043b\u0435 \u043a\u043b\u0438\u043a\u0430 \u043f\u043e \u043a\u043d\u043e\u043f\u043a\u0435<\/p>\n<pre><code class=\"typescript\">import { Adapter, addAgent, addCompletion, addSwarm } from \"agent-swarm-kit\"; import { OpenAI } from \"openai\";  export const OPENAI_COMPLETION = addCompletion({   completionName: \"openai_completion\",   getCompletion: Adapter.fromOpenAI(new OpenAI({ apiKey: process.env.OPENAI_API_KEY })) });  export const TEST_AGENT = addAgent({   docDescription: \"This agent operates within the nginx-balancer-chat project as a test agent, utilizing the OpenaiCompletion to inform users about the actual server port of one of 5 chat instances running on different ports and upstreamed by Nginx to port 80, extracting the port details from the chat history\u2019s system message.\",   agentName: \"test_agent\",   completion: OPENAI_COMPLETION,   prompt: `You are a test agent for Nginx Upstream. Tell user the server port from the chat history (system message)`,   dependsOn: [], });  export const TEST_SWARM = addSwarm({   docDescription: \"This swarm serves as the core structure for the nginx-balancer-chat project, managing a single TestAgent as both the sole member and default agent to handle user interactions, leveraging the CohereCompletion to report the specific port of one of 5 upstreamed chat instances balanced by Nginx to port 80.\",   swarmName: \"test_swarm\",   agentList: [TEST_AGENT],   defaultAgent: TEST_AGENT, }); <\/code><\/pre>\n<p>\u041a\u043e\u0434 \u044d\u0442\u043e\u0433\u043e \u043f\u0440\u0438\u043c\u0435\u0440\u0430 \u0437\u0430\u043f\u0440\u043e\u0433\u0440\u0430\u043c\u043c\u0438\u0440\u043e\u0432\u0430\u043d \u0442\u0430\u043a, \u0447\u0442\u043e\u0431\u044b LLM \u043c\u043e\u0434\u0435\u043b\u044c \u043d\u0430\u0437\u0432\u0430\u043b\u0430, \u0441 \u043a\u0430\u043a\u043e\u0433\u043e \u043f\u043e\u0440\u0442\u0430 \u0431\u044b\u043b \u043f\u0440\u043e\u043a\u0441\u0438\u0440\u043e\u0432\u0430\u043d \u0437\u0430\u043f\u0440\u043e\u0441 WebSocket. \u0412\u043c\u0435\u0441\u0442\u043e <code>OPENAI_COMPLETION <\/code>\u0434\u043b\u044f \u043a\u0430\u0436\u0434\u043e\u0433\u043e \u0430\u0433\u0435\u043d\u0442\u0430 \u043f\u043e \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u0438 \u043c\u043e\u0436\u043d\u043e \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c <code>LMStudio<\/code>, <code>Ollama<\/code>, <code>Cohere<\/code>, \u0431\u043e\u043b\u0435\u0435 \u043f\u043e\u0434\u0440\u043e\u0431\u043d\u0435\u0435 \u0432 \u0440\u0435\u043f\u043e<\/p>\n<pre><code class=\"typescript\">import { Chat, getAgentName, Schema, History } from \"agent-swarm-kit\"; import type { ServerWebSocket } from \"bun\"; import { parseArgs } from \"util\"; import { TEST_SWARM } from \".\/lib\/swarm\";  declare function parseInt(value: unknown): number;  type WebSocketData = {   clientId: string; };  const { values } = parseArgs({   args: process.argv,   options: {     server: {       type: \"boolean\",     },     port: {       type: \"string\",     },   },   strict: true,   allowPositionals: true, });   History.useHistoryCallbacks({   getSystemPrompt: () =&gt; [     `The server port is ${SERVER_PORT}. Tell him that port ASAP`   ] });  const SERVER_PORT = parseInt(values.port);  if (isNaN(SERVER_PORT)) {   throw new Error(`Server port is not a number: ${values.port}`); }  if (values.server) {   Bun.serve({     fetch(req, server) {       const clientId = new URL(req.url).searchParams.get(\"clientId\")!;       if (!clientId) {         return new Response(\"Invalid clientId\", { status: 500 });       }       console.log(`Connected clientId=${clientId} port=${SERVER_PORT}`);       server.upgrade&lt;WebSocketData&gt;(req, {         data: {           clientId,         },       });     },     websocket: {       async open(ws: ServerWebSocket&lt;WebSocketData&gt;) {         await Chat.beginChat(ws.data.clientId, TEST_SWARM);         await Schema.writeSessionMemory(ws.data.clientId, { port: SERVER_PORT });       },       async message(ws: ServerWebSocket&lt;WebSocketData&gt;, message: string) {         const answer = await Chat.sendMessage(ws.data.clientId, message, TEST_SWARM);         ws.send(           JSON.stringify({             data: answer,             agentName: await getAgentName(ws.data.clientId),           })         );       },       async close(ws: ServerWebSocket&lt;WebSocketData&gt;) {         console.log(`Disconnected clientId=${ws.data.clientId} port=${SERVER_PORT}`);         await Chat.dispose(ws.data.clientId, TEST_SWARM);       },     },     port: SERVER_PORT,   }); }  console.log(`Server listening http:\/\/localhost:${SERVER_PORT}`) <\/code><\/pre>\n<p>\u0415\u0441\u043b\u0438 \u0438\u043d\u0442\u0435\u0440\u0435\u0441\u0443\u0435\u0442 \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u043a\u0430 \u0447\u0430\u0442\u043e\u0432 \u0441 \u0431\u043e\u043b\u0435\u0435 \u0447\u0435\u043c \u043e\u0434\u043d\u0438\u043c \u0430\u0433\u0435\u043d\u0442\u043e\u043c, <a href=\"https:\/\/agent-swarm.github.io\/documents\/docs_getting-started.html\" rel=\"noopener noreferrer nofollow\">\u043f\u043e\u0441\u043c\u043e\u0442\u0440\u0438\u0442\u0435 \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430\u0446\u0438\u044f<\/a><\/p>\n<h2>\u0421\u043f\u0430\u0441\u0438\u0431\u043e \u0437\u0430 \u0432\u043d\u0438\u043c\u0430\u043d\u0438\u0435!<\/h2>\n<\/p>\n<\/div>\n<\/div>\n<\/div>\n<p><!----><!----><\/div>\n<p><!----><!----><br \/> \u0441\u0441\u044b\u043b\u043a\u0430 \u043d\u0430 \u043e\u0440\u0438\u0433\u0438\u043d\u0430\u043b \u0441\u0442\u0430\u0442\u044c\u0438 <a href=\"https:\/\/habr.com\/ru\/articles\/896222\/\"> https:\/\/habr.com\/ru\/articles\/896222\/<\/a><br \/><\/br><\/br><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[],"tags":[],"class_list":["post-454346","post","type-post","status-publish","format-standard","hentry"],"_links":{"self":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/454346","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=454346"}],"version-history":[{"count":0,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/454346\/revisions"}],"wp:attachment":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=454346"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=454346"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=454346"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}