mirror of
https://github.com/supermemoryai/supermemory.git
synced 2026-10-10 03:28:14 +00:00
fix(openai-sdk-python): restore complete async middleware update
This commit is contained in:
parent
879a4707cd
commit
25ebd8ca81
2 changed files with 545 additions and 154 deletions
|
|
@ -439,164 +439,405 @@ class SupermemoryOpenAIWrapper:
|
|||
def _create_client_variant_facade(self, name: str) -> _ResourceFacade:
|
||||
"""Wrap completions reached through a client response variant."""
|
||||
client_resource = getattr(self._client, name)
|
||||
chat_resource = client_resource.chaëN½¶‰žËkºwµçXÛÛ\][ÛœË\˜]È‹˜]רܙX]WÙ˜XÝÜžBˆ
|
||||
BˆÛY[<EFBFBD>˜Ú]<EFBFBD>Ú]ܘ]×Ü™\ÜÛœÙHHÚ[\S˜[Y\ÜXÙJˆÛÛ\][ÛœÏXÛÛ\][Û—Ü™\ÛÝ\˜ÙJ˜Ú]\˜]È‹˜]רܙX]WÙ˜XÝÜžJBˆ
|
||||
BˆÛY[<EFBFBD><EFBFBD>Ú]ܘ]×Ü™\ÜÛœÙHHÚ[\S˜[Y\ÜXÙJˆÚ]TÚ[\S˜[Y\ÜXÙJˆÛÛ\][ÛœÏXÛÛ\][Û—Ü™\ÛÝ\˜ÙJ˜ÛY[<EFBFBD>\˜]È‹˜]רܙX]WÙ˜XÝÜžJBˆ
|
||||
Bˆ
|
||||
BˆÛY[<EFBFBD>˜Ú]˜ÛÛ\][ÛœË<EFBFBD>Ú]ÜÝ™X[Z[™×Ü™\ÜÛœÙHHÛÛ\][Û—Ü™\ÛÝ\˜ÙJˆ˜ÛÛ\][ÛœË\Ý™X[H‹Ý™X[Z[™×ØÜ™X]WÙ˜XÝÜžBˆ
|
||||
BˆÛY[<EFBFBD>˜Ú]<EFBFBD>Ú]ÜÝ™X[Z[™×Ü™\ÜÛœÙHHÚ[\S˜[Y\ÜXÙJˆÛÛ\][ÛœÏXÛÛ\][Û—Ü™\ÛÝ\˜ÙJ˜Ú]\Ý™X[H‹Ý™X[Z[™×ØÜ™X]WÙ˜XÝÜžJBˆ
|
||||
BˆÛY[<EFBFBD><EFBFBD>Ú]ÜÝ™X[Z[™×Ü™\ÜÛœÙHHÚ[\S˜[Y\ÜXÙJˆÚ]TÚ[\S˜[Y\ÜXÙJˆÛÛ\][ÛœÏXÛÛ\][Û—Ü™\ÛÝ\˜ÙJ˜ÛY[<EFBFBD>\Ý™X[H‹Ý™X[Z[™×ØÜ™X]WÙ˜XÝÜžJBˆ
|
||||
Bˆ
|
||||
Bˆ™]\›ˆØ[‚‚™Yˆ™\ÜÛœÙWݘ\šX[<EFBFBD>ØÜ™X]\ÊÛY[<EFBFBD>ˆ[žK˜[YNˆÝŠHOˆ\ÝÐ[žWN‚ˆ™]\›ˆÂˆÙ]]ŠÛY[<EFBFBD>˜Ú]˜ÛÛ\][ۜ˘[YJK˜Ü™X]KˆÙ]]ŠÛY[<EFBFBD>˜Ú]˜[YJK˜ÛÛ\][ۜ˘ܙX]KˆÙ]]ŠÛY[<EFBFBD>˜[YJK˜Ú]˜ÛÛ\][ۜ˘ܙX]KˆB‚‚”‘PSÐTÖS<EFBFBD>×ÐÓÓTUSÓ—ÔUÈH
|
||||
ˆ››Ü›X[‹ˆ˜ÛÛ\][ۜ˜˜]È‹ˆ˜Ú]œ˜]È‹ˆ˜ÛY[<EFBFBD>œ˜]È‹ˆ˜ÛÛ\][ۜ˜ݙX[Z[™È‹ˆ˜Ú]œÝ™X[Z[™È‹ˆ˜ÛY[<EFBFBD>œÝ™X[Z[™È‹ŠB‚‚™Yˆ™X[Ø\Þ[˜×ØÛÛ\][Û—ØÜ™X]JÛY[<EFBFBD>ˆ[žK]ˆÝŠHOˆ[žN‚ˆYˆ]OH››Ü›X[Ž‚ˆ™]\›ˆÛY[<EFBFBD>˜Ú]˜ÛÛ\][ۜ˘ܙX]BˆYˆ]OH˜ÛÛ\][ۜ˜˜]ÈŽ‚ˆ™]\›ˆÛY[<EFBFBD>˜Ú]˜ÛÛ\][ÛœË<EFBFBD>Ú]ܘ]×Ü™\ÜÛœÙK˜Ü™X]BˆYˆ]OH˜Ú]œ˜]ÈŽ‚ˆ™]\›ˆÛY[<EFBFBD>˜Ú]<EFBFBD>Ú]ܘ]×Ü™\ÜÛœÙK˜ÛÛ\][ۜ˘ܙX]BˆYˆ]OH˜ÛY[<EFBFBD>œ˜]ÈŽ‚ˆ™]\›ˆÛY[<EFBFBD><EFBFBD>Ú]ܘ]×Ü™\ÜÛœÙK˜Ú]˜ÛÛ\][ۜ˘ܙX]BˆYˆ]OH˜ÛÛ\][ۜ˜ݙX[Z[™ÈŽ‚ˆ™]\›ˆÛY[<EFBFBD>˜Ú]˜ÛÛ\][ÛœË<EFBFBD>Ú]ÜÝ™X[Z[™×Ü™\ÜÛœÙK˜Ü™X]BˆYˆ]OH˜Ú]œÝ™X[Z[™ÈŽ‚ˆ™]\›ˆÛY[<EFBFBD>˜Ú]<EFBFBD>Ú]ÜÝ™X[Z[™×Ü™\ÜÛœÙK˜ÛÛ\][ۜ˘ܙX]BˆYˆ]OH˜ÛY[<EFBFBD>œÝ™X[Z[™ÈŽ‚ˆ™]\›ˆÛY[<EFBFBD><EFBFBD>Ú]ÜÝ™X[Z[™×Ü™\ÜÛœÙK˜Ú]˜ÛÛ\][ۜ˘ܙX]Bˆ˜Z\ÙH\ÜÙ\<EFBFBD>[Û‘\œ›ÜŠˆ•[šÛ›ÝÛˆÛÛ\][Ûˆ]ˆÜ]HŠB‚‚<EFBFBD>]\Ý™š^\™J]]Ý\ÙOU<EFBFBD>YJHÈ\NˆYۛܙVÝ[<EFBFBD>\YYXÛܘ]Ü—B™YˆÝ\\›Y[[ÜžWØ\WÚÙ^J
|
||||
HOˆÙ[™\˜]Ü–Ó›Û™K›Û™K›Û™WN‚ˆÚ]]Ú™XÝ
|
||||
ÜË™[<EFBFBD>š\›Û‹È”ÕTT“QSSÔ–WÐTWÒÑVHŽˆ<EFBFBD>\ÝZÙ^HŸJN‚ˆZY[‚‚™Yˆ\ÝÜÚ\™YÜÞ[˜×ØÛY[<EFBFBD>ÙÙ\×Û›ÝÜÝXÚ×Ý[˜[<EFBFBD>ÛZY]Ø\™J
|
||||
HOˆ›Û™N‚ˆ˜\ÙWØÛY[<EFBFBD>ÜšYÚ[˜[ØÜ™X]HHÜ™X]WÜÞ[˜×ØÛY[<EFBFBD>
|
||||
chat_resource = client_resource.chat
|
||||
completions_resource = chat_resource.completions
|
||||
return _ResourceFacade(
|
||||
client_resource,
|
||||
chat=_ResourceFacade(
|
||||
chat_resource,
|
||||
completions=_ResourceFacade(
|
||||
completions_resource,
|
||||
create=self._create_completion_method(
|
||||
completions_resource.create,
|
||||
streaming_response=name == "with_streaming_response",
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
def _create_completion_method(
|
||||
self,
|
||||
original_create: Any,
|
||||
*,
|
||||
streaming_response: bool = False,
|
||||
) -> Any:
|
||||
"""Wrap one completion create implementation with memory injection."""
|
||||
if self._is_async_client and streaming_response:
|
||||
|
||||
BˆÛÚÝ\Έ\ÝÜÝ—HH×B‚ˆ\Þ[˜ÈYˆ˜ZÙWÜ›Û\
|
||||
ˆY\ÜØYÙ\Έ\ÝÐ[žWKˆÛÛ<EFBFBD>Z[™\—ÝYΈ݋ˆÙÙÙ\Žˆ[žKˆ[ÙNˆ[žKˆ\WÚÙ^NˆÝ‹ˆ
|
||||
HOˆ\ÝÐ[žWN‚ˆÛÚÝ\˘\[™
|
||||
ÛÛ<EFBFBD>Z[™\—ÝYÊBˆ™]\›ˆÂˆÈœ›ÛHŽˆœÞ\Ý[H‹˜ÛÛ<EFBFBD>[<EFBFBD>ŽˆˆœÙXÜ™]^ØÛÛ<EFBFBD>Z[™\—ÝYßHŸKˆ
|
||||
›Y\ÜØYÙ\ˈB‚ˆÚ]]Ú
|
||||
ˆœÝ\\›Y[[ÜžWÛÜ[˜ZK›ZY]Ø\™KœÝ\\›Y[[ÜžK”Ý\\›Y[[ÜžH‹ˆ™]\›—ݘ[YOS[ØÚÊ
|
||||
Kˆ
|
||||
K]Ú
|
||||
ˆœÝ\\›Y[[ÜžWÛÜ[˜ZK›ZY]Ø\™K˜YÜÞ\Ý[WÜ›Û\‹ˆÚYWÙY™™XÝY˜ZÙWÜ›Û\ˆ
|
||||
N‚ˆ[˜[<EFBFBD>ØNˆ[žHHÚ]ÜÝ\\›Y[[ÜžJˆ˜\ÙWØÛY[<EFBFBD>ˆZY]Ø\™WÛÜ[ÛœÊ<EFBFBD>[˜[<EFBFBD>XHŠKˆ
|
||||
Bˆ[˜[<EFBFBD>ØŽˆ[žHHÚ]ÜÝ\\›Y[[ÜžJˆ˜\ÙWØÛY[<EFBFBD>ˆZY]Ø\™WÛÜ[ÛœÊ<EFBFBD>[˜[<EFBFBD>XˆŠKˆ
|
||||
B‚ˆ\ÜÙ\<EFBFBD>˜\ÙWØÛY[<EFBFBD>˜Ú]˜ÛÛ\][ۜ˘ܙX]H\ÈÜšYÚ[˜[ØÜ™X]Bˆ\ÜÙ\<EFBFBD>[˜[<EFBFBD>ØK˜Ú]\țݘ\ÙWØÛY[<EFBFBD>˜Ú]ˆ\ÜÙ\<EFBFBD>[˜[<EFBFBD>Ø‹˜Ú]›X\šÙ\ˆOH˜Ú][X\šÙ\ˆ‚ˆ\ÜÙ\<EFBFBD>[˜[<EFBFBD>Ø‹˜Ú]˜ÛÛ\][ۜ˛X\šÙ\ˆOH˜ÛÛ\][ÛœË[X\šÙ\ˆ‚‚ˆ[˜[<EFBFBD>Ø‹˜Ú]˜ÛÛ\][ۜ˘ܙX]Jˆ[Ù[H™Ü]\Ý‹ˆY\ÜØYÙ\ÏVÞÈœ›ÛHŽˆ<EFBFBD>\Ù\ˆ‹˜ÛÛ<EFBFBD>[<EFBFBD>Žˆœš]˜]HŸWKˆ
|
||||
B‚ˆ\ÜÙ\<EFBFBD>ÛÚÝ\ÈOHÈ<EFBFBD>[˜[<EFBFBD>Xˆ—BˆY\ÜØYÙ\ÈHÜšYÚ[˜[ØÜ™X]K˜Ø[Ø\™ÜËšÝØ\™ÜÖÈ›Y\ÜØYÙ\È—Bˆ\ÜÙ\<EFBFBD>Y\ÜØYÙ\ÖÌVȘÛÛ<EFBFBD>[<EFBFBD>—HOHœÙXÜ™]][˜[<EFBFBD>Xˆ‚ˆ\ÜÙ\<EFBFBD>[
|
||||
<EFBFBD>[˜[<EFBFBD>XHˆ›Ý[ˆÝŠY\ÜØYÙJH›ÜˆY\ÜØYÙH[ˆY\ÜØYÙ\ÊB‚ˆ˜\ÙWØÛY[<EFBFBD>˜Ú]˜ÛÛ\][ۜ˘ܙX]Jˆ[Ù[H™Ü]\Ý‹ˆY\ÜØYÙ\ÏVÞÈœ›ÛHŽˆ<EFBFBD>\Ù\ˆ‹˜ÛÛ<EFBFBD>[<EFBFBD>Žˆ<EFBFBD>[<EFBFBD>ܘ\YŸWKˆ
|
||||
Bˆ\ÜÙ\<EFBFBD>ÛÚÝ\ÈOHÈ<EFBFBD>[˜[<EFBFBD>Xˆ—B‚‚™Yˆ\ÝÜ™]ܘ\[™×ØWÙ˜XØYWÜ™XÛÝ™\œ×ÝWÜš\Ý[™WØÛY[<EFBFBD>
|
||||
def create_streaming_with_memory(
|
||||
**kwargs: Any,
|
||||
) -> _AsyncMemoryResponseContextManager:
|
||||
return _AsyncMemoryResponseContextManager(
|
||||
self,
|
||||
original_create,
|
||||
kwargs,
|
||||
)
|
||||
|
||||
HOˆ›Û™N‚ˆ˜\ÙWØÛY[<EFBFBD>ÜšYÚ[˜[ØÜ™X]HHÜ™X]WÜÞ[˜×ØÛY[<EFBFBD>
|
||||
return create_streaming_with_memory
|
||||
|
||||
BˆÛÚÝ\Έ\ÝÜÝ—HH×B‚ˆ\Þ[˜ÈYˆ˜ZÙWÜ›Û\
|
||||
ˆY\ÜØYÙ\Έ\ÝÐ[žWKˆÛÛ<EFBFBD>Z[™\—ÝYΈ݋ˆÙÙÙ\Žˆ[žKˆ[ÙNˆ[žKˆ\WÚÙ^NˆÝ‹ˆ
|
||||
HOˆ\ÝÐ[žWN‚ˆÛÚÝ\˘\[™
|
||||
ÛÛ<EFBFBD>Z[™\—ÝYÊBˆ™]\›ˆÂˆÈœ›ÛHŽˆœÞ\Ý[H‹˜ÛÛ<EFBFBD>[<EFBFBD>ŽˆˆœÙXÜ™]^ØÛÛ<EFBFBD>Z[™\—ÝYßHŸKˆ
|
||||
›Y\ÜØYÙ\ˈB‚ˆÚ]]Ú
|
||||
ˆœÝ\\›Y[[ÜžWÛÜ[˜ZK›ZY]Ø\™KœÝ\\›Y[[ÜžK”Ý\\›Y[[ÜžH‹ˆ™]\›—ݘ[YOS[ØÚÊ
|
||||
Kˆ
|
||||
K]Ú
|
||||
ˆœÝ\\›Y[[ÜžWÛÜ[˜ZK›ZY]Ø\™K˜YÜÞ\Ý[WÜ›Û\‹ˆÚYWÙY™™XÝY˜ZÙWÜ›Û\ˆ
|
||||
N‚ˆ[˜[<EFBFBD>ØNˆ[žHHÚ]ÜÝ\\›Y[[ÜžJˆ˜\ÙWØÛY[<EFBFBD>ˆZY]Ø\™WÛÜ[ÛœÊ<EFBFBD>[˜[<EFBFBD>XHŠKˆ
|
||||
Bˆ[˜[<EFBFBD>ØŽˆ[žHHÚ]ÜÝ\\›Y[[ÜžJˆ[˜[<EFBFBD>ØKˆZY]Ø\™WÛÜ[ÛœÊ<EFBFBD>[˜[<EFBFBD>XˆŠKˆ
|
||||
B‚ˆ[˜[<EFBFBD>Ø‹˜Ú]˜ÛÛ\][ۜ˘ܙX]Jˆ[Ù[H™Ü]\Ý‹ˆY\ÜØYÙ\ÏVÞÈœ›ÛHŽˆ<EFBFBD>\Ù\ˆ‹˜ÛÛ<EFBFBD>[<EFBFBD>Žˆœš]˜]HŸWKˆ
|
||||
B‚ˆ\ÜÙ\<EFBFBD>ÛÚÝ\ÈOHÈ<EFBFBD>[˜[<EFBFBD>Xˆ—Bˆ\ÜÙ\<EFBFBD>ÜšYÚ[˜[ØÜ™X]K˜Ø[ØÛÝ[<EFBFBD>OHBˆ\ÜÙ\<EFBFBD>˜\ÙWØÛY[<EFBFBD>˜Ú]˜ÛÛ\][ۜ˘ܙX]H\ÈÜšYÚ[˜[ØÜ™X]B‚‚™Yˆ\Ýܘ]ר[™ÜÝ™X[Z[™×Ü™\ÜÛœÙWÙ˜XØY\×Ü™[XZ[—ÛY[[ÜžWØ]Ø\™J
|
||||
HOˆ›Û™N‚ˆ˜\ÙWØÛY[<EFBFBD>ÈHÜ™X]WÜÞ[˜×ØÛY[<EFBFBD>
|
||||
if self._is_async_client:
|
||||
|
||||
BˆØ[ÈH]XÚÜ™\ÜÛœÙWݘ\šX[<EFBFBD>ʈ˜\ÙWØÛY[<EFBFBD>ˆ[X™HX™[ˆ[ØÚÊ™]\›—ݘ[YOT˜]Ô™\ÜÛœÙJX™[
|
||||
JKˆ[X™HX™[ˆ[ØÚÊ™]\›—ݘ[YOTÞ[˜ÔÝ™X[PÛÛ<EFBFBD>^
|
||||
X™[
|
||||
JKˆ
|
||||
BˆÛÚÝ\Έ\ÝÜÝ—HH×B‚ˆ\Þ[˜ÈYˆ˜ZÙWÜ›Û\
|
||||
ˆY\ÜØYÙ\Έ\ÝÐ[žWKˆÛÛ<EFBFBD>Z[™\—ÝYΈ݋ˆÙÙÙ\Žˆ[žKˆ[ÙNˆ[žKˆ\WÚÙ^NˆÝ‹ˆ
|
||||
HOˆ\ÝÐ[žWN‚ˆÛÚÝ\˘\[™
|
||||
ÛÛ<EFBFBD>Z[™\—ÝYÊBˆ™]\›ˆÂˆÈœ›ÛHŽˆœÞ\Ý[H‹˜ÛÛ<EFBFBD>[<EFBFBD>ŽˆˆœÙXÜ™]^ØÛÛ<EFBFBD>Z[™\—ÝYßHŸKˆ
|
||||
›Y\ÜØYÙ\ˈB‚ˆÚ]]Ú
|
||||
ˆœÝ\\›Y[[ÜžWÛÜ[˜ZK›ZY]Ø\™KœÝ\\›Y[[ÜžK”Ý\\›Y[[ÜžH‹ˆ™]\›—ݘ[YOS[ØÚÊ
|
||||
Kˆ
|
||||
K]Ú
|
||||
ˆœÝ\\›Y[[ÜžWÛÜ[˜ZK›ZY]Ø\™K˜YÜÞ\Ý[WÜ›Û\‹ˆÚYWÙY™™XÝY˜ZÙWÜ›Û\ˆ
|
||||
N‚ˆ[˜[<EFBFBD>ØŽˆ[žHHÚ]ÜÝ\\›Y[[ÜžJˆ˜\ÙWØÛY[<EFBFBD>ˆZY]Ø\™WÛÜ[ÛœÊ<EFBFBD>[˜[<EFBFBD>XˆŠKˆ
|
||||
B‚ˆ›ÜˆÜ™X]H[ˆ™\ÜÛœÙWݘ\šX[<EFBFBD>ØÜ™X]\Ê[˜[<EFBFBD>Ø‹<EFBFBD>Ú]ܘ]×Ü™\ÜÛœÙHŠN‚ˆ™\ÜÛœÙHHÜ™X]Jˆ[Ù[H™Ü]\Ý‹ˆY\ÜØYÙ\ÏVÞÈœ›ÛHŽˆ<EFBFBD>\Ù\ˆ‹˜ÛÛ<EFBFBD>[<EFBFBD>Žˆœ˜]ÈŸWKˆ
|
||||
Bˆ\ÜÙ\<EFBFBD>™\ÜÛœÙKœ\œÙJ
|
||||
KœÝ\<EFBFBD>ÝÚ]
|
||||
œ\œÙYHŠB‚ˆ›ÜˆÜ™X]H[ˆ™\ÜÛœÙWݘ\šX[<EFBFBD>ØÜ™X]\Ê[˜[<EFBFBD>Ø‹<EFBFBD>Ú]ÜÝ™X[Z[™×Ü™\ÜÛœÙHŠN‚ˆÚ]Ü™X]Jˆ[Ù[H™Ü]\Ý‹ˆY\ÜØYÙ\ÏVÞÈœ›ÛHŽˆ<EFBFBD>\Ù\ˆ‹˜ÛÛ<EFBFBD>[<EFBFBD>ŽˆœÝ™X[Z[™ÈŸWKˆ
|
||||
H\ÈÝ™X[N‚ˆ\ÜÙ\<EFBFBD>Ý™X[K›X™[™[™ÝÚ]
|
||||
œÝ™X[HŠB‚ˆ\ÜÙ\<EFBFBD>ÛÚÝ\ÈOHÈ<EFBFBD>[˜[<EFBFBD>Xˆ—H
|
||||
ˆ
‚ˆ›ÜˆÜ™X]H[ˆØ[Ë<EFBFBD>˜[Y\Ê
|
||||
N‚ˆ\ÜÙ\<EFBFBD>œÙXÜ™]][˜[<EFBFBD>Xˆˆ[ˆÝŠÜ™X]K˜Ø[Ø\™ÜËšÝØ\™ÜÖÈ›Y\ÜØYÙ\È—JB‚‚™Yˆ\ÝØ\Þ[˜×ܘ]ר[™ÜÝ™X[Z[™×Ü™\ÜÛœÙWÜ™Yš^\×Ü™[XZ[—ÛY[[ÜžWØ]Ø\™J
|
||||
HOˆ›Û™N‚ˆ˜\ÙWØÛY[<EFBFBD>ÈHÜ™X]WØ\Þ[˜×ØÛY[<EFBFBD>
|
||||
async def create_async_with_memory(
|
||||
**kwargs: Any,
|
||||
) -> Any:
|
||||
return await self._create_with_memory_async(original_create, **kwargs)
|
||||
|
||||
BˆØ[ÈH]XÚÜ™\ÜÛœÙWݘ\šX[<EFBFBD>ʈ˜\ÙWØÛY[<EFBFBD>ˆ[X™HX™[ˆ\Þ[˜Ó[ØÚÊ™]\›—ݘ[YOT˜]Ô™\ÜÛœÙJX™[
|
||||
JKˆ[X™HX™[ˆ[ØÚÊ™]\›—ݘ[YOP\Þ[˜ÔÝ™X[PÛÛ<EFBFBD>^
|
||||
X™[
|
||||
JKˆ
|
||||
BˆÛÚÝ\Έ\ÝÜÝ—HH×B‚ˆ\Þ[˜ÈYˆ˜ZÙWÜ›Û\
|
||||
ˆY\ÜØYÙ\Έ\ÝÐ[žWKˆÛÛ<EFBFBD>Z[™\—ÝYΈ݋ˆÙÙÙ\Žˆ[žKˆ[ÙNˆ[žKˆ\WÚÙ^NˆÝ‹ˆ
|
||||
HOˆ\ÝÐ[žWN‚ˆÛÚÝ\˘\[™
|
||||
ÛÛ<EFBFBD>Z[™\—ÝYÊBˆ™]\›ˆÂˆÈœ›ÛHŽˆœÞ\Ý[H‹˜ÛÛ<EFBFBD>[<EFBFBD>ŽˆˆœÙXÜ™]^ØÛÛ<EFBFBD>Z[™\—ÝYßHŸKˆ
|
||||
›Y\ÜØYÙ\ˈB‚ˆ\Þ[˜ÈYˆØ[ܘ]ÊÜ™X]Nˆ[žJHOˆ›Û™N‚ˆ™\ÜÛœÙHH]ØZ]Ü™X]Jˆ[Ù[H™Ü]\Ý‹ˆY\ÜØYÙ\ÏVÞÈœ›ÛHŽˆ<EFBFBD>\Ù\ˆ‹˜ÛÛ<EFBFBD>[<EFBFBD>Žˆœ˜]ÈŸWKˆ
|
||||
Bˆ\ÜÙ\<EFBFBD>™\ÜÛœÙKœ\œÙJ
|
||||
KœÝ\<EFBFBD>ÝÚ]
|
||||
œ\œÙYHŠB‚ˆ\Þ[˜ÈYˆÛÛœÝ[YWÜÝ™X[JÝ™X[WØÛÛ<EFBFBD>^ˆ[žJHOˆ›Û™N‚ˆ\Þ[˜ÈÚ]Ý™X[WØÛÛ<EFBFBD>^\ÈÝ™X[N‚ˆ\ÜÙ\<EFBFBD>Ý™X[K›X™[™[™ÝÚ]
|
||||
œÝ™X[HŠB‚ˆÚ]]Ú
|
||||
ˆœÝ\\›Y[[ÜžWÛÜ[˜ZK›ZY]Ø\™KœÝ\\›Y[[ÜžK”Ý\\›Y[[ÜžH‹ˆ™]\›—ݘ[YOS[ØÚÊ
|
||||
Kˆ
|
||||
K]Ú
|
||||
ˆœÝ\\›Y[[ÜžWÛÜ[˜ZK›ZY]Ø\™K˜YÜÞ\Ý[WÜ›Û\‹ˆÚYWÙY™™XÝY˜ZÙWÜ›Û\ˆ
|
||||
N‚ˆ[˜[<EFBFBD>ØŽˆ[žHHÚ]ÜÝ\\›Y[[ÜžJˆ˜\ÙWØÛY[<EFBFBD>ˆZY]Ø\™WÛÜ[ÛœÊ<EFBFBD>[˜[<EFBFBD>XˆŠKˆ
|
||||
B‚ˆ›ÜˆÜ™X]H[ˆ™\ÜÛœÙWݘ\šX[<EFBFBD>ØÜ™X]\Ê[˜[<EFBFBD>Ø‹<EFBFBD>Ú]ܘ]×Ü™\ÜÛœÙHŠN‚ˆ\Þ[˜Ú[Ëœ<EFBFBD>[ŠØ[ܘ]ÊÜ™X]JJB‚ˆ›ÜˆÜ™X]H[ˆ™\ÜÛœÙWݘ\šX[<EFBFBD>ØÜ™X]\Ê[˜[<EFBFBD>Ø‹<EFBFBD>Ú]ÜÝ™X[Z[™×Ü™\ÜÛœÙHŠN‚ˆÝ™X[WØÛÛ<EFBFBD>^HÜ™X]Jˆ[Ù[H™Ü]\Ý‹ˆY\ÜØYÙ\ÏVÞÈœ›ÛHŽˆ<EFBFBD>\Ù\ˆ‹˜ÛÛ<EFBFBD>[<EFBFBD>ŽˆœÝ™X[Z[™ÈŸWKˆ
|
||||
Bˆ\Þ[˜Ú[Ëœ<EFBFBD>[ŠÛÛœÝ[YWÜÝ™X[JÝ™X[WØÛÛ<EFBFBD>^
|
||||
JB‚ˆ\ÜÙ\<EFBFBD>ÛÚÝ\ÈOHÈ<EFBFBD>[˜[<EFBFBD>Xˆ—H
|
||||
ˆ
‚ˆ›ÜˆÜ™X]H[ˆØ[Ë<EFBFBD>˜[Y\Ê
|
||||
N‚ˆ\ÜÙ\<EFBFBD>œÙXÜ™]][˜[<EFBFBD>Xˆˆ[ˆÝŠÜ™X]K˜Ø[Ø\™ÜËšÝØ\™ÜÖÈ›Y\ÜØYÙ\È—JB‚‚<EFBFBD>]\Ý›X\šËœ\˜[Y]š^™Jœ]‹‘PSÐTÖS<EFBFBD>×ÐÓÓTUSÓ—ÔUÊB<EFBFBD>]\Ý›X\šË˜\Þ[˜Ú[˜\Þ[˜ÈYˆ\ÝÜ™X[Ø\Þ[˜×ÛÜ[˜ZWÜ]×Ý\ÙWØ\Þ[˜×ÛZY]Ø\™J]ˆÝŠHOˆ›Û™N‚ˆÛÚÝ\Έ\ÝÜÝ—HH×BˆÜš]\Έ\ÝÝ\VÜÝ‹Ü[Û˜[ÜÝ—KÝ—WHH×BˆÙ[<EFBFBD>ÛY\ÜØYÙ\Έ\ÝÛ\ÝÐ[žWWHH×B‚ˆ\Þ[˜ÈYˆ˜ZÙWÜ›Û\
|
||||
ˆY\ÜØYÙ\Έ\ÝÐ[žWKˆÛÛ<EFBFBD>Z[™\—ÝYΈ݋ˆÙÙÙ\Žˆ[žKˆ[ÙNˆ[žKˆ\WÚÙ^NˆÝ‹ˆ
|
||||
HOˆ\ÝÐ[žWN‚ˆÛÚÝ\˘\[™
|
||||
ÛÛ<EFBFBD>Z[™\—ÝYÊBˆ™]\›ˆÂˆÈœ›ÛHŽˆœÞ\Ý[H‹˜ÛÛ<EFBFBD>[<EFBFBD>ŽˆˆœÙXÜ™]^ØÛÛ<EFBFBD>Z[™\—ÝYßHŸKˆ
|
||||
›Y\ÜØYÙ\ˈB‚ˆ\Þ[˜ÈYˆ˜ZÙWØYÛY[[ÜžJˆÛY[<EFBFBD>ˆ[žKˆÛÛ<EFBFBD>Z[™\—ÝYΈ݋ˆÛÛ<EFBFBD>[<EFBFBD>ˆÝ‹ˆÝ\ÝÛWÚYˆÜ[Û˜[ÜÝ—KˆÙÙÙ\Žˆ[žKˆ
|
||||
HOˆ›Û™N‚ˆÜš]\˘\[™
|
||||
return create_async_with_memory
|
||||
|
||||
ÛÛ<EFBFBD>Z[™\—ÝYËÝ\ÝÛWÚYÛÛ<EFBFBD>[<EFBFBD>
|
||||
JB‚ˆYˆ[™WÜ™\]Y\Ý
|
||||
™\]Y\݈”™\]Y\Ý
|
||||
HOˆ”™\ÜÛœÙN‚ˆ›ÙHHœÛÛ‹›ØYÊ™\]Y\ݘÛÛ<EFBFBD>[<EFBFBD>
|
||||
BˆÙ[<EFBFBD>ÛY\ÜØYÙ\˘\[™
|
||||
›ÙVÈ›Y\ÜØYÙ\È—JBˆ™]\›ˆ”™\ÜÛœÙJˆŒˆ™\]Y\Ý\™\]Y\݈XY\œÏ^ȘÛÛ<EFBFBD>[<EFBFBD>]\HŽˆ˜\XØ][Û‹ÚœÛÛˆŸKˆœÛÛ<EFBFBD>^ˆšYŽˆ˜Ú]Û\]\Ý‹ˆ›Øš™XÝŽˆ˜Ú]˜ÛÛ\][Ûˆ‹ˆ˜Ü™X]YŽˆˆ›[Ù[Žˆ™Ü]\Ý‹ˆ˜ÚÚXÙ\ÈŽˆÂˆÂˆš[™^Žˆˆ›Y\ÜØYÙHŽˆÈœ›ÛHŽˆ˜\ÜÚ\Ý[<EFBFBD>‹˜ÛÛ<EFBFBD>[<EFBFBD>Žˆ›ÚÈŸKˆ™š[š\ÚÜ™X\ÛÛˆŽˆœÝÜ‹ˆBˆKˆKˆ
|
||||
B‚ˆØÛY[<EFBFBD>H<EFBFBD>\Þ[˜ÐÛY[<EFBFBD>
|
||||
˜[œÜÜ<EFBFBD>Z“[ØÚÕ˜[œÜÜ<EFBFBD>
|
||||
[™WÜ™\]Y\Ý
|
||||
JBˆ˜\ÙWØÛY[<EFBFBD>H\Þ[˜ÓÜ[<EFBFBD>RJ\WÚÙ^OH›Ü[˜ZK]\Ý‹ØÛY[<EFBFBD>ZØÛY[<EFBFBD>
|
||||
B‚ˆžN‚ˆÚ]]Ú
|
||||
ˆœÝ\\›Y[[ÜžWÛÜ[˜ZK›ZY]Ø\™KœÝ\\›Y[[ÜžK”Ý\\›Y[[ÜžH‹ˆ™]\›—ݘ[YOS[ØÚÊ
|
||||
Kˆ
|
||||
K]Ú
|
||||
ˆœÝ\\›Y[[ÜžWÛÜ[˜ZK›ZY]Ø\™K˜YÜÞ\Ý[WÜ›Û\‹ˆ™]ÏY˜ZÙWÜ›Û\ˆ
|
||||
K]Ú
|
||||
ˆœÝ\\›Y[[ÜžWÛÜ[˜ZK›ZY]Ø\™K˜YÛY[[ÜžWÝÛÛ‹ˆ™]ÏY˜ZÙWØYÛY[[ÜžKˆ
|
||||
KØ\›š[™Ü˘Ø]ÚÝØ\›š[™Üʈ™XÛÜ™U<EFBFBD>YBˆ
|
||||
H\ÈØ]YÚ‚ˆØ\›š[™ÜËœÚ[\Yš[\Š˜[Ø^\È‹<EFBFBD>[<EFBFBD>[YUØ\›š[™ÊBˆÜ˜\Yˆ[žHHÚ]ÜÝ\\›Y[[ÜžJˆ˜\ÙWØÛY[<EFBFBD>ˆZY]Ø\™WÛÜ[ÛœÊ<EFBFBD>[˜[<EFBFBD>\™X[‹YÛY[[ÜžOH˜[Ø^\ÈŠKˆ
|
||||
BˆÜ™X]HH™X[Ø\Þ[˜×ØÛÛ\][Û—ØÜ™X]Jܘ\Y]
|
||||
BˆÝØ\™ÜÈHˆ›[Ù[Žˆ™Ü]\Ý‹ˆ›Y\ÜØYÙ\ÈŽˆÞÈœ›ÛHŽˆ<EFBFBD>\Ù\ˆ‹˜ÛÛ<EFBFBD>[<EFBFBD>Žˆœš]˜]HY\ÜØYÙHŸWKˆB‚ˆYˆ]OH››Ü›X[Ž‚ˆ™\ÜÛœÙHH]ØZ]Ü™X]J
|
||||
ŠšÝØ\™ÜÊBˆ\ÜÙ\<EFBFBD>™\ÜÛœÙKšYOH˜Ú]Û\]\Ý‚ˆ[Yˆ]™[™ÝÚ]
|
||||
‹œ˜]ÈŠN‚ˆ˜]×Ü™\ÜÛœÙHH]ØZ]Ü™X]J
|
||||
ŠšÝØ\™ÜÊBˆ\ÜÙ\<EFBFBD>˜]×Ü™\ÜÛœÙKœ\œÙJ
|
||||
KšYOH˜Ú]Û\]\Ý‚ˆ[ÙN‚ˆ™\ÜÛœÙWØÛÛ<EFBFBD>^HÜ™X]J
|
||||
ŠšÝØ\™ÜÊBˆ\ÜÙ\<EFBFBD>›Ý[œÜXÝš\Ø]ØZ]X›J™\ÜÛœÙWØÛÛ<EFBFBD>^
|
||||
Bˆ\Þ[˜ÈÚ]™\ÜÛœÙWØÛÛ<EFBFBD>^\ÈÝ™X[Z[™×Ü™\ÜÛœÙN‚ˆ\ÜÙ\<EFBFBD>Ý™X[Z[™×Ü™\ÜÛœÙKœÝ]\רÛÙHOHŒ‚ˆ]ØZ]ܘ\Y<EFBFBD>ØZ]ٛܗؘXÚÙÜ›Ý[™Ý\ÚÜÊ
|
||||
Bˆ]ØZ]\Þ[˜Ú[ËœÛY\
|
||||
|
||||
BˆØË˜ÛÛXÝ
|
||||
def create_sync_with_memory(
|
||||
**kwargs: Any,
|
||||
) -> Any:
|
||||
return self._create_with_memory_sync(original_create, **kwargs)
|
||||
|
||||
B‚ˆ<EFBFBD>[<EFBFBD>[YWÝØ\›š[™ÜÈHˆØ\›š[™Âˆ›ÜˆØ\›š[™È[ˆØ]YÚˆYˆ\ÜÝX˜Û\ÜÊØ\›š[™Ë˜Ø]YÛÜžK<EFBFBD>[<EFBFBD>[YUØ\›š[™ÊBˆB‚ˆ\ÜÙ\<EFBFBD>ÛÚÝ\ÈOHÈ<EFBFBD>[˜[<EFBFBD>\™X[—Bˆ\ÜÙ\<EFBFBD>Üš]\ÈOHˆ
|
||||
ˆ<EFBFBD>[˜[<EFBFBD>\™X[‹ˆ˜ÛÛ<EFBFBD>™\œØ][ÛŽ<EFBFBD>™XY][˜[<EFBFBD>\™X[‹ˆ•\Ù\Žˆš]˜]HY\ÜØYÙH‹ˆ
|
||||
BˆBˆ\ÜÙ\<EFBFBD>[ŠÙ[<EFBFBD>ÛY\ÜØYÙ\ÊHOHBˆ\ÜÙ\<EFBFBD>Ù[<EFBFBD>ÛY\ÜØYÙ\ÖÌVÌVȘÛÛ<EFBFBD>[<EFBFBD>—HOHœÙXÜ™]][˜[<EFBFBD>\™X[‚ˆ\ÜÙ\<EFBFBD><EFBFBD>[<EFBFBD>[YWÝØ\›š[™ÜÈOH×Bˆš[˜[N‚ˆ]ØZ]˜\ÙWØÛY[<EFBFBD>˜ÛÜÙJ
|
||||
B‚‚<EFBFBD>]\Ý›X\šË˜\Þ[˜Ú[ÈÈ\NˆYۛܙVÝ[<EFBFBD>\YYXÛܘ]Ü—B˜\Þ[˜ÈYˆ\ÝÜÚ\™YØ\Þ[˜×ØÛY[<EFBFBD>ÜØ]™\×ÛÛ›WٛܗÜÙ[XÝYÝ[˜[<EFBFBD>
|
||||
|
||||
HOˆ›Û™N‚ˆ˜\ÙWØÛY[<EFBFBD>ÜšYÚ[˜[ØÜ™X]HHÜ™X]WØ\Þ[˜×ØÛY[<EFBFBD>
|
||||
|
||||
BˆÛÚÝ\Έ\ÝÜÝ—HH×BˆÜš]\Έ\ÝÝ\VÜÝ‹Ü[Û˜[ÜÝ—KÝ—WHH×B‚ˆ\Þ[˜ÈYˆ˜ZÙWÜ›Û\
|
||||
ˆY\ÜØYÙ\Έ\ÝÐ[žWKˆÛÛ<EFBFBD>Z[™\—ÝYΈ݋ˆÙÙÙ\Žˆ[žKˆ[ÙNˆ[žKˆ\WÚÙ^NˆÝ‹ˆ
|
||||
HOˆ\ÝÐ[žWN‚ˆÛÚÝ\˘\[™
|
||||
ÛÛ<EFBFBD>Z[™\—ÝYÊBˆ™]\›ˆÂˆÈœ›ÛHŽˆœÞ\Ý[H‹˜ÛÛ<EFBFBD>[<EFBFBD>ŽˆˆœÙXÜ™]^ØÛÛ<EFBFBD>Z[™\—ÝYßHŸKˆ
|
||||
›Y\ÜØYÙ\ˈB‚ˆ\Þ[˜ÈYˆ˜ZÙWØYÛY[[ÜžJˆÛY[<EFBFBD>ˆ[žKˆÛÛ<EFBFBD>Z[™\—ÝYΈ݋ˆÛÛ<EFBFBD>[<EFBFBD>ˆÝ‹ˆÝ\ÝÛWÚYˆÜ[Û˜[ÜÝ—KˆÙÙÙ\Žˆ[žKˆ
|
||||
HOˆ›Û™N‚ˆÜš]\˘\[™
|
||||
|
||||
ÛÛ<EFBFBD>Z[™\—ÝYËÝ\ÝÛWÚYÛÛ<EFBFBD>[<EFBFBD>
|
||||
JB‚ˆÚ]]Ú
|
||||
ˆœÝ\\›Y[[ÜžWÛÜ[˜ZK›ZY]Ø\™KœÝ\\›Y[[ÜžK”Ý\\›Y[[ÜžH‹ˆ™]\›—ݘ[YOS[ØÚÊ
|
||||
Kˆ
|
||||
K]Ú
|
||||
ˆœÝ\\›Y[[ÜžWÛÜ[˜ZK›ZY]Ø\™K˜YÜÞ\Ý[WÜ›Û\‹ˆÚYWÙY™™XÝY˜ZÙWÜ›Û\ˆ
|
||||
K]Ú
|
||||
ˆœÝ\\›Y[[ÜžWÛÜ[˜ZK›ZY]Ø\™K˜YÛY[[ÜžWÝÛÛ‹ˆÚYWÙY™™XÝY˜ZÙWØYÛY[[ÜžKˆ
|
||||
N‚ˆ[˜[<EFBFBD>ØNˆ[žHHÚ]ÜÝ\\›Y[[ÜžJˆ˜\ÙWØÛY[<EFBFBD>ˆZY]Ø\™WÛÜ[ÛœÊ<EFBFBD>[˜[<EFBFBD>XH‹YÛY[[ÜžOH˜[Ø^\ÈŠKˆ
|
||||
Bˆ[˜[<EFBFBD>ØŽˆ[žHHÚ]ÜÝ\\›Y[[ÜžJˆ˜\ÙWØÛY[<EFBFBD>ˆZY]Ø\™WÛÜ[ÛœÊ<EFBFBD>[˜[<EFBFBD>Xˆ‹YÛY[[ÜžOH˜[Ø^\ÈŠKˆ
|
||||
B‚ˆ]ØZ][˜[<EFBFBD>Ø‹˜Ú]˜ÛÛ\][ۜ˘ܙX]Jˆ[Ù[H™Ü]\Ý‹ˆY\ÜØYÙ\ÏVÞÈœ›ÛHŽˆ<EFBFBD>\Ù\ˆ‹˜ÛÛ<EFBFBD>[<EFBFBD>Žˆœš]˜]H[˜[<EFBFBD>ˆY\ÜØYÙHŸWKˆ
|
||||
Bˆ]ØZ][˜[<EFBFBD>ØK<EFBFBD>ØZ]ٛܗؘXÚÙÜ›Ý[™Ý\ÚÜÊ
|
||||
Bˆ]ØZ][˜[<EFBFBD>Ø‹<EFBFBD>ØZ]ٛܗؘXÚÙÜ›Ý[™Ý\ÚÜÊ
|
||||
B‚ˆ\ÜÙ\<EFBFBD>˜\ÙWØÛY[<EFBFBD>˜Ú]˜ÛÛ\][ۜ˘ܙX]H\ÈÜšYÚ[˜[ØÜ™X]Bˆ\ÜÙ\<EFBFBD>ÛÚÝ\ÈOHÈ<EFBFBD>[˜[<EFBFBD>Xˆ—Bˆ\ÜÙ\<EFBFBD>Üš]\ÈOHˆ
|
||||
ˆ<EFBFBD>[˜[<EFBFBD>Xˆ‹ˆ˜ÛÛ<EFBFBD>™\œØ][ÛŽ<EFBFBD>™XY][˜[<EFBFBD>Xˆ‹ˆ•\Ù\Žˆš]˜]H[˜[<EFBFBD>ˆY\ÜØYÙH‹ˆ
|
||||
BˆBˆ\ÜÙ\<EFBFBD>ÜšYÚ[˜[ØÜ™X]K˜Ø[ØÛÝ[<EFBFBD>OHB
|
||||
return create_sync_with_memory
|
||||
|
||||
async def _create_with_memory_async(
|
||||
self,
|
||||
original_create: Any,
|
||||
**kwargs: Any,
|
||||
) -> Any:
|
||||
"""Async version of create with memory injection."""
|
||||
messages = kwargs.get("messages", [])
|
||||
|
||||
if self._options.add_memory == "always":
|
||||
user_message = get_last_user_message(messages)
|
||||
if user_message and user_message.strip():
|
||||
content = (
|
||||
get_conversation_content(messages)
|
||||
if self._options.custom_id
|
||||
else user_message
|
||||
)
|
||||
custom_id = (
|
||||
f"conversation:{self._options.custom_id}"
|
||||
if self._options.custom_id
|
||||
else None
|
||||
)
|
||||
|
||||
# Create background task for memory storage
|
||||
task = asyncio.create_task(
|
||||
add_memory_tool(
|
||||
self._supermemory_client,
|
||||
self._container_tag,
|
||||
content,
|
||||
custom_id,
|
||||
self._logger,
|
||||
)
|
||||
)
|
||||
|
||||
# Track the task and set up cleanup
|
||||
self._background_tasks.add(task)
|
||||
task.add_done_callback(self._background_tasks.discard)
|
||||
|
||||
# Log any exceptions but don't fail the main request
|
||||
def handle_task_exception(task_obj):
|
||||
try:
|
||||
if task_obj.exception() is not None:
|
||||
exception = task_obj.exception()
|
||||
if isinstance(
|
||||
exception,
|
||||
(SupermemoryNetworkError, SupermemoryAPIError),
|
||||
):
|
||||
self._logger.warn(
|
||||
"Background memory storage failed",
|
||||
{
|
||||
"error": str(exception),
|
||||
"type": type(exception).__name__,
|
||||
},
|
||||
)
|
||||
else:
|
||||
self._logger.error(
|
||||
"Unexpected error in background memory storage",
|
||||
{
|
||||
"error": str(exception),
|
||||
"type": type(exception).__name__,
|
||||
},
|
||||
)
|
||||
except asyncio.CancelledError:
|
||||
self._logger.debug("Memory storage task was cancelled")
|
||||
|
||||
task.add_done_callback(handle_task_exception)
|
||||
|
||||
if self._options.mode != "profile":
|
||||
user_message = get_last_user_message(messages)
|
||||
if not user_message:
|
||||
self._logger.debug("No user message found, skipping memory search")
|
||||
return await original_create(**kwargs)
|
||||
|
||||
self._logger.info(
|
||||
"Starting memory search",
|
||||
{
|
||||
"container_tag": self._container_tag,
|
||||
"conversation_id": self._options.custom_id,
|
||||
"mode": self._options.mode,
|
||||
},
|
||||
)
|
||||
|
||||
enhanced_messages = await add_system_prompt(
|
||||
messages,
|
||||
self._container_tag,
|
||||
self._logger,
|
||||
self._options.mode,
|
||||
self._get_api_key(),
|
||||
)
|
||||
|
||||
kwargs["messages"] = enhanced_messages
|
||||
return await original_create(**kwargs)
|
||||
|
||||
def _create_with_memory_sync(
|
||||
self,
|
||||
original_create: Any,
|
||||
**kwargs: Any,
|
||||
) -> Any:
|
||||
"""Sync version of create with memory injection."""
|
||||
# For sync clients, we implement a simplified version without background tasks
|
||||
messages = kwargs.get("messages", [])
|
||||
|
||||
# Handle memory addition synchronously if needed
|
||||
if self._options.add_memory == "always":
|
||||
user_message = get_last_user_message(messages)
|
||||
if user_message and user_message.strip():
|
||||
content = (
|
||||
get_conversation_content(messages)
|
||||
if self._options.custom_id
|
||||
else user_message
|
||||
)
|
||||
custom_id = (
|
||||
f"conversation:{self._options.custom_id}"
|
||||
if self._options.custom_id
|
||||
else None
|
||||
)
|
||||
|
||||
# Use asyncio.run() for the memory addition
|
||||
try:
|
||||
asyncio.run(
|
||||
add_memory_tool(
|
||||
self._supermemory_client,
|
||||
self._container_tag,
|
||||
content,
|
||||
custom_id,
|
||||
self._logger,
|
||||
)
|
||||
)
|
||||
except RuntimeError as e:
|
||||
if "cannot be called from a running event loop" in str(e):
|
||||
# We're in an async context, log warning and skip memory saving
|
||||
self._logger.warn(
|
||||
"Cannot save memory in sync client from async context",
|
||||
{"error": str(e)},
|
||||
)
|
||||
else:
|
||||
raise
|
||||
except SupermemoryNetworkError as e:
|
||||
# Network errors are expected, log as warning
|
||||
self._logger.warn("Network error saving memory", {"error": str(e)})
|
||||
except (SupermemoryAPIError, SupermemoryMemoryOperationError) as e:
|
||||
# API/memory errors are concerning, log as error
|
||||
self._logger.error("Failed to save memory", {"error": str(e)})
|
||||
except Exception as e:
|
||||
# Unexpected errors should be investigated
|
||||
self._logger.error(
|
||||
"Unexpected error saving memory",
|
||||
{"error": str(e), "type": type(e).__name__},
|
||||
)
|
||||
|
||||
# Handle memory search and injection
|
||||
if self._options.mode != "profile":
|
||||
user_message = get_last_user_message(messages)
|
||||
if not user_message:
|
||||
self._logger.debug("No user message found, skipping memory search")
|
||||
return original_create(**kwargs)
|
||||
|
||||
self._logger.info(
|
||||
"Starting memory search",
|
||||
{
|
||||
"container_tag": self._container_tag,
|
||||
"conversation_id": self._options.custom_id,
|
||||
"mode": self._options.mode,
|
||||
},
|
||||
)
|
||||
|
||||
# Use asyncio.run() for memory search and injection
|
||||
try:
|
||||
enhanced_messages = asyncio.run(
|
||||
add_system_prompt(
|
||||
messages,
|
||||
self._container_tag,
|
||||
self._logger,
|
||||
self._options.mode,
|
||||
self._get_api_key(),
|
||||
)
|
||||
)
|
||||
except RuntimeError as e:
|
||||
if "cannot be called from a running event loop" in str(e):
|
||||
# We're in an async context, run in a separate thread
|
||||
import concurrent.futures
|
||||
|
||||
with concurrent.futures.ThreadPoolExecutor(max_workers=1) as executor:
|
||||
future = executor.submit(
|
||||
asyncio.run,
|
||||
add_system_prompt(
|
||||
messages,
|
||||
self._container_tag,
|
||||
self._logger,
|
||||
self._options.mode,
|
||||
self._get_api_key(),
|
||||
),
|
||||
)
|
||||
enhanced_messages = future.result()
|
||||
else:
|
||||
raise
|
||||
|
||||
kwargs["messages"] = enhanced_messages
|
||||
return original_create(**kwargs)
|
||||
|
||||
async def wait_for_background_tasks(self, timeout: Optional[float] = 10.0) -> None:
|
||||
"""
|
||||
Wait for all background memory storage tasks to complete.
|
||||
|
||||
Args:
|
||||
timeout: Maximum time to wait in seconds. None for no timeout.
|
||||
|
||||
Raises:
|
||||
asyncio.TimeoutError: If tasks don't complete within timeout
|
||||
"""
|
||||
if not self._background_tasks:
|
||||
return
|
||||
|
||||
self._logger.debug(
|
||||
f"Waiting for {len(self._background_tasks)} background tasks to complete"
|
||||
)
|
||||
|
||||
try:
|
||||
if timeout is not None:
|
||||
await asyncio.wait_for(
|
||||
asyncio.gather(*self._background_tasks, return_exceptions=True),
|
||||
timeout=timeout,
|
||||
)
|
||||
else:
|
||||
await asyncio.gather(*self._background_tasks, return_exceptions=True)
|
||||
|
||||
self._logger.debug("All background tasks completed")
|
||||
except asyncio.TimeoutError:
|
||||
self._logger.warn(
|
||||
f"Background tasks did not complete within {timeout}s timeout"
|
||||
)
|
||||
# Cancel remaining tasks
|
||||
tasks_to_cancel = [
|
||||
task for task in self._background_tasks if not task.done()
|
||||
]
|
||||
for task in tasks_to_cancel:
|
||||
task.cancel()
|
||||
|
||||
if tasks_to_cancel:
|
||||
await asyncio.gather(*tasks_to_cancel, return_exceptions=True)
|
||||
raise
|
||||
|
||||
def cancel_background_tasks(self) -> None:
|
||||
"""Cancel all pending background tasks."""
|
||||
cancelled_count = 0
|
||||
for task in self._background_tasks:
|
||||
if not task.done():
|
||||
task.cancel()
|
||||
cancelled_count += 1
|
||||
|
||||
if cancelled_count > 0:
|
||||
self._logger.debug(f"Cancelled {cancelled_count} pending background tasks")
|
||||
|
||||
async def __aenter__(self):
|
||||
"""Async context manager entry."""
|
||||
return self
|
||||
|
||||
async def __aexit__(self, exc_type, exc_val, exc_tb):
|
||||
"""Async context manager exit - wait for background tasks."""
|
||||
try:
|
||||
await self.wait_for_background_tasks(timeout=5.0)
|
||||
except asyncio.TimeoutError:
|
||||
self._logger.warn("Some background memory tasks did not complete on exit")
|
||||
|
||||
def __enter__(self):
|
||||
"""Sync context manager entry."""
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||
"""Sync context manager exit - attempt to wait for background tasks."""
|
||||
if self._background_tasks:
|
||||
try:
|
||||
# Try to wait for background tasks in sync context
|
||||
asyncio.run(self.wait_for_background_tasks(timeout=5.0))
|
||||
except RuntimeError as e:
|
||||
if "cannot be called from a running event loop" in str(e):
|
||||
# In async context, just cancel the tasks
|
||||
self._logger.warn(
|
||||
"Cannot wait for background tasks in sync context from async environment. "
|
||||
"Use async context manager or call wait_for_background_tasks() manually."
|
||||
)
|
||||
self.cancel_background_tasks()
|
||||
else:
|
||||
raise
|
||||
except asyncio.TimeoutError:
|
||||
self._logger.warn(
|
||||
"Some background memory tasks did not complete on exit"
|
||||
)
|
||||
self.cancel_background_tasks()
|
||||
|
||||
def __getattr__(self, name: str) -> Any:
|
||||
"""Delegate all other attributes to the wrapped client."""
|
||||
if name in {"with_raw_response", "with_streaming_response"}:
|
||||
value = self._create_client_variant_facade(name)
|
||||
setattr(self, name, value)
|
||||
return value
|
||||
return getattr(self._client, name)
|
||||
|
||||
|
||||
def with_supermemory(
|
||||
openai_client: Union[OpenAI, AsyncOpenAI],
|
||||
options: OpenAIMiddlewareOptions,
|
||||
) -> Union[OpenAI, AsyncOpenAI]:
|
||||
"""
|
||||
Wraps an OpenAI client with SuperMemory middleware to automatically inject relevant memories
|
||||
into the system prompt based on the user's message content.
|
||||
|
||||
This middleware searches the supermemory API for relevant memories using the container tag
|
||||
and user message, then either appends memories to an existing system prompt or creates
|
||||
a new system prompt with the memories.
|
||||
|
||||
Args:
|
||||
openai_client: The OpenAI client to wrap with SuperMemory middleware
|
||||
options: Configuration options for the middleware (container_tag and custom_id are required)
|
||||
|
||||
Returns:
|
||||
An OpenAI client with SuperMemory middleware injected
|
||||
|
||||
Example:
|
||||
```python
|
||||
from supermemory_openai import with_supermemory, OpenAIMiddlewareOptions
|
||||
from openai import OpenAI
|
||||
|
||||
# Create OpenAI client with supermemory middleware
|
||||
openai = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
|
||||
openai_with_supermemory = with_supermemory(
|
||||
openai,
|
||||
OpenAIMiddlewareOptions(
|
||||
container_tag="user-123",
|
||||
custom_id="conversation-456",
|
||||
mode="full",
|
||||
add_memory="always"
|
||||
)
|
||||
)
|
||||
|
||||
# Use normally - memories will be automatically injected
|
||||
response = await openai_with_supermemory.chat.completions.create(
|
||||
model="gpt-4",
|
||||
messages=[
|
||||
{"role": "user", "content": "What's my favorite programming language?"}
|
||||
]
|
||||
)
|
||||
```
|
||||
|
||||
Raises:
|
||||
ValueError: When SUPERMEMORY_API_KEY environment variable is not set
|
||||
Exception: When supermemory API request fails
|
||||
"""
|
||||
wrapper = SupermemoryOpenAIWrapper(openai_client, options)
|
||||
# Return the wrapper, which delegates all attributes to the original client
|
||||
return cast(Union[OpenAI, AsyncOpenAI], wrapper)
|
||||
|
|
|
|||
|
|
@ -3,12 +3,18 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import gc
|
||||
import inspect
|
||||
import json
|
||||
import os
|
||||
import warnings
|
||||
from types import SimpleNamespace
|
||||
from typing import Any, Generator, Literal, Optional
|
||||
from unittest.mock import AsyncMock, Mock, patch
|
||||
|
||||
import httpx
|
||||
import pytest
|
||||
from openai import AsyncOpenAI
|
||||
|
||||
from supermemory_openai import OpenAIMiddlewareOptions, with_supermemory
|
||||
|
||||
|
|
@ -113,6 +119,35 @@ def response_variant_creates(client: Any, name: str) -> list[Any]:
|
|||
]
|
||||
|
||||
|
||||
REAL_ASYNC_COMPLETION_PATHS = (
|
||||
"normal",
|
||||
"completions.raw",
|
||||
"chat.raw",
|
||||
"client.raw",
|
||||
"completions.streaming",
|
||||
"chat.streaming",
|
||||
"client.streaming",
|
||||
)
|
||||
|
||||
|
||||
def real_async_completion_create(client: Any, path: str) -> Any:
|
||||
if path == "normal":
|
||||
return client.chat.completions.create
|
||||
if path == "completions.raw":
|
||||
return client.chat.completions.with_raw_response.create
|
||||
if path == "chat.raw":
|
||||
return client.chat.with_raw_response.completions.create
|
||||
if path == "client.raw":
|
||||
return client.with_raw_response.chat.completions.create
|
||||
if path == "completions.streaming":
|
||||
return client.chat.completions.with_streaming_response.create
|
||||
if path == "chat.streaming":
|
||||
return client.chat.with_streaming_response.completions.create
|
||||
if path == "client.streaming":
|
||||
return client.with_streaming_response.chat.completions.create
|
||||
raise AssertionError(f"Unknown completion path: {path}")
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True) # type: ignore[untyped-decorator]
|
||||
def supermemory_api_key() -> Generator[None, None, None]:
|
||||
with patch.dict(os.environ, {"SUPERMEMORY_API_KEY": "test-key"}):
|
||||
|
|
@ -330,6 +365,121 @@ def test_async_raw_and_streaming_response_prefixes_remain_memory_aware() -> None
|
|||
assert "secret-tenant-b" in str(create.call_args.kwargs["messages"])
|
||||
|
||||
|
||||
@pytest.mark.parametrize("path", REAL_ASYNC_COMPLETION_PATHS)
|
||||
@pytest.mark.asyncio
|
||||
async def test_real_async_openai_paths_use_async_middleware(path: str) -> None:
|
||||
lookups: list[str] = []
|
||||
writes: list[tuple[str, Optional[str], str]] = []
|
||||
sent_messages: list[list[Any]] = []
|
||||
|
||||
async def fake_prompt(
|
||||
messages: list[Any],
|
||||
container_tag: str,
|
||||
logger: Any,
|
||||
mode: Any,
|
||||
api_key: str,
|
||||
) -> list[Any]:
|
||||
lookups.append(container_tag)
|
||||
return [
|
||||
{"role": "system", "content": f"secret-{container_tag}"},
|
||||
*messages,
|
||||
]
|
||||
|
||||
async def fake_add_memory(
|
||||
client: Any,
|
||||
container_tag: str,
|
||||
content: str,
|
||||
custom_id: Optional[str],
|
||||
logger: Any,
|
||||
) -> None:
|
||||
writes.append((container_tag, custom_id, content))
|
||||
|
||||
def handle_request(request: httpx.Request) -> httpx.Response:
|
||||
body = json.loads(request.content)
|
||||
sent_messages.append(body["messages"])
|
||||
return httpx.Response(
|
||||
200,
|
||||
request=request,
|
||||
headers={"content-type": "application/json"},
|
||||
json={
|
||||
"id": "chatcmpl-test",
|
||||
"object": "chat.completion",
|
||||
"created": 0,
|
||||
"model": "gpt-test",
|
||||
"choices": [
|
||||
{
|
||||
"index": 0,
|
||||
"message": {"role": "assistant", "content": "ok"},
|
||||
"finish_reason": "stop",
|
||||
}
|
||||
],
|
||||
},
|
||||
)
|
||||
|
||||
http_client = httpx.AsyncClient(transport=httpx.MockTransport(handle_request))
|
||||
base_client = AsyncOpenAI(api_key="openai-test", http_client=http_client)
|
||||
|
||||
try:
|
||||
with patch(
|
||||
"supermemory_openai.middleware.supermemory.Supermemory",
|
||||
return_value=Mock(),
|
||||
), patch(
|
||||
"supermemory_openai.middleware.add_system_prompt",
|
||||
new=fake_prompt,
|
||||
), patch(
|
||||
"supermemory_openai.middleware.add_memory_tool",
|
||||
new=fake_add_memory,
|
||||
), warnings.catch_warnings(
|
||||
record=True
|
||||
) as caught:
|
||||
warnings.simplefilter("always", RuntimeWarning)
|
||||
wrapped: Any = with_supermemory(
|
||||
base_client,
|
||||
middleware_options("tenant-real", add_memory="always"),
|
||||
)
|
||||
create = real_async_completion_create(wrapped, path)
|
||||
kwargs = {
|
||||
"model": "gpt-test",
|
||||
"messages": [{"role": "user", "content": "private message"}],
|
||||
}
|
||||
|
||||
if path == "normal":
|
||||
response = await create(**kwargs)
|
||||
assert response.id == "chatcmpl-test"
|
||||
elif path.endswith(".raw"):
|
||||
raw_response = await create(**kwargs)
|
||||
assert raw_response.parse().id == "chatcmpl-test"
|
||||
else:
|
||||
response_context = create(**kwargs)
|
||||
assert not inspect.isawaitable(response_context)
|
||||
async with response_context as streaming_response:
|
||||
assert streaming_response.status_code == 200
|
||||
|
||||
await wrapped.wait_for_background_tasks()
|
||||
await asyncio.sleep(0)
|
||||
gc.collect()
|
||||
|
||||
runtime_warnings = [
|
||||
warning
|
||||
for warning in caught
|
||||
if issubclass(warning.category, RuntimeWarning)
|
||||
]
|
||||
|
||||
assert lookups == ["tenant-real"]
|
||||
assert writes == [
|
||||
(
|
||||
"tenant-real",
|
||||
"conversation:thread-tenant-real",
|
||||
"User: private message",
|
||||
)
|
||||
]
|
||||
assert len(sent_messages) == 1
|
||||
assert sent_messages[0][0]["content"] == "secret-tenant-real"
|
||||
assert runtime_warnings == []
|
||||
finally:
|
||||
await base_client.close()
|
||||
|
||||
|
||||
@pytest.mark.asyncio # type: ignore[untyped-decorator]
|
||||
async def test_shared_async_client_saves_only_for_selected_tenant() -> None:
|
||||
base_client, original_create = create_async_client()
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue