Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions ipyai/api_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
contents, mk_tr_details)

from .backend_common import BaseBackend, ConversationSeed, compact_tool, seed_to_flat_history
from .lisette_compat import full_response_sentinel_text, is_full_response, strip_full_response_sentinel


class _BridgeNS(dict):
Expand Down Expand Up @@ -40,6 +41,11 @@ def _emit(self, text):
if text: self.display_text += text
return text or ""

def _tool_result_for_outp(self, o):
content = o.get("content")
if is_full_response(content): return {**o, "content": full_response_sentinel_text(content)}
return o

def format_item(self, o):
if isinstance(o, ModelResponse):
if tcs := getattr(contents(o), "tool_calls", None):
Expand All @@ -48,10 +54,10 @@ def format_item(self, o):
if isinstance(o, dict) and "tool_call_id" in o:
tc = self.tcs.pop(o["tool_call_id"], None)
if tc is not None:
self.outp += mk_tr_details(o, tc, mx=self.mx)
self.outp += mk_tr_details(self._tool_result_for_outp(o), tc, mx=self.mx)
self.final_text = self.outp
args = json.loads(tc.function.arguments or "{}")
return self._emit(compact_tool(tc.function.name, args, o.get("content") or ""))
return self._emit(compact_tool(tc.function.name, args, strip_full_response_sentinel(o.get("content") or "")))
res = super().format_item(o)
self.final_text = self.outp
return self._emit(res)
Expand Down
4 changes: 3 additions & 1 deletion ipyai/kernel_bridge.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
import ast, asyncio, json
from queue import Empty

from .lisette_compat import full_response_sentinel_text


CUSTOM_TOOL_NAMES = ("pyrun", "bash", "start_bgterm", "write_stdin", "close_bgterm", "lnhashview_file", "exhash_file", "list_pyskills")
_INJECT_IMPORTS = dict(bash="from safecmd import bash", start_bgterm="from bgterm import start_bgterm",
Expand Down Expand Up @@ -120,7 +122,7 @@ async def call_tool(self, name, args=None):
text = res if isinstance(res, str) else json.dumps(res, ensure_ascii=False, default=str)
if exprs.get("_full"):
from lisette.core import FullResponse
return FullResponse(text)
return FullResponse(full_response_sentinel_text(text))
return text

async def read_var(self, name):
Expand Down
25 changes: 25 additions & 0 deletions ipyai/lisette_compat.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
"Compatibility helpers for lisette response formatting contracts."

FULL_RESPONSE_SENTINEL = "𝍁"


def is_full_response(value):
"Return whether `value` is a lisette-style FullResponse without importing lisette."
return any(cls.__name__ == "FullResponse" for cls in type(value).__mro__)


def full_response_sentinel_text(value):
"Wrap `value` in lisette's serialization-safe no-truncation sentinel."
text = str(value)
if len(text) > 2 and text[0] == FULL_RESPONSE_SENTINEL and text[-1] == FULL_RESPONSE_SENTINEL:
return text
return f"{FULL_RESPONSE_SENTINEL}{text}{FULL_RESPONSE_SENTINEL}"


def strip_full_response_sentinel(value):
"Strip lisette's no-truncation sentinel from display text."
if not isinstance(value, str): return value
text = str(value)
if len(text) > 2 and text[0] == FULL_RESPONSE_SENTINEL and text[-1] == FULL_RESPONSE_SENTINEL:
return text[1:-1]
return value