Initial commit: add .gitignore and README
This commit is contained in:
13
fusionagi/api/openai_compat/__init__.py
Normal file
13
fusionagi/api/openai_compat/__init__.py
Normal file
@@ -0,0 +1,13 @@
|
||||
"""OpenAI-compatible API bridge for Cursor Composer and other OpenAI API consumers."""
|
||||
|
||||
from fusionagi.api.openai_compat.translators import (
|
||||
messages_to_prompt,
|
||||
estimate_usage,
|
||||
final_response_to_openai,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
"messages_to_prompt",
|
||||
"estimate_usage",
|
||||
"final_response_to_openai",
|
||||
]
|
||||
146
fusionagi/api/openai_compat/translators.py
Normal file
146
fusionagi/api/openai_compat/translators.py
Normal file
@@ -0,0 +1,146 @@
|
||||
"""Translators for OpenAI API request/response format to FusionAGI."""
|
||||
|
||||
import time
|
||||
from typing import Any
|
||||
|
||||
from fusionagi.schemas.witness import FinalResponse
|
||||
|
||||
|
||||
def _extract_content(msg: dict[str, Any]) -> str:
|
||||
"""Extract text content from a message. Handles string or array content parts."""
|
||||
content = msg.get("content")
|
||||
if content is None:
|
||||
return ""
|
||||
if isinstance(content, str):
|
||||
return content
|
||||
if isinstance(content, list):
|
||||
parts: list[str] = []
|
||||
for part in content:
|
||||
if isinstance(part, dict) and part.get("type") == "text":
|
||||
parts.append(part.get("text", "") or "")
|
||||
elif isinstance(part, str):
|
||||
parts.append(part)
|
||||
return "\n".join(parts)
|
||||
return str(content)
|
||||
|
||||
|
||||
def messages_to_prompt(messages: list[dict[str, Any]]) -> str:
|
||||
"""
|
||||
Translate OpenAI messages array to a single prompt string for Dvādaśa.
|
||||
|
||||
Format:
|
||||
[System]: {system_content}
|
||||
[User]: {user_msg_1}
|
||||
[Assistant]: {assistant_msg_1}
|
||||
[User]: {user_msg_2}
|
||||
...
|
||||
[User]: {last_user_message} <- primary goal for submit_task
|
||||
|
||||
Tool result messages (role: "tool") are appended as "Tool {name} returned: {content}".
|
||||
Falls back to last non-system message if no explicit user turn.
|
||||
|
||||
Args:
|
||||
messages: List of message dicts with 'role' and 'content'.
|
||||
|
||||
Returns:
|
||||
Single prompt string for orch.submit_task / run_dvadasa.
|
||||
"""
|
||||
if not messages:
|
||||
return ""
|
||||
|
||||
parts: list[str] = []
|
||||
system_content = ""
|
||||
last_user_content = ""
|
||||
|
||||
for msg in messages:
|
||||
role = (msg.get("role") or "user").lower()
|
||||
content = _extract_content(msg)
|
||||
|
||||
if role == "system":
|
||||
system_content = content
|
||||
elif role == "user":
|
||||
last_user_content = content
|
||||
if system_content and not parts:
|
||||
parts.append(f"[System]: {system_content}")
|
||||
parts.append(f"[User]: {content}")
|
||||
elif role == "assistant":
|
||||
if system_content and not parts:
|
||||
parts.append(f"[System]: {system_content}")
|
||||
parts.append(f"[Assistant]: {content}")
|
||||
elif role == "tool":
|
||||
name = msg.get("name", "unknown")
|
||||
tool_id = msg.get("tool_call_id", "")
|
||||
parts.append(f"[Tool {name}]{f' (id={tool_id})' if tool_id else ''} returned: {content}")
|
||||
|
||||
if not parts:
|
||||
return last_user_content or system_content
|
||||
|
||||
return "\n\n".join(parts)
|
||||
|
||||
|
||||
def estimate_usage(
|
||||
messages: list[dict[str, Any]],
|
||||
completion_text: str,
|
||||
chars_per_token: int = 4,
|
||||
) -> dict[str, int]:
|
||||
"""
|
||||
Estimate token usage from character counts (OpenAI-like heuristic).
|
||||
|
||||
Args:
|
||||
messages: Request messages for prompt_tokens.
|
||||
completion_text: Response text for completion_tokens.
|
||||
chars_per_token: Approximate chars per token (default 4).
|
||||
|
||||
Returns:
|
||||
Dict with prompt_tokens, completion_tokens, total_tokens.
|
||||
"""
|
||||
prompt_chars = sum(len(_extract_content(m)) for m in messages)
|
||||
completion_chars = len(completion_text)
|
||||
prompt_tokens = max(1, prompt_chars // chars_per_token)
|
||||
completion_tokens = max(1, completion_chars // chars_per_token)
|
||||
return {
|
||||
"prompt_tokens": prompt_tokens,
|
||||
"completion_tokens": completion_tokens,
|
||||
"total_tokens": prompt_tokens + completion_tokens,
|
||||
}
|
||||
|
||||
|
||||
def final_response_to_openai(
|
||||
final: FinalResponse,
|
||||
task_id: str,
|
||||
request_model: str | None = None,
|
||||
messages: list[dict[str, Any]] | None = None,
|
||||
) -> dict[str, Any]:
|
||||
"""
|
||||
Map FusionAGI FinalResponse to OpenAI Chat Completion format.
|
||||
|
||||
Args:
|
||||
final: FinalResponse from run_dvadasa.
|
||||
task_id: Task ID for response id.
|
||||
request_model: Model ID from request, or default fusionagi-dvadasa.
|
||||
messages: Original request messages for usage estimation.
|
||||
|
||||
Returns:
|
||||
OpenAI-compatible chat completion dict.
|
||||
"""
|
||||
model = request_model or "fusionagi-dvadasa"
|
||||
usage = estimate_usage(messages or [], final.final_answer)
|
||||
|
||||
return {
|
||||
"id": f"chatcmpl-{task_id[:24]}" if len(task_id) >= 24 else f"chatcmpl-{task_id}",
|
||||
"object": "chat.completion",
|
||||
"created": int(time.time()),
|
||||
"model": model,
|
||||
"choices": [
|
||||
{
|
||||
"index": 0,
|
||||
"message": {
|
||||
"role": "assistant",
|
||||
"content": final.final_answer,
|
||||
"tool_calls": None,
|
||||
},
|
||||
"finish_reason": "stop",
|
||||
}
|
||||
],
|
||||
"usage": usage,
|
||||
}
|
||||
Reference in New Issue
Block a user