Files
AI/참고/guardrails-main/guardrails/integrations/llama_index/guardrails_query_engine.py
2026-05-12 19:40:31 +09:00

129 lines
4.9 KiB
Python

from typing import Any, Optional, Dict, List, cast
from guardrails import Guard
from guardrails.errors import ValidationError
from guardrails.classes.validation_outcome import ValidationOutcome
try:
import llama_index # noqa: F401
from llama_index.core.query_engine import BaseQueryEngine
from llama_index.core.schema import QueryBundle
from llama_index.core.callbacks import CallbackManager
from llama_index.core.base.response.schema import (
RESPONSE_TYPE,
Response,
StreamingResponse,
AsyncStreamingResponse,
PydanticResponse,
)
from llama_index.core.prompts.mixin import PromptMixinType
except ImportError:
raise ImportError(
"llama_index is not installed. Please install it with "
"`pip install llama-index` to use GuardrailsEngine."
)
class GuardrailsQueryEngine(BaseQueryEngine):
_engine_response: RESPONSE_TYPE
def __init__(
self,
engine: BaseQueryEngine,
guard: Guard,
guard_kwargs: Optional[Dict[str, Any]] = None,
callback_manager: Optional["CallbackManager"] = None,
):
self._engine = engine
self._guard = guard
self._guard_kwargs = guard_kwargs or {}
super().__init__(callback_manager)
@property
def guard(self) -> Guard:
return self._guard
def engine_api(self, *, messages: List[Dict[str, str]], **kwargs) -> str:
query = messages[0]["content"]
response = self._engine.query(query)
self._engine_response = response
return str(response)
def _query(self, query_bundle: "QueryBundle") -> RESPONSE_TYPE:
if not isinstance(self._engine, BaseQueryEngine):
raise ValueError(
"Cannot perform query with a ChatEngine. Use chat() method instead."
)
if isinstance(query_bundle, str):
query_bundle = QueryBundle(query_bundle)
try:
messages = [
{
"role": "user",
"content": query_bundle.query_str,
}
]
validated_output = self.guard(
llm_api=self.engine_api,
messages=messages,
**self._guard_kwargs,
)
validated_output = cast(ValidationOutcome, validated_output)
# self._engine_response = cast(RESPONSE_TYPE, self._engine_response)
if not validated_output.validation_passed:
raise ValidationError(f"Validation failed: {validated_output.error}")
self._update_response_metadata(validated_output)
if validated_output.validation_passed:
if isinstance(self._engine_response, Response):
self._engine_response.response = validated_output.validated_output
elif isinstance(
self._engine_response, (StreamingResponse, AsyncStreamingResponse)
):
self._engine_response.response_txt = (
validated_output.validated_output
)
elif isinstance(self._engine_response, PydanticResponse):
if self._engine_response.response:
import json
json_str = (
validated_output.validated_output
if isinstance(validated_output.validated_output, str)
else json.dumps(validated_output.validated_output)
)
self._engine_response.response = self._engine_response.response.__class__.model_validate_json( # noqa: E501
json_str
)
else:
raise ValueError("Unsupported response type")
except ValidationError as e:
raise ValidationError(f"Validation failed: {str(e)}")
except Exception as e:
raise RuntimeError(f"An error occurred during query processing: {str(e)}")
return self._engine_response
def _update_response_metadata(self, validated_output):
if self._engine_response is None:
return
self._engine_response = cast(RESPONSE_TYPE, self._engine_response)
metadata_update = {
"validation_passed": validated_output.validation_passed,
"validated_output": validated_output.validated_output,
"error": validated_output.error,
"raw_llm_output": validated_output.raw_llm_output,
}
if self._engine_response.metadata is None:
self._engine_response.metadata = {}
self._engine_response.metadata.update(metadata_update)
async def _aquery(self, query_bundle: "QueryBundle") -> "RESPONSE_TYPE":
"""Async version of _query."""
return self._query(query_bundle)
def _get_prompt_modules(self) -> "PromptMixinType":
"""Get prompt modules."""
return self._engine._get_prompt_modules()