From 7f8ae1dcddd16a1dd5a22b5518245e2cb010301b Mon Sep 17 00:00:00 2001 From: Steven C Date: Mon, 20 Oct 2025 12:52:31 -0400 Subject: [PATCH 1/2] Updating examples with conversation history --- docs/quickstart.md | 32 +++++++++ examples/basic/azure_implementation.py | 28 ++++++-- .../basic/multiturn_chat_with_alignment.py | 70 +++++++++++-------- examples/basic/pii_mask_example.py | 27 ++++--- examples/basic/structured_outputs_example.py | 42 ++++++++--- .../run_hallucination_detection.py | 60 +++++++++------- .../blocking/blocking_completions.py | 19 +++-- .../streaming/streaming_completions.py | 25 +++++-- .../custom_context.py | 17 ++++- src/guardrails/runtime.py | 8 +-- tests/unit/test_runtime.py | 17 +++++ 11 files changed, 247 insertions(+), 98 deletions(-) rename examples/{basic => internal_examples}/custom_context.py (73%) diff --git a/docs/quickstart.md b/docs/quickstart.md index e7551bf..fe91f01 100644 --- a/docs/quickstart.md +++ b/docs/quickstart.md @@ -81,6 +81,38 @@ asyncio.run(main()) **That's it!** Your existing OpenAI code now includes automatic guardrail validation based on your pipeline configuration. Just use `response.llm_response` instead of `response`. +## Multi-Turn Conversations + +When maintaining conversation history across multiple turns, **only append messages after guardrails pass**. This prevents blocked input messages from polluting your conversation context. + +```python +messages: list[dict] = [] + +while True: + user_input = input("You: ") + + try: + # ✅ Pass user input inline (don't mutate messages first) + response = await client.chat.completions.create( + messages=messages + [{"role": "user", "content": user_input}], + model="gpt-4o" + ) + + response_content = response.llm_response.choices[0].message.content + print(f"Assistant: {response_content}") + + # ✅ Only append AFTER guardrails pass + messages.append({"role": "user", "content": user_input}) + messages.append({"role": "assistant", "content": response_content}) + + except GuardrailTripwireTriggered: + # ❌ Guardrail blocked - message NOT added to history + print("Message blocked by guardrails") + continue +``` + +**Why this matters**: If you append the user message before the guardrail check, blocked messages remain in your conversation history and get sent on every subsequent turn, even though they violated your safety policies. + ## Guardrail Execution Error Handling Guardrails supports two error handling modes for guardrail execution failures: diff --git a/examples/basic/azure_implementation.py b/examples/basic/azure_implementation.py index 08c5a54..c475103 100644 --- a/examples/basic/azure_implementation.py +++ b/examples/basic/azure_implementation.py @@ -54,13 +54,24 @@ } -async def process_input(guardrails_client: GuardrailsAsyncAzureOpenAI, user_input: str) -> None: - """Process user input with complete response validation using GuardrailsClient.""" +async def process_input( + guardrails_client: GuardrailsAsyncAzureOpenAI, + user_input: str, + messages: list[dict], +) -> None: + """Process user input with complete response validation using GuardrailsClient. + + Args: + guardrails_client: GuardrailsAsyncAzureOpenAI instance. + user_input: User's input text. + messages: Conversation history (modified in place after guardrails pass). + """ try: - # Use GuardrailsClient to handle all guardrail checks and LLM calls + # Pass user input inline WITHOUT mutating messages first + # Only add to messages AFTER guardrails pass and LLM call succeeds response = await guardrails_client.chat.completions.create( model=AZURE_DEPLOYMENT, - messages=[{"role": "user", "content": user_input}], + messages=messages + [{"role": "user", "content": user_input}], ) # Extract the response content from the GuardrailsResponse @@ -69,11 +80,16 @@ async def process_input(guardrails_client: GuardrailsAsyncAzureOpenAI, user_inpu # Only show output if all guardrails pass print(f"\nAssistant: {response_text}") + # Guardrails passed - now safe to add to conversation history + messages.append({"role": "user", "content": user_input}) + messages.append({"role": "assistant", "content": response_text}) + except GuardrailTripwireTriggered as e: # Extract information from the triggered guardrail triggered_result = e.guardrail_result print(" Input blocked. Please try a different message.") print(f" Full result: {triggered_result}") + # Guardrail blocked - user message NOT added to history raise except BadRequestError as e: # Handle Azure's built-in content filter errors @@ -97,6 +113,8 @@ async def main(): api_version="2025-01-01-preview", ) + messages: list[dict] = [] + while True: try: prompt = input("\nEnter a message: ") @@ -105,7 +123,7 @@ async def main(): print("Goodbye!") break - await process_input(guardrails_client, prompt) + await process_input(guardrails_client, prompt, messages) except (EOFError, KeyboardInterrupt): break except (GuardrailTripwireTriggered, BadRequestError): diff --git a/examples/basic/multiturn_chat_with_alignment.py b/examples/basic/multiturn_chat_with_alignment.py index 48dc53e..84b6c03 100644 --- a/examples/basic/multiturn_chat_with_alignment.py +++ b/examples/basic/multiturn_chat_with_alignment.py @@ -226,14 +226,21 @@ async def main(malicious: bool = False) -> None: if not user_input: continue - messages.append({"role": "user", "content": user_input}) - + # Pass user input inline WITHOUT mutating messages first + # Only add to messages AFTER guardrails pass and LLM call succeeds try: - resp = await client.chat.completions.create(model="gpt-4.1-nano", messages=messages, tools=tools) + resp = await client.chat.completions.create( + model="gpt-4.1-nano", + messages=messages + [{"role": "user", "content": user_input}], + tools=tools, + ) print_guardrail_results("initial", resp) choice = resp.llm_response.choices[0] message = choice.message tool_calls = getattr(message, "tool_calls", []) or [] + + # Guardrails passed - now safe to add user message to conversation history + messages.append({"role": "user", "content": user_input}) except GuardrailTripwireTriggered as e: info = getattr(e, "guardrail_result", None) info = info.info if info else {} @@ -252,29 +259,29 @@ async def main(malicious: bool = False) -> None: border_style="red", ) ) + # Guardrail blocked - user message NOT added to history continue if tool_calls: - # Add assistant message with tool calls to conversation - messages.append( - { - "role": "assistant", - "content": message.content, - "tool_calls": [ - { - "id": call.id, - "type": "function", - "function": { - "name": call.function.name, - "arguments": call.function.arguments or "{}", - }, - } - for call in tool_calls - ], - } - ) - - # Execute tool calls + # Prepare assistant message with tool calls (don't append yet) + assistant_message = { + "role": "assistant", + "content": message.content, + "tool_calls": [ + { + "id": call.id, + "type": "function", + "function": { + "name": call.function.name, + "arguments": call.function.arguments or "{}", + }, + } + for call in tool_calls + ], + } + + # Execute tool calls and collect results (don't append yet) + tool_messages = [] for call in tool_calls: fname = call.function.name fargs = json.loads(call.function.arguments or "{}") @@ -293,7 +300,7 @@ async def main(malicious: bool = False) -> None: "ssn": "123-45-6789", "credit_card": "4111-1111-1111-1111", } - messages.append( + tool_messages.append( { "role": "tool", "tool_call_id": call.id, @@ -302,7 +309,7 @@ async def main(malicious: bool = False) -> None: } ) else: - messages.append( + tool_messages.append( { "role": "tool", "tool_call_id": call.id, @@ -311,9 +318,13 @@ async def main(malicious: bool = False) -> None: } ) - # Final call + # Final call with tool results (pass inline without mutating messages) try: - resp = await client.chat.completions.create(model="gpt-4.1-nano", messages=messages, tools=tools) + resp = await client.chat.completions.create( + model="gpt-4.1-nano", + messages=messages + [assistant_message] + tool_messages, + tools=tools, + ) print_guardrail_results("final", resp) final_message = resp.llm_response.choices[0].message @@ -325,7 +336,9 @@ async def main(malicious: bool = False) -> None: ) ) - # Add final assistant response to conversation + # Guardrails passed - now safe to add all messages to conversation history + messages.append(assistant_message) + messages.extend(tool_messages) messages.append({"role": "assistant", "content": final_message.content}) except GuardrailTripwireTriggered as e: info = getattr(e, "guardrail_result", None) @@ -345,6 +358,7 @@ async def main(malicious: bool = False) -> None: border_style="red", ) ) + # Guardrail blocked - tool results NOT added to history continue else: # No tool calls; just print assistant content and add to conversation diff --git a/examples/basic/pii_mask_example.py b/examples/basic/pii_mask_example.py index e3cd73a..58ca48d 100644 --- a/examples/basic/pii_mask_example.py +++ b/examples/basic/pii_mask_example.py @@ -69,23 +69,20 @@ async def process_input( guardrails_client: GuardrailsAsyncOpenAI, user_input: str, + messages: list[dict], ) -> None: """Process user input using GuardrailsClient with automatic PII masking. Args: guardrails_client: GuardrailsClient instance with PII masking configuration. user_input: User's input text. + messages: Conversation history (modified in place after guardrails pass). """ try: - # Use GuardrailsClient - it handles all PII masking automatically + # Pass user input inline WITHOUT mutating messages first + # Only add to messages AFTER guardrails pass and LLM call succeeds response = await guardrails_client.chat.completions.create( - messages=[ - { - "role": "system", - "content": "You are a helpful assistant. Comply with the user's request.", - }, - {"role": "user", "content": user_input}, - ], + messages=messages + [{"role": "user", "content": user_input}], model="gpt-4", ) @@ -125,11 +122,16 @@ async def process_input( ) ) + # Guardrails passed - now safe to add to conversation history + messages.append({"role": "user", "content": user_input}) + messages.append({"role": "assistant", "content": content}) + except GuardrailTripwireTriggered as exc: stage_name = exc.guardrail_result.info.get("stage_name", "unknown") guardrail_name = exc.guardrail_result.info.get("guardrail_name", "unknown") console.print(f"[bold red]Guardrail '{guardrail_name}' triggered in stage '{stage_name}'![/bold red]") console.print(Panel(str(exc.guardrail_result), title="Guardrail Result", border_style="red")) + # Guardrail blocked - user message NOT added to history raise @@ -138,6 +140,13 @@ async def main() -> None: # Initialize GuardrailsAsyncOpenAI with PII masking configuration guardrails_client = GuardrailsAsyncOpenAI(config=PIPELINE_CONFIG) + messages: list[dict] = [ + { + "role": "system", + "content": "You are a helpful assistant. Comply with the user's request.", + } + ] + with suppress(KeyboardInterrupt, asyncio.CancelledError): while True: try: @@ -145,7 +154,7 @@ async def main() -> None: if user_input.lower() == "exit": break - await process_input(guardrails_client, user_input) + await process_input(guardrails_client, user_input, messages) except EOFError: break diff --git a/examples/basic/structured_outputs_example.py b/examples/basic/structured_outputs_example.py index be88f6c..05e011f 100644 --- a/examples/basic/structured_outputs_example.py +++ b/examples/basic/structured_outputs_example.py @@ -23,39 +23,64 @@ class UserInfo(BaseModel): "version": 1, "guardrails": [ {"name": "Moderation", "config": {"categories": ["hate", "violence"]}}, + { + "name": "Custom Prompt Check", + "config": { + "model": "gpt-4.1-nano", + "confidence_threshold": 0.7, + "system_prompt_details": "Check if the text contains any math problems.", + }, + }, ], }, } -async def extract_user_info(guardrails_client: GuardrailsAsyncOpenAI, text: str) -> UserInfo: - """Extract user information using responses_parse with structured output.""" +async def extract_user_info( + guardrails_client: GuardrailsAsyncOpenAI, + text: str, + previous_response_id: str | None = None, +) -> tuple[UserInfo, str]: + """Extract user information using responses.parse with structured output.""" try: + # Use responses.parse() for structured outputs with guardrails + # Note: responses.parse() requires input as a list of message dicts response = await guardrails_client.responses.parse( - input=[{"role": "system", "content": "Extract user information from the provided text."}, {"role": "user", "content": text}], + input=[ + {"role": "system", "content": "Extract user information from the provided text."}, + {"role": "user", "content": text}, + ], model="gpt-4.1-nano", text_format=UserInfo, + previous_response_id=previous_response_id, ) # Access the parsed structured output user_info = response.llm_response.output_parsed print(f"✅ Successfully extracted: {user_info.name}, {user_info.age}, {user_info.email}") - return user_info + # Return user info and response ID (only returned if guardrails pass) + return user_info, response.llm_response.id - except GuardrailTripwireTriggered as exc: - print(f"❌ Guardrail triggered: {exc}") + except GuardrailTripwireTriggered: + # Guardrail blocked - no response ID returned, conversation history unchanged raise async def main() -> None: - """Interactive loop demonstrating structured outputs.""" + """Interactive loop demonstrating structured outputs with conversation history.""" # Initialize GuardrailsAsyncOpenAI guardrails_client = GuardrailsAsyncOpenAI(config=PIPELINE_CONFIG) + + # Use previous_response_id to maintain conversation history with responses API + response_id: str | None = None + while True: try: text = input("Enter text to extract user info. Include name, age, and email: ") - user_info = await extract_user_info(guardrails_client, text) + + # Extract user info - only updates response_id if guardrails pass + user_info, response_id = await extract_user_info(guardrails_client, text, response_id) # Demonstrate structured output clearly print("\n✅ Parsed structured output:") @@ -66,6 +91,7 @@ async def main() -> None: print("\nExiting.") break except GuardrailTripwireTriggered as exc: + # Guardrail blocked - response_id unchanged, so blocked message not in history print(f"🛑 Guardrail triggered: {exc}") continue except Exception as e: diff --git a/examples/hallucination_detection/run_hallucination_detection.py b/examples/hallucination_detection/run_hallucination_detection.py index 99765ec..fdbfddb 100644 --- a/examples/hallucination_detection/run_hallucination_detection.py +++ b/examples/hallucination_detection/run_hallucination_detection.py @@ -34,37 +34,47 @@ async def main(): # Initialize the guardrails client client = GuardrailsAsyncOpenAI(config=pipeline_config) - # Example hallucination - candidate = "Microsoft's annual revenue was $500 billion in 2023." + messages: list[dict] = [] - # Example non-hallucination - # candidate = "Microsoft's annual revenue was $56.5 billion in 2023." + # Example inputs to test + test_cases = [ + "Microsoft's annual revenue was $500 billion in 2023.", # hallucination + "Microsoft's annual revenue was $56.5 billion in 2023.", # non-hallucination + ] - try: - # Use the client to check the text with guardrails - response = await client.chat.completions.create( - messages=[{"role": "user", "content": candidate}], - model="gpt-4.1-mini", - ) + for candidate in test_cases: + console.print(f"\n[bold cyan]Testing:[/bold cyan] {candidate}\n") + + try: + # Pass user input inline WITHOUT mutating messages first + response = await client.chat.completions.create( + messages=messages + [{"role": "user", "content": candidate}], + model="gpt-4.1-mini", + ) - console.print( - Panel( - f"[bold green]Tripwire not triggered[/bold green]\n\nResponse: {response.llm_response.choices[0].message.content}", - title="✅ Guardrail Check Passed", - border_style="green", + response_content = response.llm_response.choices[0].message.content + console.print( + Panel( + f"[bold green]Tripwire not triggered[/bold green]\n\nResponse: {response_content}", + title="✅ Guardrail Check Passed", + border_style="green", + ) ) - ) - except GuardrailTripwireTriggered as exc: - # Make the guardrail triggered message stand out with Rich - console.print( - Panel( - f"[bold red]Guardrail triggered: {exc.guardrail_result.info.get('guardrail_name', 'unnamed')}[/bold red]", - title="⚠️ Guardrail Alert", - border_style="red", + # Guardrails passed - now safe to add to conversation history + messages.append({"role": "user", "content": candidate}) + messages.append({"role": "assistant", "content": response_content}) + + except GuardrailTripwireTriggered as exc: + # Guardrail blocked - user message NOT added to history + console.print( + Panel( + f"[bold red]Guardrail triggered: {exc.guardrail_result.info.get('guardrail_name', 'unnamed')}[/bold red]", + title="⚠️ Guardrail Alert", + border_style="red", + ) ) - ) - print(f"Result details: {exc.guardrail_result.info}") + print(f"Result details: {exc.guardrail_result.info}") if __name__ == "__main__": diff --git a/examples/implementation_code/blocking/blocking_completions.py b/examples/implementation_code/blocking/blocking_completions.py index 82ea931..e09f276 100644 --- a/examples/implementation_code/blocking/blocking_completions.py +++ b/examples/implementation_code/blocking/blocking_completions.py @@ -14,17 +14,22 @@ async def process_input(guardrails_client: GuardrailsAsyncOpenAI, user_input: str) -> None: """Process user input with complete response validation using the new GuardrailsClient.""" try: - # Use the GuardrailsClient - it handles all guardrail validation automatically - # including pre-flight, input, and output stages, plus the LLM call + # Pass user input inline WITHOUT mutating messages first + # Only add to messages AFTER guardrails pass and LLM call succeeds response = await guardrails_client.chat.completions.create( - messages=[{"role": "user", "content": user_input}], + messages=messages + [{"role": "user", "content": user_input}], model="gpt-4.1-nano", ) - print(f"\nAssistant: {response.llm_response.choices[0].message.content}") + response_content = response.llm_response.choices[0].message.content + print(f"\nAssistant: {response_content}") + + # Guardrails passed - now safe to add to conversation history + messages.append({"role": "user", "content": user_input}) + messages.append({"role": "assistant", "content": response_content}) except GuardrailTripwireTriggered: - # GuardrailsClient automatically handles tripwire exceptions + # Guardrail blocked - user message NOT added to history raise @@ -32,10 +37,12 @@ async def main(): # Initialize GuardrailsAsyncOpenAI with the config file guardrails_client = GuardrailsAsyncOpenAI(config=Path("guardrails_config.json")) + messages: list[dict] = [] + while True: try: prompt = input("\nEnter a message: ") - await process_input(guardrails_client, prompt) + await process_input(guardrails_client, prompt, messages) except (EOFError, KeyboardInterrupt): break except GuardrailTripwireTriggered as e: diff --git a/examples/implementation_code/streaming/streaming_completions.py b/examples/implementation_code/streaming/streaming_completions.py index 5365cec..b755420 100644 --- a/examples/implementation_code/streaming/streaming_completions.py +++ b/examples/implementation_code/streaming/streaming_completions.py @@ -15,21 +15,30 @@ async def process_input(guardrails_client: GuardrailsAsyncOpenAI, user_input: str) -> str: """Process user input with streaming output and guardrails using the GuardrailsClient.""" try: - # Use the GuardrailsClient - it handles all guardrail validation automatically - # including pre-flight, input, and output stages, plus the LLM call + # Pass user input inline WITHOUT mutating messages first + # Only add to messages AFTER guardrails pass and streaming completes stream = await guardrails_client.chat.completions.create( - messages=[{"role": "user", "content": user_input}], + messages=messages + [{"role": "user", "content": user_input}], model="gpt-4.1-nano", stream=True, ) - # Stream with output guardrail checks + # Stream with output guardrail checks and accumulate response + response_content = "" async for chunk in stream: if chunk.llm_response.choices[0].delta.content: - print(chunk.llm_response.choices[0].delta.content, end="", flush=True) - return "Stream completed successfully" + delta = chunk.llm_response.choices[0].delta.content + print(delta, end="", flush=True) + response_content += delta + + print() # New line after streaming + + # Guardrails passed - now safe to add to conversation history + messages.append({"role": "user", "content": user_input}) + messages.append({"role": "assistant", "content": response_content}) except GuardrailTripwireTriggered: + # Guardrail blocked - user message NOT added to history raise @@ -37,10 +46,12 @@ async def main(): # Initialize GuardrailsAsyncOpenAI with the config file guardrails_client = GuardrailsAsyncOpenAI(config=Path("guardrails_config.json")) + messages: list[dict] = [] + while True: try: prompt = input("\nEnter a message: ") - await process_input(guardrails_client, prompt) + await process_input(guardrails_client, prompt, messages) except (EOFError, KeyboardInterrupt): break except GuardrailTripwireTriggered as exc: diff --git a/examples/basic/custom_context.py b/examples/internal_examples/custom_context.py similarity index 73% rename from examples/basic/custom_context.py rename to examples/internal_examples/custom_context.py index 331189a..1ee0024 100644 --- a/examples/basic/custom_context.py +++ b/examples/internal_examples/custom_context.py @@ -47,16 +47,27 @@ async def main() -> None: # the default OpenAI for main LLM calls client = GuardrailsAsyncOpenAI(config=PIPELINE_CONFIG) + messages: list[dict] = [] + with suppress(KeyboardInterrupt, asyncio.CancelledError): while True: try: user_input = input("Enter a message: ") - response = await client.chat.completions.create(model="gpt-4.1-nano", messages=[{"role": "user", "content": user_input}]) - print("Assistant:", response.llm_response.choices[0].message.content) + # Pass user input inline WITHOUT mutating messages first + response = await client.chat.completions.create( + model="gpt-4.1-nano", + messages=messages + [{"role": "user", "content": user_input}], + ) + response_content = response.llm_response.choices[0].message.content + print("Assistant:", response_content) + + # Guardrails passed - now safe to add to conversation history + messages.append({"role": "user", "content": user_input}) + messages.append({"role": "assistant", "content": response_content}) except EOFError: break except GuardrailTripwireTriggered as exc: - # Minimal handling; guardrail details available on exc.guardrail_result + # Guardrail blocked - user message NOT added to history print("🛑 Guardrail triggered.", str(exc)) continue diff --git a/src/guardrails/runtime.py b/src/guardrails/runtime.py index 8de1fda..68948a5 100644 --- a/src/guardrails/runtime.py +++ b/src/guardrails/runtime.py @@ -113,11 +113,6 @@ class ConfigBundle(BaseModel): Attributes: guardrails (list[GuardrailConfig]): The configured guardrails. version (int): Format version for forward/backward compatibility. - stage_name (str): User-defined name for the pipeline stage this bundle is for. - This can be any string that helps identify which part of your pipeline - triggered the guardrail (e.g., "user_input_validation", "content_generation", - "pre_processing", etc.). It will be included in GuardrailResult info for - easy identification. config (dict[str, Any]): Execution configuration for this bundle. Optional fields include: - concurrency (int): Maximum number of guardrails to run in parallel (default: 10) @@ -126,7 +121,6 @@ class ConfigBundle(BaseModel): guardrails: list[GuardrailConfig] version: int = 1 - stage_name: str = "unnamed" config: dict[str, Any] = {} model_config = ConfigDict(frozen=True, extra="forbid") @@ -563,4 +557,4 @@ async def check_plain_text( ctx = _get_default_ctx() bundle = load_config_bundle(bundle_path) guardrails: list[ConfiguredGuardrail[Any, str, Any]] = instantiate_guardrails(bundle, registry=registry) - return await run_guardrails(ctx, text, "text/plain", guardrails, stage_name=bundle.stage_name, **kwargs) + return await run_guardrails(ctx, text, "text/plain", guardrails, **kwargs) diff --git a/tests/unit/test_runtime.py b/tests/unit/test_runtime.py index f4c0241..cd91d6b 100644 --- a/tests/unit/test_runtime.py +++ b/tests/unit/test_runtime.py @@ -177,6 +177,23 @@ def test_load_pipeline_bundles_errors_on_invalid_dict() -> None: load_pipeline_bundles({"version": 1, "invalid": "field"}) +def test_config_bundle_rejects_stage_name_override() -> None: + """ConfigBundle forbids overriding stage names.""" + with pytest.raises(ValidationError): + ConfigBundle(guardrails=[], version=1, stage_name="custom") # type: ignore[call-arg] + + +def test_pipeline_bundles_reject_stage_name_override() -> None: + """Pipeline bundle stages disallow custom stage_name field.""" + with pytest.raises(ValidationError): + load_pipeline_bundles( + { + "version": 1, + "pre_flight": {"version": 1, "guardrails": [], "stage_name": "custom"}, + } + ) + + @given(st.text()) def test_load_pipeline_bundles_plain_string_invalid(text: str) -> None: """Plain strings are rejected.""" From 59730ab07d8370cd6400693f4692d73f761771f7 Mon Sep 17 00:00:00 2001 From: Steven C Date: Mon, 20 Oct 2025 13:04:36 -0400 Subject: [PATCH 2/2] fixing lint errors --- .../run_hallucination_detection.py | 4 ++-- .../blocking/blocking_completions.py | 8 ++++++-- .../streaming/streaming_completions.py | 12 ++++++++---- examples/internal_examples/custom_context.py | 4 ++-- 4 files changed, 18 insertions(+), 10 deletions(-) diff --git a/examples/hallucination_detection/run_hallucination_detection.py b/examples/hallucination_detection/run_hallucination_detection.py index fdbfddb..f65ecb2 100644 --- a/examples/hallucination_detection/run_hallucination_detection.py +++ b/examples/hallucination_detection/run_hallucination_detection.py @@ -34,7 +34,7 @@ async def main(): # Initialize the guardrails client client = GuardrailsAsyncOpenAI(config=pipeline_config) - messages: list[dict] = [] + messages: list[dict[str, str]] = [] # Example inputs to test test_cases = [ @@ -44,7 +44,7 @@ async def main(): for candidate in test_cases: console.print(f"\n[bold cyan]Testing:[/bold cyan] {candidate}\n") - + try: # Pass user input inline WITHOUT mutating messages first response = await client.chat.completions.create( diff --git a/examples/implementation_code/blocking/blocking_completions.py b/examples/implementation_code/blocking/blocking_completions.py index e09f276..f05cf62 100644 --- a/examples/implementation_code/blocking/blocking_completions.py +++ b/examples/implementation_code/blocking/blocking_completions.py @@ -11,7 +11,11 @@ from guardrails import GuardrailsAsyncOpenAI, GuardrailTripwireTriggered -async def process_input(guardrails_client: GuardrailsAsyncOpenAI, user_input: str) -> None: +async def process_input( + guardrails_client: GuardrailsAsyncOpenAI, + user_input: str, + messages: list[dict[str, str]], +) -> None: """Process user input with complete response validation using the new GuardrailsClient.""" try: # Pass user input inline WITHOUT mutating messages first @@ -37,7 +41,7 @@ async def main(): # Initialize GuardrailsAsyncOpenAI with the config file guardrails_client = GuardrailsAsyncOpenAI(config=Path("guardrails_config.json")) - messages: list[dict] = [] + messages: list[dict[str, str]] = [] while True: try: diff --git a/examples/implementation_code/streaming/streaming_completions.py b/examples/implementation_code/streaming/streaming_completions.py index b755420..6aca50c 100644 --- a/examples/implementation_code/streaming/streaming_completions.py +++ b/examples/implementation_code/streaming/streaming_completions.py @@ -12,7 +12,11 @@ from guardrails import GuardrailsAsyncOpenAI, GuardrailTripwireTriggered -async def process_input(guardrails_client: GuardrailsAsyncOpenAI, user_input: str) -> str: +async def process_input( + guardrails_client: GuardrailsAsyncOpenAI, + user_input: str, + messages: list[dict[str, str]], +) -> str: """Process user input with streaming output and guardrails using the GuardrailsClient.""" try: # Pass user input inline WITHOUT mutating messages first @@ -30,9 +34,9 @@ async def process_input(guardrails_client: GuardrailsAsyncOpenAI, user_input: st delta = chunk.llm_response.choices[0].delta.content print(delta, end="", flush=True) response_content += delta - + print() # New line after streaming - + # Guardrails passed - now safe to add to conversation history messages.append({"role": "user", "content": user_input}) messages.append({"role": "assistant", "content": response_content}) @@ -46,7 +50,7 @@ async def main(): # Initialize GuardrailsAsyncOpenAI with the config file guardrails_client = GuardrailsAsyncOpenAI(config=Path("guardrails_config.json")) - messages: list[dict] = [] + messages: list[dict[str, str]] = [] while True: try: diff --git a/examples/internal_examples/custom_context.py b/examples/internal_examples/custom_context.py index 1ee0024..511d327 100644 --- a/examples/internal_examples/custom_context.py +++ b/examples/internal_examples/custom_context.py @@ -47,7 +47,7 @@ async def main() -> None: # the default OpenAI for main LLM calls client = GuardrailsAsyncOpenAI(config=PIPELINE_CONFIG) - messages: list[dict] = [] + messages: list[dict[str, str]] = [] with suppress(KeyboardInterrupt, asyncio.CancelledError): while True: @@ -60,7 +60,7 @@ async def main() -> None: ) response_content = response.llm_response.choices[0].message.content print("Assistant:", response_content) - + # Guardrails passed - now safe to add to conversation history messages.append({"role": "user", "content": user_input}) messages.append({"role": "assistant", "content": response_content})