-
Notifications
You must be signed in to change notification settings - Fork 1.7k
update openai server #3346
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
update openai server #3346
Changes from 6 commits
986abd9
f175024
fff6cf2
59f473c
1bac929
7452bb7
041f564
bd11420
8d69cf8
ebb6c28
648661d
43235f9
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -1957,6 +1957,26 @@ | |||||||||||||||||||||||||||||||||||||
| if reset_memory: | ||||||||||||||||||||||||||||||||||||||
| self.init_messages() | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| def reset_system_message( | ||||||||||||||||||||||||||||||||||||||
| self, content: str, reset_memory: bool = True | ||||||||||||||||||||||||||||||||||||||
| ) -> None: | ||||||||||||||||||||||||||||||||||||||
| """Reset context to new system message. | ||||||||||||||||||||||||||||||||||||||
| Args: | ||||||||||||||||||||||||||||||||||||||
| content (str): The new system message. | ||||||||||||||||||||||||||||||||||||||
| reset_memory (bool): | ||||||||||||||||||||||||||||||||||||||
| Whether to reinitialize conversation messages after appending | ||||||||||||||||||||||||||||||||||||||
| additional context. Defaults to True. | ||||||||||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||||||||||
| self._original_system_message = BaseMessage.make_system_message( | ||||||||||||||||||||||||||||||||||||||
| content | ||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||
| self._system_message = ( | ||||||||||||||||||||||||||||||||||||||
| self._generate_system_message_for_output_language() | ||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||
| if reset_memory: | ||||||||||||||||||||||||||||||||||||||
| self.init_messages() | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| def reset_to_original_system_message(self) -> None: | ||||||||||||||||||||||||||||||||||||||
| r"""Reset system message to original, removing any appended context. | ||||||||||||||||||||||||||||||||||||||
|
|
@@ -5208,3 +5228,265 @@ | |||||||||||||||||||||||||||||||||||||
| mcp_server.tool()(get_available_tools) | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| return mcp_server | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| @dependencies_required("fastapi") | ||||||||||||||||||||||||||||||||||||||
| def to_openai_compatible_server(self) -> Any: | ||||||||||||||||||||||||||||||||||||||
| r"""Create an OpenAI-compatible FastAPI server for this ChatAgent. | ||||||||||||||||||||||||||||||||||||||
| Returns: | ||||||||||||||||||||||||||||||||||||||
| FastAPI: A FastAPI application that can be served to provide | ||||||||||||||||||||||||||||||||||||||
| OpenAI-compatible API endpoints for this ChatAgent. | ||||||||||||||||||||||||||||||||||||||
| Example: | ||||||||||||||||||||||||||||||||||||||
| ```python | ||||||||||||||||||||||||||||||||||||||
| agent = ChatAgent(model="gpt-4") | ||||||||||||||||||||||||||||||||||||||
| app = agent.to_openai_compatible_server() | ||||||||||||||||||||||||||||||||||||||
| # Serve with uvicorn | ||||||||||||||||||||||||||||||||||||||
| import uvicorn | ||||||||||||||||||||||||||||||||||||||
| uvicorn.run(app, host="0.0.0.0", port=8000) | ||||||||||||||||||||||||||||||||||||||
| ``` | ||||||||||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||||||||||
| import asyncio | ||||||||||||||||||||||||||||||||||||||
| import json | ||||||||||||||||||||||||||||||||||||||
| import time | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| from fastapi import FastAPI | ||||||||||||||||||||||||||||||||||||||
| from fastapi.responses import JSONResponse, StreamingResponse | ||||||||||||||||||||||||||||||||||||||
| from pydantic import BaseModel | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| # Define Pydantic models for request/response | ||||||||||||||||||||||||||||||||||||||
| class ChatMessage(BaseModel): | ||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||
| role: str | ||||||||||||||||||||||||||||||||||||||
| content: str = "" | ||||||||||||||||||||||||||||||||||||||
| name: Optional[str] = None | ||||||||||||||||||||||||||||||||||||||
| tool_calls: Optional[List[Dict[str, Any]]] = None | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| app = FastAPI( | ||||||||||||||||||||||||||||||||||||||
| title="CAMEL OpenAI-compatible API", | ||||||||||||||||||||||||||||||||||||||
| description="OpenAI-compatible API for CAMEL ChatAgent", | ||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| @app.post("/v1/chat/completions") | ||||||||||||||||||||||||||||||||||||||
| async def chat_completions(request_data: dict): | ||||||||||||||||||||||||||||||||||||||
| try: | ||||||||||||||||||||||||||||||||||||||
| print("\n" + "=" * 80) | ||||||||||||||||||||||||||||||||||||||
| print(f"[{time.strftime('%H:%M:%S')}] 📨 Received Request:") | ||||||||||||||||||||||||||||||||||||||
| print(json.dumps(request_data, indent=2, ensure_ascii=False)) | ||||||||||||||||||||||||||||||||||||||
| print("=" * 80) | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| messages = request_data.get("messages", []) | ||||||||||||||||||||||||||||||||||||||
| model = request_data.get("model", "camel-model") | ||||||||||||||||||||||||||||||||||||||
| stream = request_data.get("stream", False) | ||||||||||||||||||||||||||||||||||||||
| functions = request_data.get("functions") | ||||||||||||||||||||||||||||||||||||||
| tools = request_data.get("tools") | ||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| # Convert OpenAI messages to CAMEL format and record in memory | ||||||||||||||||||||||||||||||||||||||
| current_user_message = None | ||||||||||||||||||||||||||||||||||||||
| for msg in messages: | ||||||||||||||||||||||||||||||||||||||
| msg_role = msg.get("role", "") | ||||||||||||||||||||||||||||||||||||||
| msg_content = msg.get("content", "") | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| if msg_role == "user": | ||||||||||||||||||||||||||||||||||||||
| user_msg = BaseMessage.make_user_message( | ||||||||||||||||||||||||||||||||||||||
| role_name="User", content=msg_content | ||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||
| # Record all but the last user message in memory | ||||||||||||||||||||||||||||||||||||||
| # The last user message will be passed to step() | ||||||||||||||||||||||||||||||||||||||
| if current_user_message is not None: | ||||||||||||||||||||||||||||||||||||||
| self.update_memory( | ||||||||||||||||||||||||||||||||||||||
| current_user_message, OpenAIBackendRole.USER | ||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||
| current_user_message = user_msg | ||||||||||||||||||||||||||||||||||||||
| elif msg_role == "system": | ||||||||||||||||||||||||||||||||||||||
| sys_msg = BaseMessage.make_system_message( | ||||||||||||||||||||||||||||||||||||||
| role_name="System", content=msg_content | ||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||
| self.update_memory(sys_msg, OpenAIBackendRole.SYSTEM) | ||||||||||||||||||||||||||||||||||||||
| self.reset_system_message(msg_content, True) | ||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||
| elif msg_role == "assistant": | ||||||||||||||||||||||||||||||||||||||
| # Record previous assistant messages | ||||||||||||||||||||||||||||||||||||||
| assistant_msg = BaseMessage.make_assistant_message( | ||||||||||||||||||||||||||||||||||||||
| role_name="Assistant", content=msg_content | ||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||
| self.update_memory( | ||||||||||||||||||||||||||||||||||||||
| assistant_msg, OpenAIBackendRole.ASSISTANT | ||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||
| elif msg_role == "tool": | ||||||||||||||||||||||||||||||||||||||
| # Handle tool response messages if needed | ||||||||||||||||||||||||||||||||||||||
| tool_call_id = msg.get("tool_call_id", "") | ||||||||||||||||||||||||||||||||||||||
| tool_msg = FunctionCallingMessage.make_tool_message( | ||||||||||||||||||||||||||||||||||||||
| role_name="Tool", | ||||||||||||||||||||||||||||||||||||||
| content=msg_content, | ||||||||||||||||||||||||||||||||||||||
| tool_call_id=tool_call_id, | ||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||
| self.update_memory(tool_msg, OpenAIBackendRole.TOOL) | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| # Process tools/functions if provided | ||||||||||||||||||||||||||||||||||||||
| if tools or functions: | ||||||||||||||||||||||||||||||||||||||
| tools_to_use = tools if tools else functions | ||||||||||||||||||||||||||||||||||||||
| # Type guard to ensure tools_to_use is not None | ||||||||||||||||||||||||||||||||||||||
| if tools_to_use is not None: | ||||||||||||||||||||||||||||||||||||||
| for tool in tools_to_use: | ||||||||||||||||||||||||||||||||||||||
| self.add_external_tool(tool) | ||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| # Get the response from the agent | ||||||||||||||||||||||||||||||||||||||
| if current_user_message is not None: | ||||||||||||||||||||||||||||||||||||||
| if stream: | ||||||||||||||||||||||||||||||||||||||
| return StreamingResponse( | ||||||||||||||||||||||||||||||||||||||
| _stream_response( | ||||||||||||||||||||||||||||||||||||||
| current_user_message, request_data | ||||||||||||||||||||||||||||||||||||||
| ), | ||||||||||||||||||||||||||||||||||||||
| media_type="text/event-stream", | ||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||
| else: | ||||||||||||||||||||||||||||||||||||||
| agent_response = await self.astep(current_user_message) | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| print(f"agent_response.info {agent_response.info}") | ||||||||||||||||||||||||||||||||||||||
| print(f"agent_response.msgs {agent_response.msgs}") | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| # Convert CAMEL response to OpenAI format | ||||||||||||||||||||||||||||||||||||||
| if not agent_response.msgs: | ||||||||||||||||||||||||||||||||||||||
| # Empty response or error | ||||||||||||||||||||||||||||||||||||||
| content = "No response generated" | ||||||||||||||||||||||||||||||||||||||
| finish_reason = "error" | ||||||||||||||||||||||||||||||||||||||
| else: | ||||||||||||||||||||||||||||||||||||||
| content = agent_response.msgs[0].content | ||||||||||||||||||||||||||||||||||||||
| finish_reason = "stop" | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| # Check for tool calls | ||||||||||||||||||||||||||||||||||||||
| tool_calls_response = None | ||||||||||||||||||||||||||||||||||||||
| external_tool_requests = agent_response.info.get( | ||||||||||||||||||||||||||||||||||||||
| "external_tool_call_requests" | ||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||
| if external_tool_requests: | ||||||||||||||||||||||||||||||||||||||
| tool_calls_response = [] | ||||||||||||||||||||||||||||||||||||||
| for tool_call in external_tool_requests: | ||||||||||||||||||||||||||||||||||||||
| tool_calls_response.append( | ||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||
| "id": ( | ||||||||||||||||||||||||||||||||||||||
| tool_call.tool_call_id | ||||||||||||||||||||||||||||||||||||||
| or f"call_{int(time.time())}" | ||||||||||||||||||||||||||||||||||||||
| ), | ||||||||||||||||||||||||||||||||||||||
| "type": "function", | ||||||||||||||||||||||||||||||||||||||
| "function": { | ||||||||||||||||||||||||||||||||||||||
| "name": tool_call.tool_name, | ||||||||||||||||||||||||||||||||||||||
| "arguments": json.dumps( | ||||||||||||||||||||||||||||||||||||||
| tool_call.args | ||||||||||||||||||||||||||||||||||||||
| ), | ||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||
| finish_reason = "tool_calls" | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| usage = agent_response.info.get("usage") or { | ||||||||||||||||||||||||||||||||||||||
| "prompt_tokens": agent_response.info.get( | ||||||||||||||||||||||||||||||||||||||
| "prompt_tokens", 0 | ||||||||||||||||||||||||||||||||||||||
| ), | ||||||||||||||||||||||||||||||||||||||
| "completion_tokens": agent_response.info.get( | ||||||||||||||||||||||||||||||||||||||
| "completion_tokens", 0 | ||||||||||||||||||||||||||||||||||||||
| ), | ||||||||||||||||||||||||||||||||||||||
| "total_tokens": agent_response.info.get( | ||||||||||||||||||||||||||||||||||||||
| "total_tokens", 0 | ||||||||||||||||||||||||||||||||||||||
| ), | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| response = { | ||||||||||||||||||||||||||||||||||||||
| "id": agent_response.info.get( | ||||||||||||||||||||||||||||||||||||||
| "id", f"chatcmpl-{int(time.time())}" | ||||||||||||||||||||||||||||||||||||||
| ), | ||||||||||||||||||||||||||||||||||||||
| "object": "chat.completion", | ||||||||||||||||||||||||||||||||||||||
| "created": int(time.time()), | ||||||||||||||||||||||||||||||||||||||
| "model": model, | ||||||||||||||||||||||||||||||||||||||
| "choices": [ | ||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||
| "index": 0, | ||||||||||||||||||||||||||||||||||||||
| "message": { | ||||||||||||||||||||||||||||||||||||||
| "role": "assistant", | ||||||||||||||||||||||||||||||||||||||
| "content": ( | ||||||||||||||||||||||||||||||||||||||
| content | ||||||||||||||||||||||||||||||||||||||
| if not tool_calls_response | ||||||||||||||||||||||||||||||||||||||
| else None | ||||||||||||||||||||||||||||||||||||||
| ), | ||||||||||||||||||||||||||||||||||||||
| "tool_calls": tool_calls_response, | ||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||
| "finish_reason": finish_reason, | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
| ], | ||||||||||||||||||||||||||||||||||||||
| "usage": usage, | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| print(f"[{time.strftime('%H:%M:%S')}] 💬 Response:") | ||||||||||||||||||||||||||||||||||||||
| print( | ||||||||||||||||||||||||||||||||||||||
| json.dumps(response, indent=2, ensure_ascii=False) | ||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||
| print("=" * 80 + "\n") | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| return response | ||||||||||||||||||||||||||||||||||||||
| else: | ||||||||||||||||||||||||||||||||||||||
| # No user message provided | ||||||||||||||||||||||||||||||||||||||
| return JSONResponse( | ||||||||||||||||||||||||||||||||||||||
| status_code=400, | ||||||||||||||||||||||||||||||||||||||
| content={"error": "No user message provided"}, | ||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||
| except Exception as e: | ||||||||||||||||||||||||||||||||||||||
| return JSONResponse( | ||||||||||||||||||||||||||||||||||||||
| status_code=500, | ||||||||||||||||||||||||||||||||||||||
| content={"error": f"Internal server error: {e!s}"}, | ||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||
| @@ -5909,9 +5909,10 @@ | ||
| content={"error": "No user message provided"}, | ||
| ) | ||
| except Exception as e: | ||
| logger.error("Exception in /v1/chat/completions endpoint", exc_info=True) | ||
| return JSONResponse( | ||
| status_code=500, | ||
| content={"error": f"Internal server error: {e!s}"}, | ||
| content={"error": "Internal server error"}, | ||
| ) | ||
|
|
||
| async def _stream_response( |
Outdated
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The streaming implementation awaited the full response first, then split it into words and sent them one-by-one with artificial delays. This defeats the purpose of streaming
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we already has
update_system_message, seems no necessary to add this new functionThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
deleted