From baff18aa9781f660a45cd0b78a837b47c3f9b94d Mon Sep 17 00:00:00 2001 From: Xi Bai Date: Tue, 7 Jul 2026 17:09:13 +0100 Subject: [PATCH] fix: resolve path function conflicts and fix proxy health check --- app/api/api.py | 6 +++--- app/api/routers/invocation.py | 12 ++++++------ app/api/static/images/favicon.ico | Bin 4286 -> 0 bytes app/api/static/images/favicon.png | Bin 0 -> 2016 bytes app/domain.py | 1 + app/mcp/server.py | 2 +- docker-compose-proxy.yml | 2 +- tests/integration/features/serving.feature | 8 ++++++++ tests/integration/steps/test_steps.py | 12 ++++++++++++ 9 files changed, 32 insertions(+), 11 deletions(-) delete mode 100644 app/api/static/images/favicon.ico create mode 100644 app/api/static/images/favicon.png diff --git a/app/api/api.py b/app/api/api.py index 44384150..2ef30ca5 100644 --- a/app/api/api.py +++ b/app/api/api.py @@ -273,7 +273,7 @@ def _get_app( add_exception_handlers(app) instrumentator = Instrumentator( - excluded_handlers=["/docs", "/redoc", "/metrics", "/openapi.json", "/favicon.ico", "none"] + excluded_handlers=["/docs", "/redoc", "/metrics", "/openapi.json", "/favicon.png", "none"] ).instrument(app) if msd_overwritten is not None: @@ -309,7 +309,7 @@ async def swagger_doc(req: Request) -> HTMLResponse: title="CogStack ModelServe", oauth2_redirect_url=oauth2_redirect_url, init_oauth=app.swagger_ui_init_oauth, - swagger_favicon_url="/static/images/favicon.ico", + swagger_favicon_url="/static/images/favicon.png", swagger_ui_parameters=app.swagger_ui_parameters, ) @@ -320,7 +320,7 @@ async def redoc_doc(req: Request) -> HTMLResponse: return get_redoc_html( openapi_url=openapi_url, title="CogStack ModelServe", - redoc_favicon_url="/static/images/favicon.ico", + redoc_favicon_url="/static/images/favicon.png", ) @app.get("/", include_in_schema=False) diff --git a/app/api/routers/invocation.py b/app/api/routers/invocation.py index 3c6f5c12..c1d8e3c1 100644 --- a/app/api/routers/invocation.py +++ b/app/api/routers/invocation.py @@ -45,7 +45,7 @@ PATH_PROCESS_BULK_FILE = "/process_bulk_file" PATH_REDACT = "/redact" PATH_REDACT_WITH_ENCRYPTION = "/redact_with_encryption" -PATH_OPENAI_EMBEDDINGS = "/v1/embeddings" +PATH_EMBEDDINGS = "/embeddings" router = APIRouter() config = get_settings() @@ -359,13 +359,13 @@ def get_redacted_text_with_encryption( @router.post( - PATH_OPENAI_EMBEDDINGS, - tags=[Tags.OpenAICompatible.name], + PATH_EMBEDDINGS, + tags=[Tags.Embeddings.name], response_model=None, dependencies=[Depends(cms_globals.props.current_active_user)], - description="Create embeddings based on text(s), similar to OpenAI's /v1/embeddings endpoint", + description="Create embeddings based on text(s)", ) -def embed_texts( +def get_text_embeddings( request: Request, request_data: Annotated[OpenAIEmbeddingsRequest, Body( description="Text(s) to be embedded", media_type="application/json" @@ -374,7 +374,7 @@ def embed_texts( model_service: AbstractModelService = Depends(cms_globals.model_service_dep) ) -> JSONResponse: """ - Embeds text or a list of texts, mimicking OpenAI's /v1/embeddings endpoint. + Embeds text or a list of texts. Args: request (Request): The request object. diff --git a/app/api/static/images/favicon.ico b/app/api/static/images/favicon.ico deleted file mode 100644 index 0a2a28a213567f70242d7f2a3f638d2563b8d757..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4286 zcmbVQ2~btn8NS3?(`9OBI$Z==qnh zeg6R^$qD~FJS6c`%KD)s{X~)^9|WeP`3Ny@|KQove0qtbjd|+CU#2LovlEr5@B_-b zfpJPAA}=6TDfihU*G%6n*I--`6sP0|#L5Tp&gO^&WpPvr^LR5?{r1&|^fZw6ci%2j zpIex&{463_c_m=Koaz%RU-I5P&}-Z!n+-c<8$ur^Y{s3k#e0|B2X5`)lLkJ&U6{&T zHx+2leG`!nsx+rX+3csGsmi;)338)Xyvz)7z}k!0gV;Tw8+IXp@3l+T!}cpW*4Mpv zkIP5Ar~2-Z+h)cqe~CgKV+vHKv3UHtfK7Qpys~L+ z7JFWB*j#UN3O~+PdZj5{(+(@#GZok=h)-nhg;SD_xf)+~gxT*$^FmE}z+wG6r?lr5pJ7vdvy{4N zX$qhADdGs?5b(icLy5+3_3NYo>%PM2=v505O;F+@6E_+cutd z!NTu4zVpSGoc;{%LMx*=KoggLk*s)!=CZY(nTj$k1Gwo32M>RY%kEgI3%EO5%+!op z>zwEwumq2{i!~mAT==0rZ|1A~5O6#Dbh6={_6IilPqm5PdaTohE*s5)PDkTI4;r-l z&Ww}F#~#^=?vVxTqX-9&K-@QBo$FX*K5`$G!}vcMOtdPO@#&~bP>RCWoK*R1xSxr8 z?|7LnMlSwdVd5*&nf^k8tT*1Z(Pz*IddFAr1s`ZOBPgHcdgLhF;}{|vkvYi204^V0 zFq4Yh=iIQ8$j6X2W};5n*Ddxs3_yEDH?0(TNE!4AH0rZY){|SzT(xjYZ8dZBf-&(? zgX$TlS-Ja3;GRGnM+hF+yWp|rtjhbbe&szI1qfaaZM;>can;JH_;V}ebXe{B*jjBO zyI0^>U(_V2)l6y7r}E&fHU%IGbus zc5N7t1ApY@pUs1L*;uOamr?sZ=<)Cs3x%XAJOp(Kfrle7ju7KSg`OuW~ze@Zx0N5;*GQL zr31ekI&H;wTJd~1puJW^?HwEayT-(Wz++?&_1`O(dzZ40rWP{pdKw(S<9CfF>POu= z6`dM;Y_uJiM&x9>Lkk`7R`Wxw37nS;RUVb6@^a{`8Q8;tXTmS`0RBzvLBX0^&@<7$ zhI@HHoSf!gu5NHCW@eWnMy`d7_I_p|89Cno9?%9i<|*78wJEu0rMuYU@)DH?A{T-1 zgcoXIL`{S?0*^8t1n&gFzk$$aAbMF4G#QLKtOdtM=$FT@SxERq^ibno*%FqhydPep z&T>1)x|~ZGITth9d1j1nWD^J*{n@ARIp;_vy7&c zG76|r>9FWK=x=77r~x(bM_%ThQ>hrae296$Lj%Y4)f!)keDuL1;=UJpX+RyqvKjY5 z9tzzY~O=SA$%u*xe}=z%N5Mc&uwR(E#?E^M#d+sEu8t@JS5j#eR?6 zw9r1(LX7_foeuR4FX$l=^$_##*TK8c!qAx_&R6eb`Tm>?C1U6dUU5O2P1+tlJ6QE^pGv z2f10@sL_lvmEczj$2I1HiQYrZ12+$NBmlkRl?si|!gUd9aSi9uWt<wZR|Ilzx;G0{1AUi9liyc-Q|w&CpgFZx9|JmNUpbiYg`W5sFpdryGlCjWs=nsr_c zbgO0^A_p$m|CSqOd;Nt5gx*@e)aefNuR+r{z-uvd8Cj{)MeI{N@5Zwx>{s;c0dN&Q ziG$~j;#|R*V$4!HXXmonN$pSWeTd7t%i09rI`$T@nc!fkWwi0CiDGYCXmf{|g6mb9 zj~Zll!Oy^Z9esM)dCi`ev*;I#YcvXmhc`i=`{0p3A}0X_jEnOXXNneZl6~-Mnf9$A zys@r}TD8f>T9)cu#dNzCzyo6%8VhY~Xg3i9ehD;qq8sNQ#vfiYlQZfev>|-rhFqhYME@G&>FiuHQcu=vOZ)nfTADK-)EJUw`t!GX;9}PA6wvq48M9c6I$Gg zxdkt^X+OyBwtejyqaWh0bAl_}J0)pSr6f(*CjPtN89w5BA}XuTWOm_R5^jDe=_|aT8rh) diff --git a/app/api/static/images/favicon.png b/app/api/static/images/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..40901132d6541c1a8b952847c799f725232300ac GIT binary patch literal 2016 zcmV<62Os!}P)irsb9NcpY5cFpLADx90Xdp1(ckck48t!$esR4Uk`2c!F4DG^;*Z%7T!;4&GOn+(TZNb&T%wE;>mZvUk5 z{Y5i}(9nMOkkt~v<_%NG^=2LQh5_&>;!V-xs|;sqlpcm3D8 zuJ$H2{&obB(pbOcq&|1%3JjkeVL@i+6ilBvgvL!Bge8+V>$-{tyn7^<*5wrH?`s<2 zCPc(Vtky*slHcI10|3rH{1<0J4~vg?kOj~VnA8wH8?%-d=?Kki2@)En)__pBe(452 zSh8PlUb+u%0#Z!^F~A7nAQ}z^lW?C&*s?M+@42OQUJz4GZxSj!KHfnVtSCIAxBY%3 zr2=tSc(i{ezT7{X_8yr>a~6yM*$Ny{;LY_;q6?+fXsT%pZp`AvWO$YnrU3wvP<`6t zd%!Qy(By+?wd0tP=Xr)avv%m84tYn9p4gulJb|P`ss7?^|~i5E5sL%dxU_A+kB^Dm;O^9&G`WdHw&XXigdRT9Y6Myfp}!X0Q%y0+jZi9 zF2X-4F^#$$2bLv*Kn%y-RxmlCrO6llS7>m=C7bB!VqC8LHh3r$JV_`x3Dp`4zQf;K z#*2^lK*{?rQggF~SLW>0N+`Vh=fis2u9?)_{)th;s7ufOezFK z`wTD)!l!e#>4f-)4L!@5t(s!Nr!9fpRR?vd)L89#hK48MMFmT5~uYQ@>Fn^b>FRzcdH}Yiw*&;HR5Puj9dx&iLT`~$vpN$>6{;hL<`51+D z+Aq+0y%qiz8(q4j!el_lE~&6?_@VqZv=tQ7V9=I;6AO!)@!)y)f ze*rxB;N9q85(3T9V)N;`W7=CZhw7`Fpe%vw7q16T7+QSt+b>Y_rAA%4{%|7oVhvb6 zrNXN@le&@#)j~q0Cwk${#baoG&US6xuwaBsEdve~oz= zP-A1Uz=XzMR+s2apS((1X&l=5Eo#o!={Gh`qqL50EPV1E&Ea6`l5zCJe0IDuR_TQjU8(x3MP$WR>9;6kCoBzkn$eTtdO| z*^vbDVc9}j^yFGiO2PB@S83m>BHehQ4sK#d;S6ahJc9n1q|?F~CQYyM9%*wSxmmfl zIphBE*<{Nfhc6{Y=u*P`0?ZOHqZbSG>X*p|KHZyj?%Ki0o;Z+|yjS8F+JP%XFRpdm z|0dy@L74DTmG|p*?}QYP`H3tbI~bBg1b?*n3*CIRS^sF@y|iJ*ChgcVS;-9pu9Pth z{Y91cWU@P9+`FLfL#sa~OH7Lm0hcP#m2u;su;9EAH*cE^`J7?ZYt>$vZ1V^RBVMrO ze6rPWvQ-w@5)`~%w>eonvRSki#Lx^t-t3xNKTO&@%?5UT^AWN{iQ$)>U0TOgwnTA- zP9GX;7vX@(kng$T{ee%jP Starlette: oauth_manager=oauth_manager, public_paths=[ "/authorize", - "/favicon.ico", + "/favicon.png", "/oauth/", "/.well-known", "/.well-known/", diff --git a/docker-compose-proxy.yml b/docker-compose-proxy.yml index 7ee7d53c..b1f1efb3 100644 --- a/docker-compose-proxy.yml +++ b/docker-compose-proxy.yml @@ -33,7 +33,7 @@ services: - 28202:28202 # grafana - 28203:28203 # graylog healthcheck: - test: ["CMD", "curl", "-f", "http://localhost/health"] + test: ["CMD", "curl", "-f", "-k", "https://localhost/health"] interval: 60s timeout: 20s retries: 3 diff --git a/tests/integration/features/serving.feature b/tests/integration/features/serving.feature index 4b270fd6..a2bd48a8 100644 --- a/tests/integration/features/serving.feature +++ b/tests/integration/features/serving.feature @@ -93,6 +93,14 @@ Feature: | /redact_with_encryption | {"text": "Spinal stenosis", "public_key_pem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3ITkTP8Tm/5FygcwY2EQ7LgVsuCF0OH7psUqvlXnOPNCfX86CobHBiSFjG9o5ZeajPtTXaf1thUodgpJZVZSqpVTXwGKo8r0COMO87IcwYigkZZgG/WmZgoZART+AA0+JvjFGxflJAxSv7puGlf82E+u5Wz2psLBSDO5qrnmaDZTvPh5eX84cocahVVI7X09/kI+sZiKauM69yoy1bdx16YIIeNm0M9qqS3tTrjouQiJfZ8jUKSZ44Na/81LMVw5O46+5GvwD+OsR43kQ0TexMwgtHxQQsiXLWHCDNy2ZzkzukDYRwA3V2lwVjtQN0WjxHg24BTBDBM+v7iQ7cbweQIDAQAB\n-----END PUBLIC KEY-----"} | application/json | Then the response should contain encrypted labels + @embeddings + Scenario: Generate embeddings for a single text + Given CMS app is up and running + When I send a POST request with the following content + | endpoint | data | content_type | + | /embeddings | {"input": "Spinal stenosis", "model": "cms-model"} | application/json | + Then the response should contain embeddings + @preview Scenario: Extract and preview entities Given CMS app is up and running diff --git a/tests/integration/steps/test_steps.py b/tests/integration/steps/test_steps.py index fe41011b..144c1f0c 100644 --- a/tests/integration/steps/test_steps.py +++ b/tests/integration/steps/test_steps.py @@ -284,3 +284,15 @@ def check_response_annotation_stats(context): assert len(response_lines) > 1 assert "concept,anno_count,anno_unique_counts,anno_ignorance_counts" == response_lines[0] context["response"].close() + +@then("the response should contain embeddings") +def check_response_embeddings(context): + assert context["response"].status_code == 200 + assert context["response"].headers["Content-Type"] == "application/json" + response_json = context["response"].json() + assert "data" in response_json + assert isinstance(response_json["data"], list) + assert len(response_json["data"]) > 0 + assert "embedding" in response_json["data"][0] + assert isinstance(response_json["data"][0]["embedding"], list) + context["response"].close()