Skip to content

Commit 8129b62

Browse files
committed
implemented caching! should increase the usage available
1 parent 2ed6dbe commit 8129b62

File tree

2 files changed

+104
-1
lines changed

2 files changed

+104
-1
lines changed

chatmock/session.py

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
from __future__ import annotations
2+
3+
import hashlib
4+
import json
5+
import threading
6+
import uuid
7+
from typing import Any, Dict, List, Tuple
8+
9+
10+
_LOCK = threading.Lock()
11+
_FINGERPRINT_TO_UUID: Dict[str, str] = {}
12+
_ORDER: List[str] = []
13+
_MAX_ENTRIES = 10000
14+
15+
16+
def _canonicalize_first_user_message(input_items: List[Dict[str, Any]]) -> Dict[str, Any] | None:
17+
"""
18+
Extract the first stable user message from Responses input items. Good use for a fingerprint for prompt caching.
19+
"""
20+
for item in input_items:
21+
if not isinstance(item, dict):
22+
continue
23+
if item.get("type") != "message":
24+
continue
25+
role = item.get("role")
26+
if role != "user":
27+
continue
28+
content = item.get("content")
29+
if not isinstance(content, list):
30+
continue
31+
norm_content = []
32+
for part in content:
33+
if not isinstance(part, dict):
34+
continue
35+
ptype = part.get("type")
36+
if ptype == "input_text":
37+
text = part.get("text") if isinstance(part.get("text"), str) else ""
38+
if text:
39+
norm_content.append({"type": "input_text", "text": text})
40+
elif ptype == "input_image":
41+
url = part.get("image_url") if isinstance(part.get("image_url"), str) else None
42+
if url:
43+
norm_content.append({"type": "input_image", "image_url": url})
44+
if norm_content:
45+
return {"type": "message", "role": "user", "content": norm_content}
46+
return None
47+
48+
49+
def canonicalize_prefix(instructions: str | None, input_items: List[Dict[str, Any]]) -> str:
50+
prefix: Dict[str, Any] = {}
51+
if isinstance(instructions, str) and instructions.strip():
52+
prefix["instructions"] = instructions.strip()
53+
first_user = _canonicalize_first_user_message(input_items)
54+
if first_user is not None:
55+
prefix["first_user_message"] = first_user
56+
return json.dumps(prefix, sort_keys=True, separators=(",", ":"))
57+
58+
59+
def _fingerprint(s: str) -> str:
60+
return hashlib.sha256(s.encode("utf-8")).hexdigest()
61+
62+
63+
def _remember(fp: str, sid: str) -> None:
64+
if fp in _FINGERPRINT_TO_UUID:
65+
return
66+
_FINGERPRINT_TO_UUID[fp] = sid
67+
_ORDER.append(fp)
68+
if len(_ORDER) > _MAX_ENTRIES:
69+
oldest = _ORDER.pop(0)
70+
_FINGERPRINT_TO_UUID.pop(oldest, None)
71+
72+
73+
def ensure_session_id(
74+
instructions: str | None,
75+
input_items: List[Dict[str, Any]],
76+
client_supplied: str | None = None,
77+
) -> str:
78+
if isinstance(client_supplied, str) and client_supplied.strip():
79+
return client_supplied.strip()
80+
81+
canon = canonicalize_prefix(instructions, input_items)
82+
fp = _fingerprint(canon)
83+
with _LOCK:
84+
if fp in _FINGERPRINT_TO_UUID:
85+
return _FINGERPRINT_TO_UUID[fp]
86+
sid = str(uuid.uuid4())
87+
_remember(fp, sid)
88+
return sid
89+

chatmock/upstream.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99

1010
from .config import CHATGPT_RESPONSES_URL
1111
from .http import build_cors_headers
12+
from .session import ensure_session_id
13+
from flask import request as flask_request
1214
from .utils import get_effective_chatgpt_auth
1315

1416

@@ -59,6 +61,17 @@ def start_upstream_request(
5961
if isinstance(reasoning_param, dict) and reasoning_param.get("effort") != "none":
6062
include.append("reasoning.encrypted_content")
6163

64+
client_session_id = None
65+
try:
66+
client_session_id = (
67+
flask_request.headers.get("X-Session-Id")
68+
or flask_request.headers.get("session_id")
69+
or None
70+
)
71+
except Exception:
72+
client_session_id = None
73+
session_id = ensure_session_id(instructions, input_items, client_session_id)
74+
6275
responses_payload = {
6376
"model": model,
6477
"instructions": instructions if isinstance(instructions, str) and instructions.strip() else instructions,
@@ -69,6 +82,7 @@ def start_upstream_request(
6982
"store": False,
7083
"stream": True,
7184
"include": include,
85+
"prompt_cache_key": session_id,
7286
}
7387

7488
if reasoning_param is not None:
@@ -80,6 +94,7 @@ def start_upstream_request(
8094
"Accept": "text/event-stream",
8195
"chatgpt-account-id": account_id,
8296
"OpenAI-Beta": "responses=experimental",
97+
"session_id": session_id,
8398
}
8499

85100
try:
@@ -96,4 +111,3 @@ def start_upstream_request(
96111
resp.headers.setdefault(k, v)
97112
return None, resp
98113
return upstream, None
99-

0 commit comments

Comments
 (0)