Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,45 @@
# Changelog

## [0.1.9] - 2026-06-24

### Summary

Enterprise-level answer governance was added directly to `PromptConfig`.

### Changes

1. **Response language control** (`prompt_orchestrator/config/prompt_config.py`)
- Added `response_language: ru | en | auto`
- Default is `ru` for Russian-first enterprise flows

2. **Strict output contract** (`prompt_orchestrator/config/prompt_config.py`)
- Added `OutputContractConfig`
- Added `output_contract` field in `PromptConfig`
- Default mode is `json_markdown`
- Default strictness is enabled (`strict=True`)

3. **Tool-calling policy** (`prompt_orchestrator/config/prompt_config.py`)
- Added `ToolCallingPolicyConfig`
- Added `tool_calling_policy` field in `PromptConfig`
- Default policy allows tool usage (`mode="allow"`)
- Added per-policy limits and guard flags (`max_calls`, JSON args/result acknowledgement)

4. **Prompt rendering updates**
- `render_static_header()` now includes:
- Response Language
- Output Contract
- Tool Calling Policy

5. **Public API exports**
- Added exports in:
- `prompt_orchestrator/config/__init__.py`
- `prompt_orchestrator/__init__.py`
for `OutputContractConfig` and `ToolCallingPolicyConfig`

6. **Docs and tests**
- README updated with enterprise configuration examples
- Core tests extended to validate defaults and rendering

## [0.1.0] - 2026-05-29

### Summary
Expand Down
40 changes: 40 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,46 @@ Use it as a panel/query blueprint in SigNoz to create a dashboard for prompt bui
- `ModuleConfig`: full module config in one object
- `ConfigStore`: mutable config holder (`get`, `set_config`, `as_dict`)

### Enterprise Prompt Controls

`PromptConfig` now includes enterprise-level controls for answer governance:

- `response_language`: `ru | en | auto` (default: `ru`)
- `output_contract`: strict output contract configuration
- default `mode="json_markdown"`
- default `strict=True`
- default schema hint for enterprise review payload
- `tool_calling_policy`: tool usage policy for downstream tool-aware agents
- `mode`: `allow | deny | allowlist` (default: `allow`)
- `max_calls` (default: `8`)
- `allowed_tools` for allowlist mode
- JSON args/result acknowledgement guard flags

Example:

```python
from prompt_orchestrator import PromptConfig, OutputContractConfig, ToolCallingPolicyConfig

cfg = PromptConfig(
system_prompt="Вы корпоративный ассистент.",
role="Ревьюер документации",
task="Сформируйте проверяемый ответ на русском.",
constraints=["Не придумывайте факты", "Всегда давайте ссылки/цитаты"],
output_format="Markdown",
examples=[],
response_language="ru",
output_contract=OutputContractConfig(
mode="json_markdown",
strict=True,
schema_hint='{"summary":"str","findings":["str"],"risks":["str"],"actions":["str"],"citations":["str"]}',
),
tool_calling_policy=ToolCallingPolicyConfig(
mode="allow",
max_calls=8,
),
)
```

## Safety Engine

The safety layer is configured from [prompt_orchestrator/safety/threats.json](prompt_orchestrator/safety/threats.json). The catalog is grouped by threat family, and each family has its own weight so the final severity is still computed by the maximum matched threat score.
Expand Down
4 changes: 3 additions & 1 deletion prompt_orchestrator/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from .cache.local_ttl import LocalTTLCacheBackend
from .config.config_store import ConfigStore
from .config.module_config import ModuleConfig
from .config.prompt_config import PromptConfig
from .config.prompt_config import OutputContractConfig, PromptConfig, ToolCallingPolicyConfig
from .config.settings import OrchestratorSettings
from .context.manager import PromptContextManager
from .context.state import DocChunk, Message, PromptContextState
Expand Down Expand Up @@ -35,6 +35,7 @@
"OllamaSummaryClient",
"OpenAIConfig",
"OpenAISummaryClient",
"OutputContractConfig",
"OrchestratedPrompt",
"OrchestratorSettings",
"PromptAnalyzer",
Expand All @@ -50,6 +51,7 @@
"SummaryLLM",
"SummaryLLMConfig",
"TokenCounter",
"ToolCallingPolicyConfig",
"init_telemetry",
"shutdown_telemetry",
]
4 changes: 3 additions & 1 deletion prompt_orchestrator/config/__init__.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
from .config_store import ConfigStore
from .module_config import ModuleConfig
from .prompt_config import PromptConfig
from .prompt_config import OutputContractConfig, PromptConfig, ToolCallingPolicyConfig
from .settings import OrchestratorSettings

__all__ = [
"ConfigStore",
"ModuleConfig",
"OutputContractConfig",
"OrchestratorSettings",
"PromptConfig",
"ToolCallingPolicyConfig",
]
56 changes: 56 additions & 0 deletions prompt_orchestrator/config/prompt_config.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,69 @@
from __future__ import annotations

from typing import Literal

from pydantic import BaseModel, Field


class OutputContractConfig(BaseModel):
mode: Literal["json_markdown", "json", "markdown", "text"] = "json_markdown"
strict: bool = True
schema_hint: str = (
'{"summary": "str", "findings": ["str"], '
'"risks": ["str"], "actions": ["str"], "citations": ["str"]}'
)


