-
Notifications
You must be signed in to change notification settings - Fork 22
Fix proxy #87
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
Merged
Fix proxy #87
Changes from all commits
Commits
Show all changes
9 commits
Select commit
Hold shift + click to select a range
9bee81d
update init
Yunnglin a42bd71
update proxy
Yunnglin 1816c9d
update proxy
Yunnglin 42ae691
update proxy
Yunnglin 599cb3e
update
Yunnglin 074cb91
Merge branch 'main' into fix/proxy
Yunnglin 894785f
update
Yunnglin 37d21ca
Merge branch 'main' into fix/proxy
Yunnglin 7634154
update
Yunnglin File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,15 +1,7 @@ | ||
| # Copyright (c) ModelScope Contributors. All rights reserved. | ||
| from .launcher import ServerLauncher, launch_server | ||
| from .twinkle.model import build_model_app | ||
| from .twinkle.processor import build_processor_app | ||
| from .twinkle.sampler import build_sampler_app | ||
| from .twinkle.server import build_server_app | ||
|
|
||
| __all__ = [ | ||
| 'build_model_app', | ||
| 'build_processor_app', | ||
| 'build_sampler_app', | ||
| 'build_server_app', | ||
| 'ServerLauncher', | ||
| 'launch_server', | ||
| ] | ||
Yunnglin marked this conversation as resolved.
Show resolved
Hide resolved
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,15 +1,18 @@ | ||
| # Copyright (c) ModelScope Contributors. All rights reserved. | ||
| import sys | ||
| from typing import TYPE_CHECKING | ||
|
|
||
| from ..utils import wrap_builder_with_device_group_env | ||
| from .model import build_model_app as _build_model_app | ||
| from .sampler import build_sampler_app as _build_sampler_app | ||
| from .server import build_server_app | ||
| from twinkle.utils.import_utils import _LazyModule | ||
|
|
||
| build_model_app = wrap_builder_with_device_group_env(_build_model_app) | ||
| build_sampler_app = wrap_builder_with_device_group_env(_build_sampler_app) | ||
| _import_structure = { | ||
| 'model': ['build_model_app'], | ||
| 'sampler': ['build_sampler_app'], | ||
| 'server': ['build_server_app'], | ||
| } | ||
|
|
||
| __all__ = [ | ||
| 'build_model_app', | ||
| 'build_sampler_app', | ||
| 'build_server_app', | ||
| ] | ||
| if TYPE_CHECKING: | ||
| from .model import build_model_app | ||
| from .sampler import build_sampler_app | ||
| from .server import build_server_app | ||
| else: | ||
| sys.modules[__name__] = _LazyModule(__name__, __file__, _import_structure, module_spec=__spec__) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,184 @@ | ||
| # Copyright (c) ModelScope Contributors. All rights reserved. | ||
| """ | ||
| Proxy utilities for forwarding requests to internal services. | ||
|
|
||
| This module provides HTTP proxy functionality to route requests from the Tinker server | ||
| to appropriate model or sampler services based on base_model routing. | ||
| """ | ||
|
|
||
| from __future__ import annotations | ||
|
|
||
| import httpx | ||
| import os | ||
| from fastapi import Request, Response | ||
| from typing import Any | ||
|
|
||
| from twinkle.utils.logger import get_logger | ||
|
|
||
| logger = get_logger() | ||
|
|
||
|
|
||
| class ServiceProxy: | ||
| """HTTP proxy for routing requests to internal model and sampler services. | ||
|
|
||
| This proxy handles: | ||
| 1. URL construction using localhost to avoid external routing loops | ||
| 2. Header forwarding with appropriate cleanup | ||
| 3. Debug logging for troubleshooting | ||
| 4. Error handling and response forwarding | ||
| """ | ||
|
|
||
| def __init__( | ||
| self, | ||
| http_options: dict[str, Any] | None = None, | ||
| route_prefix: str = '/api/v1', | ||
| ): | ||
| """Initialize the service proxy. | ||
|
|
||
| Args: | ||
| http_options: HTTP server options (host, port) for internal routing | ||
| route_prefix: URL prefix for routing (default: '/api/v1') | ||
| """ | ||
| self.http_options = http_options or {} | ||
| self.route_prefix = route_prefix | ||
| # Disable proxy for internal requests to avoid routing through external proxies | ||
| self.client = httpx.AsyncClient(timeout=None, trust_env=False) | ||
|
|
||
| def _build_target_url(self, service_type: str, base_model: str, endpoint: str) -> str: | ||
| """Build the target URL for internal service routing. | ||
|
|
||
| Constructs URLs using localhost to avoid extra external hops. | ||
| When requests come from www.modelscope.com/twinkle, we proxy to | ||
| localhost:port directly instead of back to modelscope.com. | ||
|
|
||
| Args: | ||
| service_type: Either 'model' or 'sampler' | ||
| base_model: The base model name for routing | ||
| endpoint: The target endpoint name | ||
|
|
||
| Returns: | ||
| Complete target URL for the internal service | ||
| """ | ||
| prefix = self.route_prefix.rstrip('/') if self.route_prefix else '' | ||
| host = self.http_options.get('host', 'localhost') | ||
| port = self.http_options.get('port', 8000) | ||
|
|
||
| # Use localhost for internal routing | ||
| if host == '0.0.0.0': | ||
| host = 'localhost' | ||
|
|
||
| base_url = f'http://{host}:{port}' | ||
Yunnglin marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| return f'{base_url}{prefix}/{service_type}/{base_model}/{endpoint}' | ||
|
|
||
| def _prepare_headers(self, request_headers) -> dict[str, str]: | ||
| """Prepare headers for proxying by removing problematic headers. | ||
|
|
||
| Args: | ||
| request_headers: Original request headers (case-insensitive from FastAPI) | ||
|
|
||
| Returns: | ||
| Cleaned headers safe for proxying | ||
| """ | ||
| logger.debug('prepare_headers request_headers=%s', request_headers) | ||
Yunnglin marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| # Convert to dict while preserving case-insensitive lookups for special headers | ||
| headers = dict(request_headers) | ||
| # Remove headers that should not be forwarded | ||
| headers.pop('host', None) | ||
| headers.pop('content-length', None) | ||
| # Add serve_multiplexed_model_id for sticky sessions if present | ||
| # Use case-insensitive lookup from original request_headers | ||
| request_id = request_headers.get('X-Ray-Serve-Request-Id') | ||
| if request_id is not None: | ||
| headers['serve_multiplexed_model_id'] = request_id | ||
| return headers | ||
|
|
||
| async def proxy_request( | ||
| self, | ||
| request: Request, | ||
| endpoint: str, | ||
| base_model: str, | ||
| service_type: str, | ||
| ) -> Response: | ||
| """Generic proxy method to forward requests to model or sampler services. | ||
|
|
||
| This method consolidates the common proxy logic for both model and sampler endpoints. | ||
|
|
||
| Args: | ||
| request: The incoming FastAPI request | ||
| endpoint: The target endpoint name (e.g., 'create_model', 'asample') | ||
| base_model: The base model name for routing | ||
| service_type: Either 'model' or 'sampler' to determine the target service | ||
|
|
||
| Returns: | ||
| Proxied response from the target service | ||
| """ | ||
| body_bytes = await request.body() | ||
| target_url = self._build_target_url(service_type, base_model, endpoint) | ||
| # Pass original request.headers (case-insensitive) instead of dict conversion | ||
| headers = self._prepare_headers(request.headers) | ||
|
|
||
| try: | ||
| # Debug logging for troubleshooting proxy issues | ||
| logger.debug( | ||
| 'proxy_request service=%s endpoint=%s target_url=%s request_id=%s', | ||
| service_type, | ||
| endpoint, | ||
| target_url, | ||
| headers.get('serve_multiplexed_model_id'), | ||
| ) | ||
|
|
||
| # Forward the request to the target service | ||
| response = await self.client.request( | ||
| method=request.method, | ||
| url=target_url, | ||
| content=body_bytes, | ||
| headers=headers, | ||
| params=request.query_params, | ||
| ) | ||
|
|
||
| # Debug logging for response | ||
| logger.debug( | ||
| 'proxy_response status=%s body_preview=%s', | ||
| response.status_code, | ||
| response.text[:200], | ||
| ) | ||
|
|
||
| return Response( | ||
| content=response.content, | ||
| status_code=response.status_code, | ||
| headers=dict(response.headers), | ||
| media_type=response.headers.get('content-type'), | ||
| ) | ||
| except Exception as e: | ||
| logger.error('Proxy error: %s', str(e), exc_info=True) | ||
| return Response(content=f'Proxy Error: {str(e)}', status_code=502) | ||
tastelikefeet marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| async def proxy_to_model(self, request: Request, endpoint: str, base_model: str) -> Response: | ||
| """Proxy request to model endpoint. | ||
|
|
||
| Routes the request to the appropriate model deployment based on base_model. | ||
|
|
||
| Args: | ||
| request: The incoming FastAPI request | ||
| endpoint: The target endpoint name (e.g., 'create_model', 'forward') | ||
| base_model: The base model name for routing | ||
|
|
||
| Returns: | ||
| Proxied response from the model service | ||
| """ | ||
| return await self.proxy_request(request, endpoint, base_model, 'model') | ||
|
|
||
| async def proxy_to_sampler(self, request: Request, endpoint: str, base_model: str) -> Response: | ||
| """Proxy request to sampler endpoint. | ||
|
|
||
| Routes the request to the appropriate sampler deployment based on base_model. | ||
|
|
||
| Args: | ||
| request: The incoming FastAPI request | ||
| endpoint: The target endpoint name (e.g., 'asample') | ||
| base_model: The base model name for routing | ||
|
|
||
| Returns: | ||
| Proxied response from the sampler service | ||
| """ | ||
| return await self.proxy_request(request, endpoint, base_model, 'sampler') | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.