Multi-agent discussion framework: - Custom/AutoGen/CrewAI backends - Multi-provider API (role→model binding) - Tools system (files, web, commands) - Markdown scenarios with YAML frontmatter - Workspace init (meeting-room init) - Session save (--save)
97 lines
2.5 KiB
Python
97 lines
2.5 KiB
Python
"""Universal API client supporting multiple OpenAI-compatible providers."""
|
|
|
|
import httpx
|
|
import json
|
|
|
|
|
|
PROVIDERS = {}
|
|
|
|
|
|
def init_providers(config: dict):
|
|
global PROVIDERS
|
|
PROVIDERS = config.get("providers", {})
|
|
|
|
|
|
def _get_client(provider_name: str) -> httpx.Client:
|
|
p = PROVIDERS.get(provider_name)
|
|
if not p:
|
|
raise ValueError(f"Unknown provider: {provider_name}")
|
|
return httpx.Client(
|
|
base_url=p["base_url"],
|
|
headers={"Authorization": f"Bearer {p['api_key']}"},
|
|
timeout=120.0,
|
|
)
|
|
|
|
|
|
def chat(
|
|
provider: str,
|
|
model: str,
|
|
messages: list[dict],
|
|
temperature: float = 0.7,
|
|
tools: list[dict] | None = None,
|
|
) -> dict:
|
|
client = _get_client(provider)
|
|
payload = {
|
|
"model": model,
|
|
"messages": messages,
|
|
"temperature": temperature,
|
|
}
|
|
if tools:
|
|
payload["tools"] = tools
|
|
payload["tool_choice"] = "auto"
|
|
|
|
resp = client.post("/chat/completions", json=payload)
|
|
resp.raise_for_status()
|
|
data = resp.json()
|
|
message = data["choices"][0]["message"]
|
|
|
|
result = {
|
|
"content": message.get("content") or "",
|
|
"tool_calls": None,
|
|
}
|
|
|
|
if message.get("tool_calls"):
|
|
result["tool_calls"] = []
|
|
for tc in message["tool_calls"]:
|
|
result["tool_calls"].append({
|
|
"id": tc["id"],
|
|
"name": tc["function"]["name"],
|
|
"arguments": tc["function"]["arguments"],
|
|
})
|
|
|
|
return result
|
|
|
|
|
|
def chat_stream(
|
|
provider: str,
|
|
model: str,
|
|
messages: list[dict],
|
|
temperature: float = 0.7,
|
|
) -> str:
|
|
client = _get_client(provider)
|
|
payload = {
|
|
"model": model,
|
|
"messages": messages,
|
|
"temperature": temperature,
|
|
"stream": True,
|
|
}
|
|
full_text = ""
|
|
with client.stream("POST", "/chat/completions", json=payload) as resp:
|
|
resp.raise_for_status()
|
|
for line in resp.iter_lines():
|
|
if not line or not line.startswith("data: "):
|
|
continue
|
|
data = line[6:]
|
|
if data == "[DONE]":
|
|
break
|
|
try:
|
|
chunk = json.loads(data)
|
|
delta = chunk["choices"][0]["delta"]
|
|
if "content" in delta and delta["content"]:
|
|
print(delta["content"], end="", flush=True)
|
|
full_text += delta["content"]
|
|
except (json.JSONDecodeError, KeyError, IndexError):
|
|
continue
|
|
print()
|
|
return full_text
|