local-openai2anthropic 0.2.3__py3-none-any.whl → 0.2.5__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- local_openai2anthropic/__init__.py +1 -1
- local_openai2anthropic/converter.py +2 -2
- local_openai2anthropic/router.py +99 -2
- {local_openai2anthropic-0.2.3.dist-info → local_openai2anthropic-0.2.5.dist-info}/METADATA +2 -1
- {local_openai2anthropic-0.2.3.dist-info → local_openai2anthropic-0.2.5.dist-info}/RECORD +8 -8
- {local_openai2anthropic-0.2.3.dist-info → local_openai2anthropic-0.2.5.dist-info}/WHEEL +0 -0
- {local_openai2anthropic-0.2.3.dist-info → local_openai2anthropic-0.2.5.dist-info}/entry_points.txt +0 -0
- {local_openai2anthropic-0.2.3.dist-info → local_openai2anthropic-0.2.5.dist-info}/licenses/LICENSE +0 -0
|
@@ -49,11 +49,11 @@ def convert_anthropic_to_openai(
|
|
|
49
49
|
system = anthropic_params.get("system")
|
|
50
50
|
stop_sequences = anthropic_params.get("stop_sequences")
|
|
51
51
|
stream = anthropic_params.get("stream", False)
|
|
52
|
-
temperature = anthropic_params.get("temperature")
|
|
52
|
+
temperature = anthropic_params.get("temperature", 0.6)
|
|
53
53
|
tool_choice = anthropic_params.get("tool_choice")
|
|
54
54
|
tools = anthropic_params.get("tools")
|
|
55
55
|
top_k = anthropic_params.get("top_k")
|
|
56
|
-
top_p = anthropic_params.get("top_p")
|
|
56
|
+
top_p = anthropic_params.get("top_p", 0.95)
|
|
57
57
|
thinking = anthropic_params.get("thinking")
|
|
58
58
|
# metadata is accepted but not forwarded to OpenAI
|
|
59
59
|
|
local_openai2anthropic/router.py
CHANGED
|
@@ -406,7 +406,7 @@ async def _handle_with_server_tools(
|
|
|
406
406
|
async with httpx.AsyncClient(timeout=settings.request_timeout) as client:
|
|
407
407
|
try:
|
|
408
408
|
# Log full request for debugging
|
|
409
|
-
logger.
|
|
409
|
+
logger.debug(f"Request body: {json.dumps(params, indent=2, default=str)[:3000]}")
|
|
410
410
|
|
|
411
411
|
response = await client.post(url, headers=headers, json=params)
|
|
412
412
|
|
|
@@ -421,7 +421,7 @@ async def _handle_with_server_tools(
|
|
|
421
421
|
)
|
|
422
422
|
|
|
423
423
|
completion_data = response.json()
|
|
424
|
-
logger.
|
|
424
|
+
logger.debug(f"OpenAI response: {json.dumps(completion_data, indent=2)[:500]}...")
|
|
425
425
|
from openai.types.chat import ChatCompletion
|
|
426
426
|
completion = ChatCompletion.model_validate(completion_data)
|
|
427
427
|
|
|
@@ -786,6 +786,103 @@ async def list_models(
|
|
|
786
786
|
)
|
|
787
787
|
|
|
788
788
|
|
|
789
|
+
@router.post("/v1/messages/count_tokens")
|
|
790
|
+
async def count_tokens(
|
|
791
|
+
request: Request,
|
|
792
|
+
settings: Settings = Depends(get_request_settings),
|
|
793
|
+
) -> JSONResponse:
|
|
794
|
+
"""
|
|
795
|
+
Count tokens in messages without creating a message.
|
|
796
|
+
Uses tiktoken for local token counting.
|
|
797
|
+
"""
|
|
798
|
+
try:
|
|
799
|
+
body_bytes = await request.body()
|
|
800
|
+
body_json = json.loads(body_bytes.decode("utf-8"))
|
|
801
|
+
logger.debug(f"[Count Tokens Request] {json.dumps(body_json, ensure_ascii=False, indent=2)}")
|
|
802
|
+
except json.JSONDecodeError as e:
|
|
803
|
+
error_response = AnthropicErrorResponse(
|
|
804
|
+
error=AnthropicError(type="invalid_request_error", message=f"Invalid JSON: {e}")
|
|
805
|
+
)
|
|
806
|
+
return JSONResponse(status_code=422, content=error_response.model_dump())
|
|
807
|
+
except Exception as e:
|
|
808
|
+
error_response = AnthropicErrorResponse(
|
|
809
|
+
error=AnthropicError(type="invalid_request_error", message=str(e))
|
|
810
|
+
)
|
|
811
|
+
return JSONResponse(status_code=400, content=error_response.model_dump())
|
|
812
|
+
|
|
813
|
+
# Validate required fields
|
|
814
|
+
if not isinstance(body_json, dict):
|
|
815
|
+
error_response = AnthropicErrorResponse(
|
|
816
|
+
error=AnthropicError(type="invalid_request_error", message="Request body must be a JSON object")
|
|
817
|
+
)
|
|
818
|
+
return JSONResponse(status_code=422, content=error_response.model_dump())
|
|
819
|
+
|
|
820
|
+
messages = body_json.get("messages", [])
|
|
821
|
+
if not isinstance(messages, list):
|
|
822
|
+
error_response = AnthropicErrorResponse(
|
|
823
|
+
error=AnthropicError(type="invalid_request_error", message="messages must be a list")
|
|
824
|
+
)
|
|
825
|
+
return JSONResponse(status_code=422, content=error_response.model_dump())
|
|
826
|
+
|
|
827
|
+
model = body_json.get("model", "")
|
|
828
|
+
system = body_json.get("system")
|
|
829
|
+
tools = body_json.get("tools", [])
|
|
830
|
+
|
|
831
|
+
try:
|
|
832
|
+
# Use tiktoken for token counting
|
|
833
|
+
import tiktoken
|
|
834
|
+
|
|
835
|
+
# Map model names to tiktoken encoding
|
|
836
|
+
# Claude models don't have direct tiktoken encodings, so we use cl100k_base as approximation
|
|
837
|
+
encoding = tiktoken.get_encoding("cl100k_base")
|
|
838
|
+
|
|
839
|
+
total_tokens = 0
|
|
840
|
+
|
|
841
|
+
# Count system prompt tokens if present
|
|
842
|
+
if system:
|
|
843
|
+
if isinstance(system, str):
|
|
844
|
+
total_tokens += len(encoding.encode(system))
|
|
845
|
+
elif isinstance(system, list):
|
|
846
|
+
for block in system:
|
|
847
|
+
if isinstance(block, dict) and block.get("type") == "text":
|
|
848
|
+
total_tokens += len(encoding.encode(block.get("text", "")))
|
|
849
|
+
|
|
850
|
+
# Count message tokens
|
|
851
|
+
for msg in messages:
|
|
852
|
+
content = msg.get("content", "")
|
|
853
|
+
if isinstance(content, str):
|
|
854
|
+
total_tokens += len(encoding.encode(content))
|
|
855
|
+
elif isinstance(content, list):
|
|
856
|
+
for block in content:
|
|
857
|
+
if isinstance(block, dict):
|
|
858
|
+
if block.get("type") == "text":
|
|
859
|
+
total_tokens += len(encoding.encode(block.get("text", "")))
|
|
860
|
+
elif block.get("type") == "image":
|
|
861
|
+
# Images are typically counted as a fixed number of tokens
|
|
862
|
+
# This is an approximation
|
|
863
|
+
total_tokens += 85 # Standard approximation for images
|
|
864
|
+
|
|
865
|
+
# Count tool definitions tokens
|
|
866
|
+
if tools:
|
|
867
|
+
for tool in tools:
|
|
868
|
+
tool_def = tool if isinstance(tool, dict) else tool.model_dump()
|
|
869
|
+
# Rough approximation for tool definitions
|
|
870
|
+
total_tokens += len(encoding.encode(json.dumps(tool_def)))
|
|
871
|
+
|
|
872
|
+
logger.debug(f"[Count Tokens Response] input_tokens: {total_tokens}")
|
|
873
|
+
|
|
874
|
+
return JSONResponse(content={
|
|
875
|
+
"input_tokens": total_tokens
|
|
876
|
+
})
|
|
877
|
+
|
|
878
|
+
except Exception as e:
|
|
879
|
+
logger.error(f"Token counting error: {e}")
|
|
880
|
+
error_response = AnthropicErrorResponse(
|
|
881
|
+
error=AnthropicError(type="internal_error", message=f"Failed to count tokens: {str(e)}")
|
|
882
|
+
)
|
|
883
|
+
return JSONResponse(status_code=500, content=error_response.model_dump())
|
|
884
|
+
|
|
885
|
+
|
|
789
886
|
@router.get("/health")
|
|
790
887
|
async def health_check() -> dict[str, str]:
|
|
791
888
|
"""Health check endpoint."""
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: local-openai2anthropic
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.5
|
|
4
4
|
Summary: A lightweight proxy server that converts Anthropic Messages API to OpenAI API
|
|
5
5
|
Project-URL: Homepage, https://github.com/dongfangzan/local-openai2anthropic
|
|
6
6
|
Project-URL: Repository, https://github.com/dongfangzan/local-openai2anthropic
|
|
@@ -55,6 +55,7 @@ This proxy translates Claude SDK calls to OpenAI API format in real-time, enabli
|
|
|
55
55
|
- **Offline development** without cloud API costs
|
|
56
56
|
- **Privacy-first AI** - data never leaves your machine
|
|
57
57
|
- **Seamless model switching** between cloud and local
|
|
58
|
+
- **Web Search tool** - built-in Tavily web search for local models
|
|
58
59
|
|
|
59
60
|
---
|
|
60
61
|
|
|
@@ -1,19 +1,19 @@
|
|
|
1
|
-
local_openai2anthropic/__init__.py,sha256=
|
|
1
|
+
local_openai2anthropic/__init__.py,sha256=IEn8YcQGsaEaCr04s3hS2AcgsIt5NU5Qa2C8Uwz7RdY,1059
|
|
2
2
|
local_openai2anthropic/__main__.py,sha256=K21u5u7FN8-DbO67TT_XDF0neGqJeFrVNkteRauCRQk,179
|
|
3
3
|
local_openai2anthropic/config.py,sha256=bnM7p5htd6rHgLn7Z0Ukmm2jVImLuVjIB5Cnfpf2ClY,1918
|
|
4
|
-
local_openai2anthropic/converter.py,sha256=
|
|
4
|
+
local_openai2anthropic/converter.py,sha256=d-qYwtv6FIbpKSRsZN4jhnKM4D4k52la-_bpEYPTAS0,15790
|
|
5
5
|
local_openai2anthropic/daemon.py,sha256=pZnRojGFcuIpR8yLDNjV-b0LJRBVhgRAa-dKeRRse44,10017
|
|
6
6
|
local_openai2anthropic/daemon_runner.py,sha256=rguOH0PgpbjqNsKYei0uCQX8JQOQ1wmtQH1CtW95Dbw,3274
|
|
7
7
|
local_openai2anthropic/main.py,sha256=5tdgPel8RSCn1iK0d7hYAmcTM9vYHlepgQujaEXA2ic,9866
|
|
8
8
|
local_openai2anthropic/openai_types.py,sha256=jFdCvLwtXYoo5gGRqOhbHQcVaxcsxNnCP_yFPIv7rG4,3823
|
|
9
9
|
local_openai2anthropic/protocol.py,sha256=vUEgxtRPFll6jEtLc4DyxTLCBjrWIEScZXhEqe4uibk,5185
|
|
10
|
-
local_openai2anthropic/router.py,sha256=
|
|
10
|
+
local_openai2anthropic/router.py,sha256=imzvgduneiniwHroTgeT9d8q4iF5GAuptaVP38sakUg,40226
|
|
11
11
|
local_openai2anthropic/tavily_client.py,sha256=QsBhnyF8BFWPAxB4XtWCCpHCquNL5SW93-zjTTi4Meg,3774
|
|
12
12
|
local_openai2anthropic/server_tools/__init__.py,sha256=QlJfjEta-HOCtLe7NaY_fpbEKv-ZpInjAnfmSqE9tbk,615
|
|
13
13
|
local_openai2anthropic/server_tools/base.py,sha256=pNFsv-jSgxVrkY004AHAcYMNZgVSO8ZOeCzQBUtQ3vU,5633
|
|
14
14
|
local_openai2anthropic/server_tools/web_search.py,sha256=1C7lX_cm-tMaN3MsCjinEZYPJc_Hj4yAxYay9h8Zbvs,6543
|
|
15
|
-
local_openai2anthropic-0.2.
|
|
16
|
-
local_openai2anthropic-0.2.
|
|
17
|
-
local_openai2anthropic-0.2.
|
|
18
|
-
local_openai2anthropic-0.2.
|
|
19
|
-
local_openai2anthropic-0.2.
|
|
15
|
+
local_openai2anthropic-0.2.5.dist-info/METADATA,sha256=saRDX2uZwiYyNQMXq75WnYbQ8oG2KBkk9gh8yuyqvDg,10108
|
|
16
|
+
local_openai2anthropic-0.2.5.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
17
|
+
local_openai2anthropic-0.2.5.dist-info/entry_points.txt,sha256=hdc9tSJUNxyNLXcTYye5SuD2K0bEQhxBhGnWTFup6ZM,116
|
|
18
|
+
local_openai2anthropic-0.2.5.dist-info/licenses/LICENSE,sha256=X3_kZy3lJvd_xp8IeyUcIAO2Y367MXZc6aaRx8BYR_s,11369
|
|
19
|
+
local_openai2anthropic-0.2.5.dist-info/RECORD,,
|
|
File without changes
|
{local_openai2anthropic-0.2.3.dist-info → local_openai2anthropic-0.2.5.dist-info}/entry_points.txt
RENAMED
|
File without changes
|
{local_openai2anthropic-0.2.3.dist-info → local_openai2anthropic-0.2.5.dist-info}/licenses/LICENSE
RENAMED
|
File without changes
|