From d0275ff3b91d87d05a72c98001a49222e3cba348 Mon Sep 17 00:00:00 2001 From: Dimitri Kennedy Date: Mon, 25 Mar 2024 19:52:23 -0400 Subject: [PATCH] adding basic support for. anthropic (#144) --- .changeset/itchy-trains-relax.md | 5 + .github/workflows/test-pr.yml | 1 + .github/workflows/test.yml | 1 + .vscode/launch.json | 36 +++++ bun.lockb | Bin 274805 -> 286599 bytes package.json | 17 +-- src/constants/providers.ts | 15 +- src/dsl/validator.ts | 10 +- src/index.ts | 4 +- src/instructor.ts | 60 +++++--- src/types/index.ts | 65 +++++++-- tests/anthropic.test.ts | 236 +++++++++++++++++++++++++++++++ tests/inference.test.ts | 4 +- tests/mode.test.ts | 1 + 14 files changed, 404 insertions(+), 51 deletions(-) create mode 100644 .changeset/itchy-trains-relax.md create mode 100644 .vscode/launch.json create mode 100644 tests/anthropic.test.ts diff --git a/.changeset/itchy-trains-relax.md b/.changeset/itchy-trains-relax.md new file mode 100644 index 00000000..45383351 --- /dev/null +++ b/.changeset/itchy-trains-relax.md @@ -0,0 +1,5 @@ +--- +"@instructor-ai/instructor": major +--- + +updating all types to better support non openai clients - this changes some of the previously exported types and adds a few new ones diff --git a/.github/workflows/test-pr.yml b/.github/workflows/test-pr.yml index e3da2477..c67ed6b8 100644 --- a/.github/workflows/test-pr.yml +++ b/.github/workflows/test-pr.yml @@ -21,6 +21,7 @@ jobs: OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} ANYSCALE_API_KEY: ${{ secrets.ANYSCALE_API_KEY }} TOGETHER_API_KEY: ${{ secrets.TOGETHER_API_KEY }} + ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} steps: - uses: actions/checkout@v3 diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 7f22cf72..78beae35 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -12,6 +12,7 @@ jobs: env: OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }} ANYSCALE_API_KEY: ${{ secrets.ANYSCALE_API_KEY }} + ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }} TOGETHER_API_KEY: ${{ secrets.TOGETHER_API_KEY }} steps: diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 00000000..bb4e87ef --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,36 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "bun", + "internalConsoleOptions": "neverOpen", + "request": "launch", + "name": "Debug File", + "program": "${file}", + "cwd": "${workspaceFolder}", + "stopOnEntry": false, + "watchMode": false + }, + { + "type": "bun", + "internalConsoleOptions": "neverOpen", + "request": "launch", + "name": "Run File", + "program": "${file}", + "cwd": "${workspaceFolder}", + "noDebug": true, + "watchMode": false + }, + { + "type": "bun", + "internalConsoleOptions": "neverOpen", + "request": "attach", + "name": "Attach Bun", + "url": "ws://localhost:6499/", + "stopOnEntry": false + } + ] +} \ No newline at end of file diff --git a/bun.lockb b/bun.lockb index db6e4e78693e9ed87ac6bc8fbcf75ecaffeb1447..0d596f21c97b163dd943f3a7b946df65d764a5b4 100755 GIT binary patch delta 61503 zcmeFacU%<7yYD|UGD4%QS-}-^Rty*hL5u-!HML#ex*85(|H={;XtsJ8j5k*H0DJ&e%B1?9TgPCMr5^B@L)%xOG*d82qJ_ zN)T2$1fqwxVX{u6{px6i{Qgr~lH0)|-Jo^^mWG+IJ~rMXRchGOoJ;a(btU^@NKKVST%=?!sd2vFn3m*W}+o4T$f2C5QN z4W$BGBA)auY;w@C0#7d-{fB;x`6pE56*Br+kBXIs$GB9KLXPLlw>6bKYFqO9zlTyW zVV;qGHi14dRxqf28YorEwz5iP1+4`A6}lKoTmwbT;##8(f0;tEd$CG}a#gt3@X(+j z8kJL3RVr(w-U>xj+PWkpkw1=JcEO1c#~zdNmF-29p2a(b$KIlq15=1;htVT zn6w@1ah?FBLB0W{_z6fyUFjcTqs$k%K2H}UPco_GV%dO~l@Fz|>onwDJp{REYIysH zE7@Lyleb9Jg1q@e`*_iWzR`&1PnB<*R5F}{MP7~y-3O&yTcFg!RZtoLpNNrB{z2ZV zD$V#Zk)YJ#w@vw4zYC@1b5_{DLn(fP;EM#0gJKHB1#x?1Tt5V8#GOP$V<=^`nEur6`v>rV}VuC#*z5Kj9 zy=dH2UrX?G|35d^U~KRI`R1b8)LmM8K7K>6eU()iumZz`q!A=CnCr{Bob4sBcBLbq5GjU-GY3^gdm%0m*7c4mq2OldHeW; z*%k-v13q7*|>lR8)Z3phWmzjhDZ3MgZ~1)2}%`CgVIc; zCE^(rp$c~S>*^WhAL4C;-JzMQO4XS%)IoqW%qA$*%QF(y3i1#3k5tv^#<>iK)S^AG zY08KD2Zwu(QmKr4c3^jTkZE1Ri0(W&cC#_maBUB6j|%dPjP&tVO@*6M$nFyn73q(9 z`9zG4@(GVoweD$LN$6T1m1;E7(FEQGPVMmY3Js^8^Z}=SxY~57fQZnLR`7EQoGKFG6FkNzT%~FUo3E*Xt?|+7(0WHo#q^GHT1UB`|CZUT(d>phgcwZDuezdSDzaNw;)(1*^UPq{bYJ@9AxR0+- zbPE8=;2+@`9_|^VvW87HFB!nOFO*tT8k~CQy{O1@C{6i;P)ffIN)1^ACI9hIO1~qB z4^1D~l-_YD`kyjb0Z>cKMTUmxc52}Vw18TC0!ou`E0kK$0%JS z2>vmX3~{~_cm<3OjgE{NyCETsSiOn7KfcHEk-Gw$9JWS0O`S@xsof=^Q~~QaUa=BT z8jw$z*EEwJBR>uBRcKjNZOmB&sHQui?#xj(TPqlxD2)g8Ls` zXgAK`>4O7t=NG0@Dc4Qe=a+JZwev)u!V~p*A5nv?NxbA0P%0rFY6)F1pYv$gWnlY< zjE?dT_X!u1`A1h7yCz@(_iHLNJQ`QjNFUWqS*ejB&cFk4s~2*|qx^lN&_vZ3!GoYQ z7K<10ieT+T;?0@r8Eoo;5s0V0_V*154ac%FUh4n-N)ueKrrM$Si0{cX?wh8tOQ{7xDe=1ei&{=d}=VjcP z1C*A`Jj^ia*56RF6?7W{)u543>dx8F+R&@Zd1=P$nKNbqwJydpCsMyynq1M)SdTXQvnTDbHxposupbHEuFC< zCTn;;leOIM9oRLI?gW%J>$k|STwsm;{s@3td}^IaRTsJiN)?fzlws)xzWI2u zh@c*bhSDw=1f>}|6&b5S!=YuNE+~L@1v^xhw&RlN#xW1~jQ~3VoASjpw+n%(+5?+< zY%P@1djSd@-vB7p-?-F%M+V}jPynr%Zg52D1L;jl=qOd; zDV`p;ZAuFr5&sL~n?NZawj-O+kpZgef~VwCKjGfj7*R$f^ot7i46*U|9uQ~)N*U!J0| zp(03)aAV(|VK%6C zq)&L5vL&ljA1?8H0f;wH0fP~s7B#xUTM_~-2m36PT4aHEO4siycQ^qyrFVkTUho+e zqxfpDt3waLroG_~l(zLWXjQ2BHJ<-#KA%h1^Rczib_pknBkxe$?2S>W2I8KPcEgX? zcaEzVQp`+0O!NDtmu7`KA6YqTjp50K;WI!DfK z;UwLW@EA_taYbD}uBj+Nu_`by5mtBWkzJ4utU3>;8~IrW6}J1lCEB%g1hleAbuondLH z&5{Q!-WsY+s_fFnN&TlB-^NL5jTuh4lzK{^gk?&-A~|8TP>YJo?!VcY2?Nbbw3D+P zoV2xYo$If3a127!{bowGbYYp$AWE7FyrVFrIHIqdnf5C*uVOqdKHzbJ5r$Q zqU_9I&=gWuo3s=b6^;Sb>7+}rX!;>x6P-mJTsc~ig$>6-p47OJe6W*LvZ_io_^+_B znMDWO^?3Y5mxBJB_C08ypVGQwFZBE?oh+3E9Qs#?!pyK;`us$LL5KfDPya-ny`_>h z|IY19(4QSV1pT>X&bYrF@b8653{-SxE1h%}mIKnLWp`JdWM11i^Jp%(!sY!D_GMxsMPvv^eFl zXpBn=jkXF$IoW+#BRTGvR1!<8(_i30%-9IfE^JXk zJy@fke)cd5)a|D_?EKrbUqE{nO*=nDD%s-i@+X7-T(3gVpKDMbGyUft3kLl;?HLtxO}a5cFriH$ngEmv#xv@Sl@Lg8o!pDH9aEgW2TXRVO`##dmaf zY^qMWA{MT{^ed(ZO0U+ zl(^Hu<=`bODnPl_mK-oMDNebYm12a2`!-y`(qU2IJmpJRlnw^8{8iB~ck4JChM~17XqH zLGnk4-1FOvRN_1l#<>s{U!>G!R-SyEm79N!yQiEGp_k?Yigk=T${Vm~G;nW)(a?{? zlK{n{?2BO0Jm7H;U}1D&(Vb`;FXJvmi)T438V0_U>UbMVP?pjUE2*bWN`WO7B&^%8 zu;{4-RKAf<(R}oAv*-(ykOm9ul~UU4G%tN**Rgu3<)|WqDJA@{&L`QK!Qkm>pK0ui zIjrQSI?RN{OQrW^7hv&ns0T{>{pf+7I;|h9-pWAlMF?9hwI1)gRQ@ypEF2vqJA}9| z>aoeNS|b&P2CFy2UrrdOm#hPRT%C{GnZe+FO54*ZSTuN;zHT~=MWCE8UN7|y{BaFH z!{@-_-A)~S2Nrc7e{0wc%7zCm~0-WmsSAMBEohaq0?LplM~|fnzp0m9KI}kR8@3fm`r$u~}Sp!RKf-P`SjOHDOt!;o#+6ap#vU1gz9{9KS`p5F;nd&`ZO|83&R2Fzts$Yvds;DoA-ZB-I(u`&ZfW zr6^df5y$&!BP>1%G3~TZVezLGHWPR=&?sA-HjFHKex}(lK{ij&YhF)~T@&<@F4nkn z(?kk}MU%6bOuM>%A|G*n62l#cWapu#c z7yygv$ghlPu&DLe8$EQ=eOQzaeT%WGKh0=SzLBtK8&KXTNXuaH@+jXqSRIu(+7!x9 zM@prAy(pw`@;4inm^xVMM`7{ZjJAmKGk8O>MPU2r4XY*c@vFylSmXsY9%N?*gHH?E zFUrj1vmccjuakDeqU!Uj+ecVD8@fc(DqhZ6pqFCfd3NPdfs_S{k|_5K+K*%@^=_5G zX9#yR3l^n9ui*M|B0+Xtq}P50#6%hApmCff=Pc4oL9>k4F>336STs6(LH!A*Y%wYU$E*M~mp;~LFPLy3!^wMj=kl9A3 zZ8QgiBIk~2YlZ-?|M>+vX&0uClLr$Wjy3lj7N1#|J({LTat@O0P2z1(9$8BtV9~VU zQ?c!QSgm{ps8KCr}u#?9SoSgn+rCE*R>H74h*)JtWOjgx|& zk$S-51<_mmm9S_bVu!#5`YNO2g z?+B42*HmhewEFl*2&Ua)9>VI||* zC&Ow8i}z-hh=YZP4bPXzu50yDlcmP)pzimB#mhJkXX{{5$ElQ?oTp1=*L8ZyCe;X& z(9>S9Xjg&H<~q%iRM~vJUdjce1t)d_SbPf5_TOt6?+)}B-Vx1)#pfAq=Q*%M%9c8d zlE3kOFRsEze*F0}uET|(ec}6GG_Jo?ayh@k{0kmzs=#4?Nr@)|n!C$o^JaRf)(YOW zc)W?O9RQ2g8h>w_1dE!j>`N9WVDn-vS+3PPkP~|f9SMl1FN0t&etD-aN(PIux$SOHui(b1G z30lh*OC6*a2syw3hU2_WvR%!qVTa#yUA?8}__)+)D{O0L5+HdYY8ETOBmx*2@Xo z^_oZPnPz*61?YhNIQhrBip zQsYg=S&=jtcTTWqIVqP+?FNdI6Sp}?ZxNy)b&fV^y z-HFg(*=>h|_LuD{l{Xb3Z9#}Cfq})|@(32qBc+QqRd>j)8G6mu9dbejo^90LC$r+ofASW;I)jh7hHtZ4ps%FZsjxhK7kM3a68%&7T@5h-g98lyv4X<*S!Rb zdI^&VTSDrox-427_$^is;_6E1ss>1CBW9T^rMM7r3V zg+=+4RU=9Jc+D_PQJy_4XT>Ky7VRti@st4;Rg=&B zbdeG_A*kkaSbP#tRjOrii*E$(u&82q9FS*c27~&DxAn4!!UYoBSgHLyoAPc@Y6q){ zQaIh*uYg7O6eteY@yGk+gu{AGl>>4PWY_`O{D@wgasZ8#b4NKy`3O;o%7{wk4)Rv; zebil87&5eQF)XS#1_B${X;?I5u+WOKhm0$RCRblrm?bGs*A4ivonK1?Zs6KJ=6FE_k|^yVrK?pB%+iX8R;P`F?%u38=mBj zl}owg2dghniI%*FH3Ako$6lw6&Q_i+G;z>8&X&zj<1tx|alz1xiiYK^B&IGo42yPF zRIRB_^Daj=&&6BhQ?hF=o(`VkQ-jaTr?6taI+9Bs?q-hQ= zMTpuAf2h?3SbWyu*}7zYo(~9aaxdDc&&#eC_0mKz2c*YkdkJ;`ST2fldK*;pg7K!0 zdTbOd@`dOAu$IBJ^7GD+^7;S~C)QR|SXFAARygS(HNk0(Q^HvnCmp03 zob`n^gwjz|DZa5n|6VD+3C?0To2%$g0C?a(RH~RQPSO@iDz0RsZOTDPjxm`P$M#UQ z99vMyfB;Qd6Y@!W>eLqFNTDm*AwPI0p#3s8Tbi4didIP>if{K$Y@osEt6S zMaUB;Ro6?Qe^qkijg!G(UPj5BbO!+4x@{GF2H1e}yU z5hop_#OIKK!>9%dl){-x6vzK%HTjr z-6nK9RHc$9-YV`-(K~RG?!-w4DY@B=llUH-bdVCyz)3t4CmltV_+EwnRVDfBtx5)p zJt|^J$v!6R|4b_(%?*+6f1rHE{SO={Rrue@PYo@gFN2yPa33du2RP{) zbrAeNQ%ceiFO;vdNdH5X`6(hOu8T74KsUijDcD`8o6sHvagb85 zRZqN97xfmP50qN$L9s$9I7rxv%BlA%$-m#TluzF?qf=?cFp+|kJbDV7l!7CLO-fbu z7Tia0QVNd33&r~hTiGyRlhT$IEO-c%4k({RMSw9vsmN%Nu&B~(7$-O>6*xiIq!f%5 zc2T8t)LPO>B7TyZm#+X~lSM>Pr7QRXaB4xah%c&yQUoum)RRlW>p_n|Y12IkrKH*P zLTVi48~`fdj7V@+Bq*wc&IwLR1zdzuzN=8R{Plh%16hTzsJMGXq4$M85c&{G{qR`W zPoQ*=lGo>gzkrgTKcM9Iy@>xL^s7+C8RDS^#X&K6Cr8DFN>D1W1YXEdDKdmoy0WlI z=}Y8vkWxA;VUwaPRb`>S3QkJ-stT>4!GIE|EfUs&;-9J>UdUl1D8)B{lEbFLwuPF) zwuj=M$^kEw-U&+S+e0aR2caE>b`tSjg?3Z6WhCqdKnE#h><%S|y@XA_7(#YGD0T55 zp~Imx#r>d^&mT$$DFp+CT~rALi+Dq*NI*&s!k|=8B$TG|bSSkb9!kgGDMif^@ubu< z^My@Heq~|*n;QN$f-)=+85csSLMg&QQKgoz1gEG~c%gi&MY_LJYREbf&lThUSMcAn zslfHZAt~7#gl>dV#kY$1zf)yH*(MScRT|^nf|HWtJ;Ej>J6+iS6|I5vc_LrF$VY0R zOF$t4#BSq-9N!`1|3fL=U6JoTlv?^&-NrHL^CoczX$cv7+r6A>VX zaRN+&lB0Mi9i)^n0ZJ%Q@V`@Y#4{0JR4IQlIB5!$%3A`(KZ9zS0LwX0r9mm-DkvSK zWUm&wMsQLJt`~Mur6JfXI4Qjw{ax69Rg6CYiaUT88k=KKD)6|_6Hw~H)56Y$(m_fE zore;-B=~~~PQjC>`P9beW{CW$G^qH385m7rAJQqU66%24uB1xm+% zM=4)b7gOc(WDxTL^6_ z{E$+**1|5TgxZ+k`5Gl?3yXlWNJvV&J(SwjL2y#)uO7lKs+7JLrI+1aS5kVtuSi8o z!GXdirQi@@lTs-|g-uF9PrQ&_;)VRr5cVvBI7rF= zY~}Wx0wRKx)?t#ciz@9{DI()y5l>1LS}J%dl$O;hVXqdt21*Ah6}(p11`1Gto1m0& zr_jBEAAl+w29z=$hti&W5lYA3DYf{Dh$p2A<_Vkf6nQjk_8zm@tX-oD{BHGQs``WLnHj+Q&O zzPB}KZVr@!OIf_|Xi`Np{`T&*#h&YC_i69f?aQc37hI#Kmbkb$Ie%37iMv+3{oK=# z*y-xCO$QuX@3}nhV0wY1_A__xGUZX%-oDN5xmzw9d9VD5j?br#4KKV_t(fhM6Jr-1 z>NCCL1$VX7JZatiLwhqWmVQ2O*~JSZ%tGHspGrS+%y#)vCpN9TI?T{;XTgm(i_P|a zesz5H|LmoB8#4 zo;0b7W~o)Ym30s{9wx2kHs7r+sFu;8SMjjXrUSnYxql?@(X_x%7x&lqELSgSL4aN4u6|Xoo%hT7*kZt-_34Qf)C0>I3T7;C@HlOS zl-Dp&z2J6Xv)w)0{xZ5r$F|MFPjxF*SbY1S{_QHC+#mHzgPyA{cH1~$`_@8jo^_uy zyCW;OxZTty(KnSb0A{73!;O`Ln!K%&xM7l8zF2&D6Ao^ z{qkq)(rdnN-{<_r%DL~HZk_KGOE126WheXG!Sx>R*1QEVhB#(va=^{f}bR zCPwD$t=X7u>ic=J>U9Ut4)5P?lg`CtPW?7y-s?4Yw-1>j*Y+Ni5SetbM*W?y;WVX?8}zAi!zJ(-?FOHB`0}#e17TL-;-yK{8ana9rwE}=C5iI zR3lwe*orQI#cXJ)IJ@{#S zLEO#Z6Q6BrT37nmJG;%L9mOwQK6~k%e)Tnr=`&{x^c(TDy{^0SA7v-S@0)Jc@?mON zwVHMIp(<*;0i^2r6s_JL#YaZ1mfUU2+}AhlWV5G#$JzFS>i^mU zYVK@uXmEJVODC@NoK$+prH74Ik8EPI+@^NjMS*=*1ukv*_1O^D9y8|zN8-2l-J&i( zwxus4sqv{9RnNC*^-9F{+8pArXkdk$U0$`!<{UnBZ1l=zi`%cf@oM9W;`aW7GO9V- zANYLPs&9!-+Y)^zSl!w))jy_8Q}4%S)4jubtyhg@j*U^hn$OLbC5CR&|6#3q-6rtW zxRv*c*}X0^C~;c*O2v;)nEvvYfF9rP_dUC{+*F@2nkz9IwY~k%vl4kd?z=vn({F|D zv#4Gi)W2x;?rvQ6M}y5vzV&qPvEKZ{{rKD#e*`R&fBksNJA0J+X}fMIsqWwQe7<1i z)c#qTZLw|DJ2_1Asy8(E>FI`>RxNWmW**6&Q}wiUubKOQ@mcFy;9Po4|1GYc2i3Ln zDcSYHgEAHRP8nNl`gIodZRL&MzIBKn(DT=kwMWN3I(wtlvD44f&W_%5aj3&bQ9b&Y zEwE_y+Pr@^?(HC_jTP^Ai?p1upzD*kCzo9(?m7{5ebiC!Dwk@X3+%P6@Xc&1v*sq% z>$VGR=pP~7Zedz%^h=K}mHKyHIP?iiwNZyTf9qW4=!jE;o1Zb=yZ`W|1?}Afcc17x zI%oK)``_NxQm+dC-Fe6D6G3KK*kDGH*BGsoH{N5g5&+rT`fPf|~+FuuKAe%>b;M0YowXW&oDW0dfh9Wme4r zP7;W14iLk#2}Iih*w_M$XEC+_^;-ZG5Qt?BS^(q`NNfRMVEF`Qwghl&2{4Jpw*;_j z1@N4}6lUKF;1Pk;Rshr3V*<&o0o+;x%wQ?40lMk{z7mLME;@h@1UBObKGn0>M*{2Y z0EXHDB(n8(00Zp-EbIa1G7ozIvo-)(1d^E41|Wk#a2o)bWfJgn0I+sYcQY(3I>LS? z0h6~jTHed+;xh9r^N)tX+I`NS?uD+2U(h~dY5ck1vA!k8XI0#IKiT(MercU;rLfAm z{ZD;=b86W19L7ZyZx{QhXLH-GNuIk+yj6yR z>z8)49kpa*xe7C{)tq&ANTc*cwbk|etnxVNlDV+&%tZU8W8z|~D2~lkaZpy&@ke>; zlZCaL-5I{bYLd5$n?u!iEjM%xe0|LB(9V4)p5$s`=9_-!4H}^M7(m4mGfYAoG2kyyz+)LbfxTTj}oRr`pk5545kZukz`)*3)VbVYe5JJ|XGNJ;&%JD0$tI)6 zk@hR9;_}xQPEB5zd@gz3`x*y(yOow>I#|g&2k$)9q}=`yPYqJJK6&P0 zE*tJVpI?2``voVaS^dHtD_4(Yjop47EShwd++qWm~9A~jk*!Cy59Neq#XVrS`gj9#vuhs2-nW;bZv~T$3(3Nk- z4s8AP;kXymT15{zb>Tq&rh6i`AN=;^=E{Q0TarCn^`2Pmne-dKD)C#66~^jO?V8jn z7-G@*MnY0&`@NfoG5-NI>o^-+t?&Uul2{({M)a=j<0 z4_?cB*L6fK$)?e!ap$79#_rqxu9o}x+lQ;Qm^FK6iP#P$N;GVjwD2_F6!=v>&G^YF z`8Fv#^=|g;S($wbBi}r;>esKt@m*_sb*lK;-?^D##P%Bg6Mp-?=tk~}Wx8R#lB&(Q z-K1OeZ)19Fu*rIH=ZgLN2b-GmKSASnH>-@bCBOBO&n`auyF~zRu}ZC$!zHS5Hox{=M%Z$6#A0 z>%nzW9PeyQ-yK^m`|z-s66PykwVe!K_6fN51s95uSAXH>;jw8&3Z?vh=v78xLI=KC052K7L{LpWmqx>O{=Qo8G_?)tFw^8@)nJZuZDu(P?joH4Ufbbq&~h#drIw;wi_9_shDNH0aKmh69(b zZ273%<%=7BspITCL|rXI-D&aL7n-LFdL^A6mR7N9&UnQystdmxiu!fxRH5a_n&YDL z4F|@4JhQo&Q$%pyB&VAxoz6U%|9<10>978@zB%LDmWo5$tuImfd%5F{T*l^Rm%F~Y z&HkRpdnHWW(pGVd&r;xcV^PN&N}ZbPvE@d+UJII=#0T6jF=Me~s_U5>#p|6LSMTMg zUtfM0U(#htM#H%$7J3F=OY48tuw}){!2!>-Nxg@U+q3Z4u7`@_IOSs+`inf{VGaE*?$7(aE%=zW^1}Mc4ciaC(9yZX zrqZwVWt-MxTW{U!<(wS9%ku6l+Xq=Kn?3r{YW?|n&CRoJ=(l*Rs=TJHNp*`Wdz^}t zu$6gqq&u2UbVt(>H!$0o)QRqB2n2Tm*vT>p_;sc`n$7^bnSW;h%Ps&>xW!SYvqKK* z0qP89)fSS;Mw9Gi*(Cc|4M)iDEQTbDohRAP8t5Sh*d&sJET7~MYmVRQS085aBuCh7 zlB3Mt8FGvzksN1_NlvhK?I0&v3Q0D5O_IZ0+CxsU6(pzGN0MCT-T_q|*b`OFrMjPG zR$TzhdZ{f8g|8O{UYS#(bJbEiE=~MndS2d?oN|jge~yx$+-O*C%g6}J>oZrHCyz+I zU#)iAX+v*r^sZhnV_WFsE^)o~1x|B1cB@D)o=4btn0Q_}YyY@`eRkQ+6Lz&fH+agh z@YXNaUhlu*;I!Y8UfqA!X%GAL_iOhySVeSD<;1MDFgA z|LvN+W$swKuzB&RZQp5yk4nv+Gkq=UW1L18jdg>Xj5{0MBIaU^&nEgtW&Gc*`Ve+% zLe#_hmi=zNwI7#uqubc~H3F6%UAWw8NYtJ2HhEhXC2n7zcmL7u{?ko@p52-AB+G;i zb;tXI%U#f&msyRjG!=TIe`5(;WibR!5-8{jkk1;p07UlzNOS?X&hiP=?+f7Q3Q)k} zT><5tA4d6C=Ou()`fLnKfyDX(Uz#{@*3EXEcZUD&x05-b;JY*jU zbR7sVvcnm93qx}AU4B5DyiAH zVIU`o6cDjevxdV#qWwVsKru70z4w{l|UWl;suZ#46xY?pdR~3plb-gP;Y<+Y`r(Y2Lcv80F9W3 z55T%mfGh$wOd16+Fbp7g6hKp!Nx*D0fVD3`bLQ_0kU=1qKnrH&2jCYD5bFofie(e9 zi~z9l2hgz?e}Iz&3JBP<1_1!kkpPJS01hmlK>a8H$3Or_79R+ZN8mXDCuSc6Fmnt* zY7js>_LzX(SOB+RfDSAr7~m0suLL?Vmk@yDXn@Tj0A1Ke0$pPOhK2&Tu=Sw;9|%~4 z0d!*?VF2sK0b~(yW724Vf#U&!M+5X^nFP!x09c0uxHJE7fD8h;1o|+m2mrrWfY=Ct zek_}SCK4|v2^0|UU=5-Gq749vQ2>KkK7smi0FGk-hO+oE0C@zS6By3y z#{$fp1duuwz>_^DU^f}SEgHa!r9=ZfBJh=f4|9nDNS*?)IR?O&eI(FzD!|Zj0RC+K zIDiiXEXD%_GLP{9>!tx@5eR0|1b~6l0fHw0gtANmW-|b+V*y4p|5$(w0=WbtnAJo8 zznK8B69J-FHUZ0c02>3qSQcXdI7y&@Kn!aT2N0bAkQfIrp5+s$KMTNdlDeCl@$YEq z3Se?<=-5HqYpLenOxv`fOMjCC3t|R-v1rk_tznW+qY)XoUX7l=w2HTEd!=>SqAN>E zKNuR*|525xZQ}gDRg7FS>2hxumOD#r+5Oa&GDD8eiAcPV&`ev=`QfHPLA!^p`u@wj zl)WF5U%q;Ax%lZ;tE|sDgj~3G{^hB>H=|eF9DMpl<0ZNsb5A&Fj+CTtdKoWF24+7Q z)txyT)lHp@`cGny3D_k9xJ?0=!cwLHJRX0GuRHKp=%Rm<15M5Fl|Dz!H{Ep#CBN$Jqd>EPggX z9)afseq;8D05cgtY9hc2_LzWOGJxA0fHame2jCHbuLM>zm$?ARDFB=20<2{p33Ocy zFmxWkdbWNZzy|^rNdOy}M-srgB>-6jHZy5Hz`&&d!SeyOvP=SIsQ}h8z;@;@17r}$ zC9soOEdcOarv8OZT%dNvU12r>%ioZ~W+76fvzUbdCkYe~$Yc!`0YoncNL&Q4kL44n zzXHIK0c5dw29QVKIe`PrJ{e%$RLnQ;16cC0l;q;K`cY&1Auz+148ANi4l+&=vyFvU8fW+>` z7F_{vPK}4j?tK-mw0Jmv-l__!pl#6xw{K|ua>be)Ho>kH6xfe4_ z+dKHRw_ozAsPA{ReRn-wKI=&TijZhGS*owD_GLK#Hg0?pRJ~6Fo-N;eSs%lK52-EP%1)>>Ju_@bwqxAJ% uKRES0VB&a9NB;%Ex4Uoo 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 |9Docn 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 JK{qY^{{i94yx0H$ delta 55647 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 Z@2h=Gxh=F+OOhIC?=~)((Mv1U_&>gUheZGY diff --git a/package.json b/package.json index 25c9e3a8..8e3c4710 100644 --- a/package.json +++ b/package.json @@ -51,11 +51,11 @@ }, "homepage": "https://github.com/instructor-ai/instructor-js#readme", "dependencies": { - "zod-stream": "1.0.0", + "zod-stream": "1.0.1", "zod-validation-error": "^2.1.0" }, "peerDependencies": { - "openai": ">=4.24.1", + "openai": ">=4.28.0", "zod": ">=3.22.4" }, "devDependencies": { @@ -64,19 +64,20 @@ "@ianvs/prettier-plugin-sort-imports": "4.1.0", "@types/bun": "^1.0.0", "@types/node": "^20.10.6", - "eslint-config-turbo": "^1.10.12", - "eslint-config-prettier": "^9.0.0", + "@typescript-eslint/eslint-plugin": "^6.11.0", + "@typescript-eslint/parser": "^6.11.0", "eslint-config": "^0.3.0", - "eslint-plugin-prettier": "^5.1.2", + "eslint-config-prettier": "^9.0.0", + "eslint-config-turbo": "^1.10.12", "eslint-import-resolver-typescript": "^3.5.5", "eslint-plugin-import": "^2.27.5", "eslint-plugin-only-warn": "^1.1.0", - "@typescript-eslint/parser": "^6.11.0", - "@typescript-eslint/eslint-plugin": "^6.11.0", + "eslint-plugin-prettier": "^5.1.2", "husky": "^8.0.3", + "llm-polyglot": "^0.0.3", "prettier": "latest", - "tsup": "^8.0.1", "ts-inference-check": "^0.3.0", + "tsup": "^8.0.1", "typescript": "^5.2.2" } } \ No newline at end of file diff --git a/src/constants/providers.ts b/src/constants/providers.ts index fd6022d3..c0c6e381 100644 --- a/src/constants/providers.ts +++ b/src/constants/providers.ts @@ -7,6 +7,7 @@ export const PROVIDERS = { OAI: "OAI", ANYSCALE: "ANYSCALE", TOGETHER: "TOGETHER", + ANTHROPIC: "ANTHROPIC", OTHER: "OTHER" } as const @@ -15,16 +16,18 @@ export type Provider = keyof typeof PROVIDERS export const PROVIDER_SUPPORTED_MODES: { [key in Provider]: Mode[] } = { - [PROVIDERS.OTHER]: [MODE.FUNCTIONS, MODE.TOOLS, MODE.JSON, MODE.JSON_SCHEMA], + [PROVIDERS.OTHER]: [MODE.FUNCTIONS, MODE.TOOLS, MODE.JSON, MODE.JSON_SCHEMA, MODE.MD_JSON], [PROVIDERS.OAI]: [MODE.FUNCTIONS, MODE.TOOLS, MODE.JSON, MODE.MD_JSON], - [PROVIDERS.ANYSCALE]: [MODE.TOOLS, MODE.JSON, MODE.JSON_SCHEMA], - [PROVIDERS.TOGETHER]: [MODE.TOOLS, MODE.JSON, MODE.JSON_SCHEMA] + [PROVIDERS.ANYSCALE]: [MODE.TOOLS, MODE.JSON, MODE.JSON_SCHEMA, MODE.MD_JSON], + [PROVIDERS.TOGETHER]: [MODE.TOOLS, MODE.JSON, MODE.JSON_SCHEMA, MODE.MD_JSON], + [PROVIDERS.ANTHROPIC]: [MODE.MD_JSON, MODE.TOOLS] } as const export const NON_OAI_PROVIDER_URLS = { [PROVIDERS.ANYSCALE]: "api.endpoints.anyscale", [PROVIDERS.TOGETHER]: "api.together.xyz", - [PROVIDERS.OAI]: "api.openai.com" + [PROVIDERS.OAI]: "api.openai.com", + [PROVIDERS.ANTHROPIC]: "api.anthropic.com" } as const export const PROVIDER_PARAMS_TRANSFORMERS = { @@ -110,5 +113,9 @@ export const PROVIDER_SUPPORTED_MODES_BY_MODEL = { "mistralai/Mixtral-8x7B-Instruct-v0.1" ], [MODE.TOOLS]: ["mistralai/Mistral-7B-Instruct-v0.1", "mistralai/Mixtral-8x7B-Instruct-v0.1"] + }, + [PROVIDERS.ANTHROPIC]: { + [MODE.MD_JSON]: ["*"], + [MODE.TOOLS]: ["*"] } } diff --git a/src/dsl/validator.ts b/src/dsl/validator.ts index 060f2c7e..4c18c9b7 100644 --- a/src/dsl/validator.ts +++ b/src/dsl/validator.ts @@ -1,11 +1,11 @@ -import { OAIClientExtended } from "@/instructor" +import { InstructorClient } from "@/instructor" import OpenAI from "openai" import { RefinementCtx, z } from "zod" type AsyncSuperRefineFunction = (data: string, ctx: RefinementCtx) => Promise export const LLMValidator = ( - instructor: OAIClientExtended, + instructor: InstructorClient, statement: string, params: Omit ): AsyncSuperRefineFunction => { @@ -42,9 +42,13 @@ export const LLMValidator = ( } } -export const moderationValidator = (client: OAIClientExtended | OpenAI) => { +export const moderationValidator = (client: InstructorClient) => { return async (value: string, ctx: z.RefinementCtx) => { try { + if (!(client instanceof OpenAI)) { + throw new Error("ModerationValidator only supports OpenAI clients") + } + const response = await client.moderations.create({ input: value }) const flaggedResults = response.results.filter(result => result.flagged) diff --git a/src/index.ts b/src/index.ts index b46ab6fc..159c9fdc 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,6 +1,6 @@ -import Instructor, { OAIClientExtended } from "./instructor" +import Instructor, { InstructorClient } from "./instructor" -export { type OAIClientExtended } +export { type InstructorClient } export * from "./types" export default Instructor diff --git a/src/instructor.ts b/src/instructor.ts index 6fd3019f..ff270361 100644 --- a/src/instructor.ts +++ b/src/instructor.ts @@ -1,8 +1,11 @@ import { ChatCompletionCreateParamsWithModel, + GenericChatCompletion, InstructorConfig, LogLevel, - ReturnTypeBasedOnParams + OpenAILikeClient, + ReturnTypeBasedOnParams, + SupportedInstructorClient } from "@/types" import OpenAI from "openai" import { z } from "zod" @@ -17,22 +20,22 @@ import { PROVIDER_SUPPORTED_MODES_BY_MODEL, PROVIDERS } from "./constants/providers" -import { CompletionMeta } from "./types" +import { ClientTypeChatCompletionParams, CompletionMeta } from "./types" const MAX_RETRIES_DEFAULT = 0 -class Instructor { - readonly client: OpenAI +class Instructor { + readonly client: OpenAILikeClient readonly mode: Mode readonly provider: Provider readonly debug: boolean = false /** * Creates an instance of the `Instructor` class. - * @param {OpenAI} client - The OpenAI client. + * @param {OpenAILikeClient} client - An OpenAI-like client. * @param {string} mode - The mode of operation. */ - constructor({ client, mode, debug = false }: InstructorConfig) { + constructor({ client, mode, debug = false }: InstructorConfig) { this.client = client this.mode = mode this.debug = debug @@ -41,6 +44,7 @@ class Instructor { this.client?.baseURL.includes(NON_OAI_PROVIDER_URLS.ANYSCALE) ? PROVIDERS.ANYSCALE : this.client?.baseURL.includes(NON_OAI_PROVIDER_URLS.TOGETHER) ? PROVIDERS.TOGETHER : this.client?.baseURL.includes(NON_OAI_PROVIDER_URLS.OAI) ? PROVIDERS.OAI + : this.client?.baseURL.includes(NON_OAI_PROVIDER_URLS.ANTHROPIC) ? PROVIDERS.ANTHROPIC : PROVIDERS.OTHER this.provider = provider @@ -137,10 +141,12 @@ class Instructor { } } - let completion: OpenAI.Chat.Completions.ChatCompletion | null = null + let completion: GenericChatCompletion | null = null try { - completion = await this.client.chat.completions.create(resolvedParams) + completion = (await this.client.chat.completions.create( + resolvedParams + )) as GenericChatCompletion this.log("debug", "raw standard completion response: ", completion) } catch (error) { this.log( @@ -258,7 +264,8 @@ class Instructor { this.log("debug", "raw stream completion response: ", completion) return OAIStream({ - res: completion + //TODO: we need to move away from strict openai types - need to cast here but should update to be more flexible + res: completion as AsyncIterable }) }, response_model @@ -282,41 +289,46 @@ class Instructor { create: async < T extends z.AnyZodObject, P extends T extends z.AnyZodObject ? ChatCompletionCreateParamsWithModel - : OpenAI.ChatCompletionCreateParams & { response_model: never } + : ClientTypeChatCompletionParams & { response_model: never } >( params: P - ): Promise> => { + ): Promise> => { this.validateModelModeSupport(params) if (this.isChatCompletionCreateParamsWithModel(params)) { if (params.stream) { return this.chatCompletionStream(params) as ReturnTypeBasedOnParams< + typeof this.client, P & { stream: true } > } else { - return this.chatCompletionStandard(params) as ReturnTypeBasedOnParams

+ return this.chatCompletionStandard(params) as ReturnTypeBasedOnParams< + typeof this.client, + P + > } } else { - const result: OpenAI.Chat.Completions.ChatCompletion = + const result = this.isStandardStream(params) ? await this.client.chat.completions.create(params) : await this.client.chat.completions.create(params) - return result as ReturnTypeBasedOnParams

+ return result as ReturnTypeBasedOnParams } } } } } -export type OAIClientExtended = OpenAI & Instructor +export type InstructorClient = Instructor & + OpenAILikeClient /** * Creates an instance of the `Instructor` class. - * @param {OpenAI} client - The OpenAI client. + * @param {OpenAILikeClient} client - The OpenAI client. * @param {string} mode - The mode of operation. * @param {boolean} debug - Whether to log debug messages. - * @returns {OAIClientExtended} The extended OpenAI client. + * @returns {InstructorClient} The extended OpenAI client. * * @example * import createInstructor from "@instructor-ai/instructor" @@ -326,24 +338,26 @@ export type OAIClientExtended = OpenAI & Instructor * * const client = createInstructor({ * client: OAI, - * mode: "TOOLS", + * mode: "TOOLS", * }) * * @param args * @returns */ -export default function (args: { client: OpenAI; mode: Mode; debug?: boolean }): OAIClientExtended { - const instructor = new Instructor(args) - +export default function (args: { + client: OpenAILikeClient + mode: Mode + debug?: boolean +}): InstructorClient { + const instructor = new Instructor(args) const instructorWithProxy = new Proxy(instructor, { get: (target, prop, receiver) => { if (prop in target) { return Reflect.get(target, prop, receiver) } - return Reflect.get(target.client, prop, receiver) } }) - return instructorWithProxy as OAIClientExtended + return instructorWithProxy as InstructorClient } diff --git a/src/types/index.ts b/src/types/index.ts index 5d989729..4b91ef59 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -7,6 +7,51 @@ import { type ResponseModel as ZResponseModel } from "zod-stream" +export type GenericCreateParams = Omit, "model"> & { + model: string + messages: OpenAI.ChatCompletionCreateParams["messages"] + stream?: boolean + max_tokens?: number | null +} + +export type GenericChatCompletion = Partial & { + [key: string]: unknown +} + +export type GenericChatCompletionStream = AsyncIterable< + Partial & { [key: string]: unknown } +> + +export type GenericClient< + P extends GenericCreateParams = GenericCreateParams, + Completion = GenericChatCompletion, + Chunk = GenericChatCompletionStream +> = { + baseURL: string + chat: { + completions: { + create: (params: P) => CreateMethodReturnType + } + } +} + +export type CreateMethodReturnType< + P extends GenericCreateParams, + Completion = GenericChatCompletion, + Chunk = GenericChatCompletionStream +> = P extends { stream: true } ? Promise : Promise + +export type ClientTypeChatCompletionParams = + C extends OpenAI ? OpenAI.ChatCompletionCreateParams : GenericCreateParams + +export type ClientType = + C extends OpenAI ? "openai" : "generic" + +export type OpenAILikeClient = + ClientType extends "openai" ? OpenAI : C + +export type SupportedInstructorClient = GenericClient | OpenAI + export type LogLevel = "debug" | "info" | "warn" | "error" export type CompletionMeta = Partial & { usage?: OpenAI.CompletionUsage @@ -14,8 +59,8 @@ export type CompletionMeta = Partial & { export type Mode = ZMode export type ResponseModel = ZResponseModel -export type InstructorConfig = { - client: OpenAI +export interface InstructorConfig { + client: OpenAILikeClient mode: Mode debug?: boolean } @@ -26,13 +71,9 @@ export type InstructorChatCompletionParams = { } export type ChatCompletionCreateParamsWithModel = - InstructorChatCompletionParams & OpenAI.ChatCompletionCreateParams - -export type ReturnWithoutModel

= - P extends { stream: true } ? OpenAI.Chat.Completions.ChatCompletion - : OpenAI.Chat.Completions.ChatCompletion + InstructorChatCompletionParams & GenericCreateParams -export type ReturnTypeBasedOnParams

= +export type ReturnTypeBasedOnParams = P extends ( { stream: true @@ -42,5 +83,9 @@ export type ReturnTypeBasedOnParams

= Promise> & { _meta?: CompletionMeta }, void, unknown>> : P extends { response_model: ResponseModel } ? Promise & { _meta?: CompletionMeta }> - : P extends { stream: true } ? Stream - : OpenAI.Chat.Completions.ChatCompletion + : C extends OpenAI ? + P extends { stream: true } ? + Stream + : OpenAI.Chat.Completions.ChatCompletion + : P extends { stream: true } ? Promise + : Promise diff --git a/tests/anthropic.test.ts b/tests/anthropic.test.ts new file mode 100644 index 00000000..7ff4c587 --- /dev/null +++ b/tests/anthropic.test.ts @@ -0,0 +1,236 @@ +import Instructor from "@/index" +import { omit } from "@/lib" +import { describe, expect, test } from "bun:test" +import { createLLMClient } from "llm-polyglot" +import z from "zod" + +const anthropicClient = createLLMClient({ + provider: "anthropic", + apiKey: process.env.ANTHROPIC_API_KEY +}) + +describe("LLMClient Anthropic Provider - mode: TOOLS", () => { + const instructor = Instructor({ + client: anthropicClient, + mode: "TOOLS" + }) + + test("basic completion", async () => { + const completion = await instructor.chat.completions.create({ + model: "claude-3-opus-20240229", + max_tokens: 1000, + messages: [ + { + role: "user", + content: "My name is Dimitri Kennedy." + } + ], + response_model: { + name: "extract_name", + schema: z.object({ + name: z.string() + }) + } + }) + + expect(omit(["_meta"], completion)).toEqual({ name: "Dimitri Kennedy" }) + }) + + test("complex schema - streaming", async () => { + const completion = await instructor.chat.completions.create({ + model: "claude-3-opus-20240229", + max_tokens: 1000, + stream: true, + messages: [ + { + role: "user", + content: `User Data Submission: + + First Name: John + Last Name: Doe + Contact Details: + Email: john.doe@example.com + Phone Number: 555-1234 + Job History: + + Company Name: Acme Corp + Role: Software Engineer + Years: 5 + Company Name: Globex Inc. + Role: Lead Developer + Years: 3 + Skills: + + Programming + Leadership + Communication + ` + } + ], + response_model: { + name: "process_user_data", + schema: z.object({ + userDetails: z.object({ + firstName: z.string(), + lastName: z.string(), + contactDetails: z.object({ + email: z.string(), + phoneNumber: z.string().optional() + }) + }), + jobHistory: z.array( + z.object({ + companyName: z.string(), + role: z.string(), + years: z.number().optional() + }) + ), + skills: z.array(z.string()) + }) + } + }) + + let final = {} + for await (const result of completion) { + final = result + } + + //@ts-expect-error - lazy + expect(omit(["_meta"], final)).toEqual({ + userDetails: { + firstName: "John", + lastName: "Doe", + contactDetails: { + email: "john.doe@example.com", + phoneNumber: "555-1234" + } + }, + jobHistory: [ + { + companyName: "Acme Corp", + role: "Software Engineer", + years: 5 + }, + { + companyName: "Globex Inc.", + role: "Lead Developer", + years: 3 + } + ], + skills: ["Programming", "Leadership", "Communication"] + }) + }) +}) + +describe("LLMClient Anthropic Provider - mode: MD_JSON", () => { + const instructor = Instructor({ + client: anthropicClient, + mode: "MD_JSON" + }) + + test("basic completion", async () => { + const completion = await instructor.chat.completions.create({ + model: "claude-3-opus-20240229", + max_tokens: 1000, + messages: [ + { + role: "user", + content: "My name is Dimitri Kennedy." + } + ], + response_model: { + name: "get_name", + schema: z.object({ + name: z.string() + }) + } + }) + + expect(completion).toEqual({ name: "Dimitri Kennedy" }) + }) + + test("complex schema - streaming", async () => { + const completion = await instructor.chat.completions.create({ + model: "claude-3-opus-20240229", + max_tokens: 1000, + stream: true, + messages: [ + { + role: "user", + content: `User Data Submission: + + First Name: John + Last Name: Doe + Contact Details: + Email: john.doe@example.com + Phone Number: 555-1234 + Job History: + + Company Name: Acme Corp + Role: Software Engineer + Years: 5 + Company Name: Globex Inc. + Role: Lead Developer + Years: 3 + Skills: + + Programming + Leadership + Communication + ` + } + ], + response_model: { + name: "process_user_data", + schema: z.object({ + userDetails: z.object({ + firstName: z.string(), + lastName: z.string(), + contactDetails: z.object({ + email: z.string(), + phoneNumber: z.string().optional() + }) + }), + jobHistory: z.array( + z.object({ + companyName: z.string(), + role: z.string(), + years: z.number().optional() + }) + ), + skills: z.array(z.string()) + }) + } + }) + + let final = {} + for await (const result of completion) { + final = result + } + + //@ts-expect-error - lazy + expect(omit(["_meta"], final)).toEqual({ + userDetails: { + firstName: "John", + lastName: "Doe", + contactDetails: { + email: "john.doe@example.com", + phoneNumber: "555-1234" + } + }, + jobHistory: [ + { + companyName: "Acme Corp", + role: "Software Engineer", + years: 5 + }, + { + companyName: "Globex Inc.", + role: "Lead Developer", + years: 3 + } + ], + skills: ["Programming", "Leadership", "Communication"] + }) + }) +}) diff --git a/tests/inference.test.ts b/tests/inference.test.ts index a24f19a7..abe62e75 100644 --- a/tests/inference.test.ts +++ b/tests/inference.test.ts @@ -49,7 +49,9 @@ describe("Inference Checking", () => { stream: true }) - expect(type(userStream).strictly.is>(true)).toBe(true) + expect( + type(userStream).strictly.is>(true) + ).toBe(true) }) test("response_model, no stream", async () => { diff --git a/tests/mode.test.ts b/tests/mode.test.ts index b13c405b..1ad4999c 100644 --- a/tests/mode.test.ts +++ b/tests/mode.test.ts @@ -82,6 +82,7 @@ const UserSchema = z.object({ async function extractUser(model: string, mode: Mode, provider: Provider) { const config = provider_config[provider] + const oai = new OpenAI({ ...config, organization: process.env.OPENAI_ORG_ID ?? undefined