class ToolCallingPolicyConfig(BaseModel):
mode: Literal["allow", "deny", "allowlist"] = "allow"
max_calls: int = 8
allowed_tools: list[str] = Field(default_factory=list)
require_json_arguments: bool = True
require_tool_result_ack: bool = True


class PromptConfig(BaseModel):
system_prompt: str
role: str
task: str
constraints: list[str] = Field(default_factory=list)
output_format: str
examples: list[str] = Field(default_factory=list)
response_language: Literal["ru", "en", "auto"] = "ru"
output_contract: OutputContractConfig = Field(default_factory=OutputContractConfig)
tool_calling_policy: ToolCallingPolicyConfig = Field(default_factory=ToolCallingPolicyConfig)

def _render_language_instruction(self) -> str:
if self.response_language == "ru":
return "Russian (ru)"
if self.response_language == "en":
return "English (en)"
return "Auto-detect from user query (auto)"

def _render_output_contract(self) -> str:
strict = "strict" if self.output_contract.strict else "soft"
return (
f"mode={self.output_contract.mode}; "
f"enforcement={strict}; "
f"schema={self.output_contract.schema_hint}"
)

def _render_tool_policy(self) -> str:
allowed = ", ".join(self.tool_calling_policy.allowed_tools) or "Any tool"
return (
f"mode={self.tool_calling_policy.mode}; "
f"max_calls={self.tool_calling_policy.max_calls}; "
f"allowed_tools={allowed}; "
f"json_args={self.tool_calling_policy.require_json_arguments}; "
f"ack_result={self.tool_calling_policy.require_tool_result_ack}"
)

def render_static_header(self, include_header: bool = False) -> str:
constraints = "\n".join(f"- {item}" for item in self.constraints) or "- None"
examples = "\n\n".join(self.examples) or "None"
response_language = self._render_language_instruction()
output_contract = self._render_output_contract()
tool_policy = self._render_tool_policy()
header = "=== STATIC PART (CACHE-FRIENDLY) ===\n" if include_header else ""
return (
f"{header}"
Expand All @@ -25,8 +75,14 @@ def render_static_header(self, include_header: bool = False) -> str:
f"{self.task}\n\n"
"Constraints:\n"
f"{constraints}\n\n"
"Response Language:\n"
f"{response_language}\n\n"
"Output Format:\n"
f"{self.output_format}\n\n"
"Output Contract:\n"
f"{output_contract}\n\n"
"Tool Calling Policy:\n"
f"{tool_policy}\n\n"
"Examples:\n"
f"{examples}"
)
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"

[project]
name = "prompt-orchestrator"
version = "0.1.7"
version = "0.1.9"
description = "Structured prompt orchestration with cache, safety, and analyzer layers"
readme = { file = "README.md", content-type = "text/markdown" }
requires-python = ">=3.10"
Expand Down
46 changes: 45 additions & 1 deletion tests/test_core_behaviors.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@

from prompt_orchestrator import (
LocalTTLCacheBackend,
NoRAGProvider,
OllamaSummaryClient,
OutputContractConfig,
OrchestratorSettings,
PromptConfig,
PromptContextManager,
Expand All @@ -15,6 +15,7 @@
SummaryLLMConfig,
TokenCounter,
SafetyLLMConfig,
ToolCallingPolicyConfig,
)
from prompt_orchestrator.safety import llm as safety_llm_module
from prompt_orchestrator.context.state import DocChunk
Expand Down Expand Up @@ -66,6 +67,49 @@ def _base_config() -> PromptConfig:
)


def test_prompt_config_enterprise_defaults() -> None:
cfg = _base_config()

assert cfg.response_language == "ru"
assert cfg.output_contract.strict is True
assert cfg.output_contract.mode == "json_markdown"
assert cfg.tool_calling_policy.mode == "allow"
assert cfg.tool_calling_policy.max_calls == 8


def test_prompt_config_renders_language_output_and_tool_policy() -> None:
cfg = PromptConfig(
system_prompt="You are a helpful assistant.",
role="Engineer",
task="Answer questions clearly.",
constraints=["No hallucinations"],
output_format="Markdown",
examples=["Q: hi A: hello"],
response_language="ru",
output_contract=OutputContractConfig(
mode="json",
strict=True,
schema_hint='{"answer": "str", "citations": ["str"]}',
),
tool_calling_policy=ToolCallingPolicyConfig(
mode="allowlist",
max_calls=4,
allowed_tools=["retrieve_context", "build_attribution"],
require_json_arguments=True,
require_tool_result_ack=True,
),
)

rendered = cfg.render_static_header(include_header=True)
assert "Response Language:" in rendered
assert "Russian (ru)" in rendered
assert "Output Contract:" in rendered
assert "mode=json; enforcement=strict" in rendered
assert "Tool Calling Policy:" in rendered
assert "mode=allowlist; max_calls=4" in rendered
assert "allowed_tools=retrieve_context, build_attribution" in rendered


def test_local_ttl_cache_expires_items() -> None:
cache = LocalTTLCacheBackend(default_ttl_seconds=1)
cache.set("k", {"v": 1})
Expand Down
Loading