talkops-opscloud 0.3.0b1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- opscloud/__init__.py +5 -0
- opscloud/__main__.py +6 -0
- opscloud/_constants.py +53 -0
- opscloud/_debug.py +145 -0
- opscloud/_debug_buffer.py +250 -0
- opscloud/_textual_patches.py +367 -0
- opscloud/_version.py +15 -0
- opscloud/agent/__init__.py +27 -0
- opscloud/agent/config.py +168 -0
- opscloud/agent/factory.py +1103 -0
- opscloud/approval_mode.py +44 -0
- opscloud/backend/__init__.py +49 -0
- opscloud/backend/composite.py +89 -0
- opscloud/backend/local.py +183 -0
- opscloud/backend/registry.py +79 -0
- opscloud/backend/sandbox_config.py +107 -0
- opscloud/backend/sandbox_factory.py +432 -0
- opscloud/built_in_skills/remember/SKILL.md +124 -0
- opscloud/built_in_skills/skill-creator/SKILL.md +348 -0
- opscloud/built_in_skills/skill-creator/scripts/init_skill.py +371 -0
- opscloud/built_in_skills/skill-creator/scripts/quick_validate.py +165 -0
- opscloud/cli/__init__.py +1 -0
- opscloud/cli/commands/__init__.py +15 -0
- opscloud/cli/commands/auth.py +45 -0
- opscloud/cli/commands/doctor.py +100 -0
- opscloud/cli/commands/mcp.py +137 -0
- opscloud/cli/commands/threads.py +51 -0
- opscloud/cli/interactive.py +154 -0
- opscloud/cli/main.py +903 -0
- opscloud/cli/non_interactive.py +425 -0
- opscloud/cli/server_manager.py +58 -0
- opscloud/commands/__init__.py +19 -0
- opscloud/commands/_base.py +110 -0
- opscloud/commands/_guards.py +52 -0
- opscloud/commands/_router.py +146 -0
- opscloud/commands/_types.py +36 -0
- opscloud/commands/core/__init__.py +47 -0
- opscloud/commands/core/auth.py +94 -0
- opscloud/commands/core/bug.py +46 -0
- opscloud/commands/core/clear.py +106 -0
- opscloud/commands/core/cloud.py +185 -0
- opscloud/commands/core/compact.py +128 -0
- opscloud/commands/core/config_cmd.py +120 -0
- opscloud/commands/core/context.py +220 -0
- opscloud/commands/core/cost.py +44 -0
- opscloud/commands/core/doctor.py +535 -0
- opscloud/commands/core/effort.py +108 -0
- opscloud/commands/core/exit_cmd.py +41 -0
- opscloud/commands/core/fast.py +148 -0
- opscloud/commands/core/help_cmd.py +94 -0
- opscloud/commands/core/mcp.py +134 -0
- opscloud/commands/core/model.py +109 -0
- opscloud/commands/core/permissions.py +196 -0
- opscloud/commands/core/plugins.py +92 -0
- opscloud/commands/core/pool.py +108 -0
- opscloud/commands/core/resume.py +97 -0
- opscloud/commands/core/skills.py +178 -0
- opscloud/commands/power/__init__.py +51 -0
- opscloud/commands/power/agents.py +98 -0
- opscloud/commands/power/btw.py +85 -0
- opscloud/commands/power/copy.py +97 -0
- opscloud/commands/power/goal.py +548 -0
- opscloud/commands/power/loop.py +295 -0
- opscloud/commands/power/memory.py +287 -0
- opscloud/commands/power/review.py +152 -0
- opscloud/commands/power/rubric.py +287 -0
- opscloud/commands/power/runtime.py +321 -0
- opscloud/commands/power/skill_creator.py +70 -0
- opscloud/commands/power/skill_invoke.py +165 -0
- opscloud/commands/power/tasks.py +209 -0
- opscloud/commands/power/trace.py +117 -0
- opscloud/commands/power/ui_toggles.py +135 -0
- opscloud/commands/power/version.py +52 -0
- opscloud/commands/registry.py +176 -0
- opscloud/config/__init__.py +114 -0
- opscloud/config/adapters/__init__.py +5 -0
- opscloud/config/adapters/postgres.py +776 -0
- opscloud/config/adapters/sqlite.py +769 -0
- opscloud/config/aws.py +248 -0
- opscloud/config/cloud_profiles.py +437 -0
- opscloud/config/env_vars.py +81 -0
- opscloud/config/langsmith.py +319 -0
- opscloud/config/manifest.py +817 -0
- opscloud/config/metadata.py +205 -0
- opscloud/config/paths.py +604 -0
- opscloud/config/plugins.py +130 -0
- opscloud/config/settings.py +834 -0
- opscloud/config/store.py +453 -0
- opscloud/config/store_factory.py +97 -0
- opscloud/config/toml_config.py +562 -0
- opscloud/editor.py +173 -0
- opscloud/exceptions.py +65 -0
- opscloud/file_ops.py +217 -0
- opscloud/hooks/__init__.py +139 -0
- opscloud/hooks/env.py +44 -0
- opscloud/hooks/interrupt.py +121 -0
- opscloud/hooks/models/__init__.py +109 -0
- opscloud/hooks/models/adapters.py +19 -0
- opscloud/hooks/models/config.py +79 -0
- opscloud/hooks/models/domain.py +434 -0
- opscloud/hooks/models/transport.py +37 -0
- opscloud/hooks/tools.py +231 -0
- opscloud/input.py +48 -0
- opscloud/integrations/__init__.py +80 -0
- opscloud/integrations/base.py +153 -0
- opscloud/integrations/content_filter.py +119 -0
- opscloud/integrations/event_bus.py +480 -0
- opscloud/integrations/hooks.py +238 -0
- opscloud/integrations/identity.py +181 -0
- opscloud/integrations/notifications.py +134 -0
- opscloud/integrations/stream_bridge.py +397 -0
- opscloud/integrations/thread_store.py +81 -0
- opscloud/json_types.py +20 -0
- opscloud/mcp/__init__.py +57 -0
- opscloud/mcp/config.py +203 -0
- opscloud/mcp/discovery.py +229 -0
- opscloud/mcp/mcp_info.py +109 -0
- opscloud/mcp/middleware.py +102 -0
- opscloud/mcp/preload.py +505 -0
- opscloud/mcp/raw_config.py +149 -0
- opscloud/mcp/semantic_profiler.py +305 -0
- opscloud/mcp/session_manager.py +727 -0
- opscloud/mcp/trust.py +105 -0
- opscloud/memory/__init__.py +27 -0
- opscloud/memory/branch.py +61 -0
- opscloud/memory/guard.py +19 -0
- opscloud/memory/onboarding.py +120 -0
- opscloud/memory/registry.py +134 -0
- opscloud/memory/store.py +223 -0
- opscloud/middleware/__init__.py +106 -0
- opscloud/middleware/_repository_bounds.py +459 -0
- opscloud/middleware/ask_user.py +152 -0
- opscloud/middleware/auto_mode.py +60 -0
- opscloud/middleware/auto_mode_hitl.py +2434 -0
- opscloud/middleware/compaction.py +566 -0
- opscloud/middleware/configurable_model.py +233 -0
- opscloud/middleware/cost_tracking.py +1021 -0
- opscloud/middleware/glm_stall_recovery.py +109 -0
- opscloud/middleware/goal_criteria.py +1234 -0
- opscloud/middleware/goal_state_notice.py +712 -0
- opscloud/middleware/goal_tools.py +407 -0
- opscloud/middleware/headless_mcp_guard.py +88 -0
- opscloud/middleware/jev_model_router.py +1018 -0
- opscloud/middleware/local_context.py +460 -0
- opscloud/middleware/mcp_context.py +151 -0
- opscloud/middleware/mcp_middleware.py +146 -0
- opscloud/middleware/memory_guard.py +393 -0
- opscloud/middleware/model_retry.py +205 -0
- opscloud/middleware/registry.py +137 -0
- opscloud/middleware/reliable_rubric.py +709 -0
- opscloud/middleware/resume_state.py +91 -0
- opscloud/middleware/server_hooks.py +1189 -0
- opscloud/middleware/shell_allow_list.py +90 -0
- opscloud/middleware/skills.py +382 -0
- opscloud/middleware/subagent_artifacts.py +84 -0
- opscloud/middleware/subagent_telemetry.py +261 -0
- opscloud/middleware/subagents.py +243 -0
- opscloud/middleware/tool_filter.py +384 -0
- opscloud/middleware/unified_system_message.py +62 -0
- opscloud/model/__init__.py +37 -0
- opscloud/model/config.py +2992 -0
- opscloud/model/factory.py +477 -0
- opscloud/model/pool.py +459 -0
- opscloud/model/reasoning.py +282 -0
- opscloud/offload.py +173 -0
- opscloud/output.py +64 -0
- opscloud/plugins/__init__.py +189 -0
- opscloud/plugins/_json.py +22 -0
- opscloud/plugins/adapters/__init__.py +13 -0
- opscloud/plugins/adapters/agents.py +112 -0
- opscloud/plugins/adapters/commands.py +190 -0
- opscloud/plugins/adapters/mcp.py +363 -0
- opscloud/plugins/adapters/skills.py +132 -0
- opscloud/plugins/commands_cli.py +541 -0
- opscloud/plugins/discovery.py +512 -0
- opscloud/plugins/manifest.py +335 -0
- opscloud/plugins/marketplace.py +893 -0
- opscloud/plugins/models.py +334 -0
- opscloud/plugins/project_plugins.py +317 -0
- opscloud/plugins/protocol.py +31 -0
- opscloud/plugins/store.py +905 -0
- opscloud/plugins/substitution.py +122 -0
- opscloud/project_utils.py +145 -0
- opscloud/prompts/__init__.py +37 -0
- opscloud/prompts/system.py +483 -0
- opscloud/prompts/templates/system_prompt.md +166 -0
- opscloud/prompts/types.py +54 -0
- opscloud/rubrics/__init__.py +42 -0
- opscloud/rubrics/evaluator.py +404 -0
- opscloud/rubrics/evidence_extractor.py +229 -0
- opscloud/rubrics/generator.py +239 -0
- opscloud/rubrics/jev_compiler.py +175 -0
- opscloud/rubrics/jev_grader.py +297 -0
- opscloud/rubrics/middleware.py +8 -0
- opscloud/schema/__init__.py +107 -0
- opscloud/schema/interrupts.py +981 -0
- opscloud/security/__init__.py +91 -0
- opscloud/security/approval_mode.py +528 -0
- opscloud/security/approval_mode_source.py +230 -0
- opscloud/security/cli_ast_evaluator.py +552 -0
- opscloud/security/jev_classifier.py +651 -0
- opscloud/security/shell_safety.py +340 -0
- opscloud/security/unicode_security.py +439 -0
- opscloud/security/url_validation.py +160 -0
- opscloud/server/__init__.py +13 -0
- opscloud/server/_server_config.py +158 -0
- opscloud/server/server.py +242 -0
- opscloud/server/server_graph.py +93 -0
- opscloud/skills/__init__.py +91 -0
- opscloud/skills/commands.py +1099 -0
- opscloud/skills/invocation.py +200 -0
- opscloud/skills/load.py +388 -0
- opscloud/skills/loader.py +23 -0
- opscloud/skills/merge.py +65 -0
- opscloud/skills/registry.py +263 -0
- opscloud/skills/sources.py +174 -0
- opscloud/skills/trust.py +417 -0
- opscloud/state/__init__.py +142 -0
- opscloud/state/base.py +154 -0
- opscloud/state/cloud_context.py +262 -0
- opscloud/state/goal_channels.py +124 -0
- opscloud/state/goal_state_limits.py +175 -0
- opscloud/state/resume_state.py +58 -0
- opscloud/state/service_context.py +281 -0
- opscloud/state/session.py +957 -0
- opscloud/state/state_migration.py +86 -0
- opscloud/subagents/__init__.py +30 -0
- opscloud/subagents/loader.py +331 -0
- opscloud/subagents/subagents_parser.py +117 -0
- opscloud/subagents/types.py +25 -0
- opscloud/tools/__init__.py +36 -0
- opscloud/tools/catalog.py +37 -0
- opscloud/tools/display.py +24 -0
- opscloud/tools/fetch_url.py +176 -0
- opscloud/tools/goal_tools.py +423 -0
- opscloud/tools/registry.py +105 -0
- opscloud/tools/thread.py +32 -0
- opscloud/tools/web_search.py +215 -0
- opscloud/ui/__init__.py +119 -0
- opscloud/ui/app.py +5400 -0
- opscloud/ui/app.tcss +479 -0
- opscloud/ui/clipboard.py +147 -0
- opscloud/ui/command_registry.py +482 -0
- opscloud/ui/message_store.py +152 -0
- opscloud/ui/permission_store.py +360 -0
- opscloud/ui/preferences.py +113 -0
- opscloud/ui/prompt_manager.py +96 -0
- opscloud/ui/remote_client.py +505 -0
- opscloud/ui/textual_adapter.py +1269 -0
- opscloud/ui/theme.py +728 -0
- opscloud/ui/ui_help.py +415 -0
- opscloud/ui/widgets/__init__.py +383 -0
- opscloud/ui/widgets/_ask_user_types.py +81 -0
- opscloud/ui/widgets/_copy_spans.py +52 -0
- opscloud/ui/widgets/_inline_prompt.py +290 -0
- opscloud/ui/widgets/_js_eval_display.py +139 -0
- opscloud/ui/widgets/_links.py +175 -0
- opscloud/ui/widgets/_tool_stream.py +308 -0
- opscloud/ui/widgets/agent_selector.py +297 -0
- opscloud/ui/widgets/approval.py +741 -0
- opscloud/ui/widgets/ask_user.py +539 -0
- opscloud/ui/widgets/auth.py +695 -0
- opscloud/ui/widgets/auth_manager.py +214 -0
- opscloud/ui/widgets/auto_mode_notice.py +146 -0
- opscloud/ui/widgets/autocomplete.py +709 -0
- opscloud/ui/widgets/chat_input.py +805 -0
- opscloud/ui/widgets/cloud_selector.py +528 -0
- opscloud/ui/widgets/config_manager.py +652 -0
- opscloud/ui/widgets/debug_console.py +1269 -0
- opscloud/ui/widgets/devops_renderers.py +184 -0
- opscloud/ui/widgets/diff.py +237 -0
- opscloud/ui/widgets/effort_selector.py +166 -0
- opscloud/ui/widgets/goal_review.py +612 -0
- opscloud/ui/widgets/goal_status.py +57 -0
- opscloud/ui/widgets/infra_panel.py +141 -0
- opscloud/ui/widgets/install_confirm.py +114 -0
- opscloud/ui/widgets/loading.py +170 -0
- opscloud/ui/widgets/mcp_viewer.py +1204 -0
- opscloud/ui/widgets/messages.py +4461 -0
- opscloud/ui/widgets/model_selector.py +863 -0
- opscloud/ui/widgets/notification_center.py +108 -0
- opscloud/ui/widgets/notification_settings.py +119 -0
- opscloud/ui/widgets/operation_card.py +106 -0
- opscloud/ui/widgets/permissions_manager.py +667 -0
- opscloud/ui/widgets/plugin_manager.py +1212 -0
- opscloud/ui/widgets/pool_selector.py +376 -0
- opscloud/ui/widgets/preamble.py +53 -0
- opscloud/ui/widgets/skills_viewer.py +432 -0
- opscloud/ui/widgets/status.py +1037 -0
- opscloud/ui/widgets/subagent_panel.py +927 -0
- opscloud/ui/widgets/theme_selector.py +232 -0
- opscloud/ui/widgets/thread_selector.py +358 -0
- opscloud/ui/widgets/toast.py +31 -0
- opscloud/ui/widgets/tool_display.py +325 -0
- opscloud/ui/widgets/tool_renderers.py +234 -0
- opscloud/ui/widgets/tool_widgets.py +278 -0
- opscloud/ui/widgets/welcome.py +421 -0
- opscloud/ui/widgets/welcome_popup.py +102 -0
- opscloud/utils/__init__.py +24 -0
- opscloud/utils/cost_estimation.py +169 -0
- opscloud/utils/git.py +390 -0
- opscloud/utils/logger.py +571 -0
- opscloud/utils/session_stats.py +115 -0
- opscloud/utils/startup_error.py +37 -0
- talkops_opscloud-0.3.0b1.dist-info/METADATA +449 -0
- talkops_opscloud-0.3.0b1.dist-info/RECORD +309 -0
- talkops_opscloud-0.3.0b1.dist-info/WHEEL +4 -0
- talkops_opscloud-0.3.0b1.dist-info/entry_points.txt +2 -0
- talkops_opscloud-0.3.0b1.dist-info/licenses/LICENSE +175 -0
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"""Types and enums for OpsCloud prompts module."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from enum import StrEnum
|
|
6
|
+
from typing import Literal
|
|
7
|
+
|
|
8
|
+
from opscloud.utils.logger import get_logger
|
|
9
|
+
|
|
10
|
+
logger = get_logger(__name__)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class CloudProvider(StrEnum):
|
|
14
|
+
"""Supported cloud providers for OpsCloud operations and prompts."""
|
|
15
|
+
|
|
16
|
+
AWS = "aws"
|
|
17
|
+
AZURE = "azure"
|
|
18
|
+
GCP = "gcp"
|
|
19
|
+
MULTI = "multi"
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
FsToolName = Literal[
|
|
23
|
+
"read_file",
|
|
24
|
+
"write_file",
|
|
25
|
+
"edit_file",
|
|
26
|
+
"list_dir",
|
|
27
|
+
"grep_search",
|
|
28
|
+
"find_by_name",
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def normalize_cloud_provider(val: str | CloudProvider | None) -> CloudProvider:
|
|
33
|
+
"""Normalize a cloud provider string or enum into a validated CloudProvider.
|
|
34
|
+
|
|
35
|
+
Defaults to `CloudProvider.AWS` when unspecified.
|
|
36
|
+
"""
|
|
37
|
+
if val is None:
|
|
38
|
+
return CloudProvider.AWS
|
|
39
|
+
|
|
40
|
+
if isinstance(val, CloudProvider):
|
|
41
|
+
return val
|
|
42
|
+
|
|
43
|
+
normalized = val.strip().lower()
|
|
44
|
+
if normalized in {"aws", "amazon", "amazon_aws"}:
|
|
45
|
+
return CloudProvider.AWS
|
|
46
|
+
if normalized in {"azure", "az", "microsoft", "azure_cloud"}:
|
|
47
|
+
return CloudProvider.AZURE
|
|
48
|
+
if normalized in {"gcp", "google", "google_cloud"}:
|
|
49
|
+
return CloudProvider.GCP
|
|
50
|
+
if normalized in {"multi", "multi_cloud", "multicloud", "hybrid", "cross"}:
|
|
51
|
+
return CloudProvider.MULTI
|
|
52
|
+
|
|
53
|
+
logger.warning("Unrecognized cloud provider '%s'; defaulting to AWS.", val)
|
|
54
|
+
return CloudProvider.AWS
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"""Rubrics and evaluation package for opscloud."""
|
|
2
|
+
|
|
3
|
+
from opscloud.rubrics.evaluator import (
|
|
4
|
+
_RUBRIC_GRADER_SYSTEM_PROMPT,
|
|
5
|
+
_create_rubric_grader_tools,
|
|
6
|
+
_rubric_grader_system_prompt,
|
|
7
|
+
create_rubric_grader_agent,
|
|
8
|
+
evaluate_rubric,
|
|
9
|
+
)
|
|
10
|
+
from opscloud.rubrics.evidence_extractor import extract_grading_evidence
|
|
11
|
+
from opscloud.rubrics.generator import (
|
|
12
|
+
GOAL_AMENDMENT_SYSTEM_PROMPT,
|
|
13
|
+
GOAL_RUBRIC_SYSTEM_PROMPT,
|
|
14
|
+
generate_rubric,
|
|
15
|
+
_goal_amendment_human_prompt,
|
|
16
|
+
_goal_rubric_human_prompt,
|
|
17
|
+
)
|
|
18
|
+
from opscloud.rubrics.jev_compiler import CompiledRubric, JevCriteriaCompiler
|
|
19
|
+
from opscloud.rubrics.jev_grader import (
|
|
20
|
+
BLOCKER_DISPATCH,
|
|
21
|
+
JevHybridRubricGrader,
|
|
22
|
+
build_criteria_evaluations,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
__all__ = [
|
|
26
|
+
"BLOCKER_DISPATCH",
|
|
27
|
+
"CompiledRubric",
|
|
28
|
+
"GOAL_AMENDMENT_SYSTEM_PROMPT",
|
|
29
|
+
"GOAL_RUBRIC_SYSTEM_PROMPT",
|
|
30
|
+
"JevCriteriaCompiler",
|
|
31
|
+
"JevHybridRubricGrader",
|
|
32
|
+
"_RUBRIC_GRADER_SYSTEM_PROMPT",
|
|
33
|
+
"_create_rubric_grader_tools",
|
|
34
|
+
"_goal_amendment_human_prompt",
|
|
35
|
+
"_goal_rubric_human_prompt",
|
|
36
|
+
"_rubric_grader_system_prompt",
|
|
37
|
+
"build_criteria_evaluations",
|
|
38
|
+
"create_rubric_grader_agent",
|
|
39
|
+
"evaluate_rubric",
|
|
40
|
+
"extract_grading_evidence",
|
|
41
|
+
"generate_rubric",
|
|
42
|
+
]
|
|
@@ -0,0 +1,404 @@
|
|
|
1
|
+
"""Grader tools, prompts, and standalone evaluator for rubric verification.
|
|
2
|
+
|
|
3
|
+
Provides:
|
|
4
|
+
- Isolated read-only tools for inspecting both offloaded outputs and repository files.
|
|
5
|
+
- Dynamic grader system prompt builder with domain-aware guidance for AWS Cloud & DevOps.
|
|
6
|
+
- ``create_rubric_grader_agent`` to instantiate the Rubric Grader as an individual agent.
|
|
7
|
+
- ``evaluate_rubric`` for direct, programmatic quality grading.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import inspect
|
|
13
|
+
from pathlib import Path, PurePosixPath
|
|
14
|
+
from typing import TYPE_CHECKING, Any, Callable, Sequence
|
|
15
|
+
|
|
16
|
+
from deepagents.middleware import GRADER_SYSTEM_PROMPT
|
|
17
|
+
from deepagents.middleware.rubric import (
|
|
18
|
+
RUBRIC_GRADER_MESSAGE_SOURCE,
|
|
19
|
+
CriterionEval,
|
|
20
|
+
CriterionFail,
|
|
21
|
+
CriterionPass,
|
|
22
|
+
GraderResponse,
|
|
23
|
+
GraderVerdict,
|
|
24
|
+
)
|
|
25
|
+
from langchain_core.messages import HumanMessage
|
|
26
|
+
from langchain_core.tools import BaseTool, StructuredTool, tool
|
|
27
|
+
|
|
28
|
+
from opscloud.middleware._repository_bounds import (
|
|
29
|
+
RepositoryBounds,
|
|
30
|
+
)
|
|
31
|
+
from opscloud.middleware.reliable_rubric import RubricGraderState
|
|
32
|
+
from opscloud.offload import _artifacts_root
|
|
33
|
+
from opscloud.utils.logger import get_logger
|
|
34
|
+
|
|
35
|
+
if TYPE_CHECKING:
|
|
36
|
+
from deepagents.backends.protocol import BackendProtocol
|
|
37
|
+
from langchain.agents.middleware.types import AgentMiddleware
|
|
38
|
+
from langchain_core.language_models import BaseChatModel
|
|
39
|
+
|
|
40
|
+
logger = get_logger(__name__)
|
|
41
|
+
|
|
42
|
+
_RUBRIC_GRADER_READ_FILE_PREFIX = "/large_tool_results/"
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
# ---------------------------------------------------------------------------
|
|
46
|
+
# Grader System Prompt
|
|
47
|
+
# ---------------------------------------------------------------------------
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _rubric_grader_system_prompt(
|
|
51
|
+
read_file_prefix: str = _RUBRIC_GRADER_READ_FILE_PREFIX,
|
|
52
|
+
repository_root: str | None = None,
|
|
53
|
+
context_tool_names: Sequence[str] | None = None,
|
|
54
|
+
repository_tool_names: Sequence[str] | None = None,
|
|
55
|
+
) -> str:
|
|
56
|
+
"""Build the comprehensive grader system prompt for AWS Cloud & DevOps verification."""
|
|
57
|
+
prompt = (
|
|
58
|
+
f"{GRADER_SYSTEM_PROMPT}\n\n"
|
|
59
|
+
"# OpsCloud — Independent Rubric Verification Grader\n\n"
|
|
60
|
+
"You are the independent Rubric Grader for OpsCloud. Your sole responsibility is to evaluate "
|
|
61
|
+
"whether the agent's work satisfies the accepted acceptance criteria without self-evaluation bias.\n\n"
|
|
62
|
+
"## Evidence Inspection Rules\n"
|
|
63
|
+
f"1. When the conversation transcript references an offloaded tool output saved under `{read_file_prefix}` "
|
|
64
|
+
"or an artifacts directory, use the `read_file` tool to inspect the referenced evidence before concluding "
|
|
65
|
+
"that a criterion lacks support.\n"
|
|
66
|
+
"2. Only mark a criterion satisfied if direct, verifiable evidence in the transcript, command outputs, or "
|
|
67
|
+
"on-disk files demonstrates it. Never assume or extrapolate completion.\n"
|
|
68
|
+
"3. If any criterion is not satisfied or is partially completed, provide clear, actionable feedback in the "
|
|
69
|
+
"deficiency report explaining exactly what is missing or failed."
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
if repository_root and repository_tool_names:
|
|
73
|
+
tools_desc = ", ".join(f"`{t}`" for t in repository_tool_names)
|
|
74
|
+
prompt += (
|
|
75
|
+
f"\n\n## Working Directory Inspection\n"
|
|
76
|
+
f"You have read-only tools ({tools_desc}) scoped to the working directory rooted at `{repository_root}`.\n"
|
|
77
|
+
"The transcript can truncate long command outputs; prefer directly inspecting modified or created files "
|
|
78
|
+
"(e.g. Terraform configs, CloudFormation templates, Dockerfiles, Kubernetes manifests, scripts, or tests) "
|
|
79
|
+
"to verify the criteria on disk. All repository inspection is read-only and confined to the repository root."
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
if context_tool_names:
|
|
83
|
+
c_names = ", ".join(f"`{t}`" for t in context_tool_names)
|
|
84
|
+
prompt += (
|
|
85
|
+
f"\n\n## External Context Tools\n"
|
|
86
|
+
f"Read-only external context tools are available: {c_names}. Use them when a criterion requires "
|
|
87
|
+
"verifying live cloud resources or external states."
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
return prompt
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
# Backward-compatible static system prompt
|
|
94
|
+
_RUBRIC_GRADER_SYSTEM_PROMPT = _rubric_grader_system_prompt()
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
# ---------------------------------------------------------------------------
|
|
98
|
+
# Path Safety Validation
|
|
99
|
+
# ---------------------------------------------------------------------------
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def _validate_rubric_grader_read_path(
|
|
103
|
+
file_path: str,
|
|
104
|
+
allowed_prefix: str | None = None,
|
|
105
|
+
repository_root: str | None = None,
|
|
106
|
+
) -> str | None:
|
|
107
|
+
"""Validate that a requested file path does not escape allowed boundaries."""
|
|
108
|
+
normalized = file_path.replace("\\", "/")
|
|
109
|
+
parts = PurePosixPath(normalized).parts
|
|
110
|
+
if ".." in parts or "~" in parts:
|
|
111
|
+
return "Invalid path: traversal characters forbidden."
|
|
112
|
+
|
|
113
|
+
# Check allowed prefix (e.g. /large_tool_results/ or offload directory)
|
|
114
|
+
if allowed_prefix and normalized.startswith(allowed_prefix):
|
|
115
|
+
return None
|
|
116
|
+
if normalized.startswith(_RUBRIC_GRADER_READ_FILE_PREFIX):
|
|
117
|
+
return None
|
|
118
|
+
|
|
119
|
+
# Check offload artifact root
|
|
120
|
+
try:
|
|
121
|
+
art_root = _artifacts_root().root.replace("\\", "/")
|
|
122
|
+
if normalized.startswith(art_root):
|
|
123
|
+
return None
|
|
124
|
+
except Exception:
|
|
125
|
+
pass
|
|
126
|
+
|
|
127
|
+
# Check repository root
|
|
128
|
+
if repository_root:
|
|
129
|
+
repo_norm = str(Path(repository_root).resolve()).replace("\\", "/")
|
|
130
|
+
try:
|
|
131
|
+
target_norm = str(Path(file_path).resolve()).replace("\\", "/")
|
|
132
|
+
if target_norm.startswith(repo_norm):
|
|
133
|
+
return None
|
|
134
|
+
except Exception:
|
|
135
|
+
pass
|
|
136
|
+
|
|
137
|
+
return f"Rubric grader cannot read path outside authorized directories: {file_path}"
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
# ---------------------------------------------------------------------------
|
|
141
|
+
# Grader Tools Creation
|
|
142
|
+
# ---------------------------------------------------------------------------
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def _create_rubric_grader_tools(
|
|
146
|
+
backend: Any = None,
|
|
147
|
+
*,
|
|
148
|
+
repository_backend: BackendProtocol | None = None,
|
|
149
|
+
repository_root: str | None = None,
|
|
150
|
+
context_tools: Sequence[BaseTool | Callable[..., Any]] = (),
|
|
151
|
+
) -> list[BaseTool]:
|
|
152
|
+
"""Create isolated read-only tools for rubric grading."""
|
|
153
|
+
if repository_root:
|
|
154
|
+
effective_root = repository_root
|
|
155
|
+
elif repository_backend is not None:
|
|
156
|
+
effective_root = "/"
|
|
157
|
+
else:
|
|
158
|
+
try:
|
|
159
|
+
from opscloud.project_utils import get_server_project_context
|
|
160
|
+
|
|
161
|
+
ctx = get_server_project_context()
|
|
162
|
+
effective_root = str(ctx.user_cwd) if ctx is not None else "/"
|
|
163
|
+
except Exception:
|
|
164
|
+
effective_root = "/"
|
|
165
|
+
read_file_prefix = _RUBRIC_GRADER_READ_FILE_PREFIX
|
|
166
|
+
try:
|
|
167
|
+
read_file_prefix = _artifacts_root().root
|
|
168
|
+
except Exception:
|
|
169
|
+
pass
|
|
170
|
+
|
|
171
|
+
bounds: RepositoryBounds | None = None
|
|
172
|
+
if repository_backend is not None and repository_root is not None:
|
|
173
|
+
try:
|
|
174
|
+
bounds = RepositoryBounds(repository_backend, root=repository_root)
|
|
175
|
+
except Exception as e:
|
|
176
|
+
logger.warning("Could not initialize RepositoryBounds for grader: %s", e)
|
|
177
|
+
|
|
178
|
+
@tool
|
|
179
|
+
def read_file(file_path: str, offset: int = 0, limit: int = 120) -> str:
|
|
180
|
+
"""Read an offloaded tool result or a working-directory file.
|
|
181
|
+
|
|
182
|
+
Args:
|
|
183
|
+
file_path: Absolute or workspace path to the file.
|
|
184
|
+
offset: Starting line index (0-based).
|
|
185
|
+
limit: Maximum number of lines to return.
|
|
186
|
+
"""
|
|
187
|
+
if err := _validate_rubric_grader_read_path(file_path, read_file_prefix, effective_root):
|
|
188
|
+
return err
|
|
189
|
+
|
|
190
|
+
try:
|
|
191
|
+
p = Path(file_path).expanduser().resolve()
|
|
192
|
+
if not p.exists():
|
|
193
|
+
return f"File {file_path} not found."
|
|
194
|
+
if not p.is_file():
|
|
195
|
+
return f"Path {file_path} is not a file."
|
|
196
|
+
content = p.read_text(encoding="utf-8", errors="replace")
|
|
197
|
+
lines = content.splitlines()
|
|
198
|
+
sliced = lines[offset : offset + limit]
|
|
199
|
+
return "\n".join(sliced)
|
|
200
|
+
except Exception as e:
|
|
201
|
+
return f"Error reading file {file_path}: {e}"
|
|
202
|
+
|
|
203
|
+
tools: list[BaseTool] = [read_file]
|
|
204
|
+
|
|
205
|
+
root_path = Path(effective_root).resolve()
|
|
206
|
+
|
|
207
|
+
def _resolve_target(rel_or_abs: str) -> Path:
|
|
208
|
+
p = Path(rel_or_abs)
|
|
209
|
+
if p.is_absolute():
|
|
210
|
+
return p.resolve()
|
|
211
|
+
return (root_path / p).resolve()
|
|
212
|
+
|
|
213
|
+
def _is_safe(target_p: Path) -> bool:
|
|
214
|
+
if bounds:
|
|
215
|
+
return bounds.safe_path(str(target_p))
|
|
216
|
+
try:
|
|
217
|
+
target_p.relative_to(root_path)
|
|
218
|
+
return True
|
|
219
|
+
except ValueError:
|
|
220
|
+
return False
|
|
221
|
+
|
|
222
|
+
# Add repository inspection tools if repository_backend or local workspace is bound
|
|
223
|
+
@tool
|
|
224
|
+
def ls(path: str = ".") -> str:
|
|
225
|
+
"""List files in the working directory (read-only)."""
|
|
226
|
+
target = _resolve_target(path)
|
|
227
|
+
if not _is_safe(target):
|
|
228
|
+
return "Path is outside the repository bounds."
|
|
229
|
+
if not target.exists():
|
|
230
|
+
return f"Directory does not exist: {path}"
|
|
231
|
+
try:
|
|
232
|
+
entries = sorted(target.iterdir(), key=lambda p: (not p.is_dir(), p.name))
|
|
233
|
+
return "\n".join(f"{'[DIR] ' if e.is_dir() else '[FILE] '}{e.name}" for e in entries[:200])
|
|
234
|
+
except Exception as e:
|
|
235
|
+
return f"Error listing directory: {e}"
|
|
236
|
+
|
|
237
|
+
@tool
|
|
238
|
+
def glob(pattern: str, path: str = ".") -> str:
|
|
239
|
+
"""Search for files matching pattern in the repository (read-only)."""
|
|
240
|
+
target = _resolve_target(path)
|
|
241
|
+
if not _is_safe(target):
|
|
242
|
+
return "Path is outside the repository bounds."
|
|
243
|
+
if not target.exists():
|
|
244
|
+
return f"Path does not exist: {path}"
|
|
245
|
+
try:
|
|
246
|
+
matches = [str(p.relative_to(target)) for p in list(target.glob(pattern))[:200]]
|
|
247
|
+
return "\n".join(matches) if matches else "No matching files found."
|
|
248
|
+
except Exception as e:
|
|
249
|
+
return f"Error searching glob: {e}"
|
|
250
|
+
|
|
251
|
+
@tool
|
|
252
|
+
def grep(pattern: str, path: str = ".") -> str:
|
|
253
|
+
"""Search for text pattern in repository files (read-only)."""
|
|
254
|
+
target = _resolve_target(path)
|
|
255
|
+
if not _is_safe(target):
|
|
256
|
+
return "Path is outside the repository bounds."
|
|
257
|
+
if not target.exists():
|
|
258
|
+
return f"Path does not exist: {path}"
|
|
259
|
+
try:
|
|
260
|
+
results: list[str] = []
|
|
261
|
+
for file_path in target.rglob("*"):
|
|
262
|
+
if file_path.is_file() and not file_path.name.startswith("."):
|
|
263
|
+
try:
|
|
264
|
+
content = file_path.read_text(encoding="utf-8", errors="ignore")
|
|
265
|
+
for idx, line in enumerate(content.splitlines(), start=1):
|
|
266
|
+
if pattern in line:
|
|
267
|
+
rel = file_path.relative_to(target)
|
|
268
|
+
results.append(f"{rel}:{idx}: {line.strip()[:160]}")
|
|
269
|
+
if len(results) >= 100:
|
|
270
|
+
break
|
|
271
|
+
except Exception:
|
|
272
|
+
continue
|
|
273
|
+
if len(results) >= 100:
|
|
274
|
+
break
|
|
275
|
+
return "\n".join(results) if results else "Pattern not found."
|
|
276
|
+
except Exception as e:
|
|
277
|
+
return f"Error running grep: {e}"
|
|
278
|
+
|
|
279
|
+
tools.extend([ls, glob, grep])
|
|
280
|
+
|
|
281
|
+
# Append any valid context tools
|
|
282
|
+
for candidate in context_tools:
|
|
283
|
+
if isinstance(candidate, BaseTool):
|
|
284
|
+
tools.append(candidate)
|
|
285
|
+
elif inspect.iscoroutinefunction(candidate):
|
|
286
|
+
tools.append(StructuredTool.from_function(coroutine=candidate))
|
|
287
|
+
elif callable(candidate):
|
|
288
|
+
tools.append(StructuredTool.from_function(func=candidate))
|
|
289
|
+
|
|
290
|
+
return tools
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
# ---------------------------------------------------------------------------
|
|
294
|
+
# Standalone Rubric Grader Agent Factory & Evaluator
|
|
295
|
+
# ---------------------------------------------------------------------------
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
def create_rubric_grader_agent(
|
|
299
|
+
*,
|
|
300
|
+
model: str | BaseChatModel | None = None,
|
|
301
|
+
tools: Sequence[BaseTool] | None = None,
|
|
302
|
+
middleware: Sequence[AgentMiddleware[Any, Any]] | None = None,
|
|
303
|
+
context_schema: type[Any] | None = None,
|
|
304
|
+
system_prompt: str | None = None,
|
|
305
|
+
) -> Any:
|
|
306
|
+
"""Create an isolated Rubric Grader Agent as an individual agent graph."""
|
|
307
|
+
from deepagents._models import resolve_model
|
|
308
|
+
from langchain.agents import create_agent
|
|
309
|
+
|
|
310
|
+
from opscloud.model.factory import create_model as opscloud_create_model
|
|
311
|
+
|
|
312
|
+
if model is None:
|
|
313
|
+
model_res = opscloud_create_model()
|
|
314
|
+
chat_model = model_res.model
|
|
315
|
+
elif isinstance(model, str):
|
|
316
|
+
chat_model = resolve_model(model)
|
|
317
|
+
else:
|
|
318
|
+
chat_model = model
|
|
319
|
+
|
|
320
|
+
grader_tools = list(tools) if tools is not None else _create_rubric_grader_tools()
|
|
321
|
+
prompt = system_prompt or _RUBRIC_GRADER_SYSTEM_PROMPT
|
|
322
|
+
|
|
323
|
+
return create_agent(
|
|
324
|
+
model=chat_model,
|
|
325
|
+
system_prompt=prompt,
|
|
326
|
+
tools=grader_tools,
|
|
327
|
+
middleware=list(middleware or ()),
|
|
328
|
+
name=RUBRIC_GRADER_MESSAGE_SOURCE,
|
|
329
|
+
response_format=GraderResponse,
|
|
330
|
+
state_schema=RubricGraderState,
|
|
331
|
+
context_schema=context_schema,
|
|
332
|
+
)
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
def evaluate_rubric(
|
|
336
|
+
criteria: str,
|
|
337
|
+
evidence: str | Sequence[Any],
|
|
338
|
+
*,
|
|
339
|
+
model: str | BaseChatModel | None = None,
|
|
340
|
+
repository_root: str | None = None,
|
|
341
|
+
use_jev: bool | None = None,
|
|
342
|
+
) -> GraderResponse:
|
|
343
|
+
"""Directly evaluate criteria against evidence using the Rubric Grader Agent or Jev."""
|
|
344
|
+
from langchain_core.messages import BaseMessage
|
|
345
|
+
|
|
346
|
+
if use_jev is True:
|
|
347
|
+
from opscloud.rubrics.jev_compiler import JevCriteriaCompiler
|
|
348
|
+
from opscloud.rubrics.jev_grader import JevHybridRubricGrader
|
|
349
|
+
|
|
350
|
+
jev_grader = JevHybridRubricGrader()
|
|
351
|
+
if jev_grader.is_available():
|
|
352
|
+
compiled = JevCriteriaCompiler.compile("Direct Evaluation", criteria)
|
|
353
|
+
return jev_grader.grade(
|
|
354
|
+
compiled,
|
|
355
|
+
evidence=evidence,
|
|
356
|
+
fallback_llm_fn=lambda failing: evaluate_rubric(
|
|
357
|
+
criteria, evidence, model=model, repository_root=repository_root, use_jev=False
|
|
358
|
+
),
|
|
359
|
+
)
|
|
360
|
+
|
|
361
|
+
tools = _create_rubric_grader_tools(repository_root=repository_root)
|
|
362
|
+
grader = create_rubric_grader_agent(model=model, tools=tools)
|
|
363
|
+
|
|
364
|
+
if isinstance(evidence, str):
|
|
365
|
+
content = f"<criteria>\n{criteria}\n</criteria>\n\n<transcript>\n{evidence}\n</transcript>"
|
|
366
|
+
elif isinstance(evidence, Sequence):
|
|
367
|
+
rendered = []
|
|
368
|
+
for msg in evidence:
|
|
369
|
+
if isinstance(msg, BaseMessage):
|
|
370
|
+
rendered.append(f"{msg.type}: {msg.content}")
|
|
371
|
+
else:
|
|
372
|
+
rendered.append(str(msg))
|
|
373
|
+
joined = "\n\n".join(rendered)
|
|
374
|
+
content = f"<criteria>\n{criteria}\n</criteria>\n\n<transcript>\n{joined}\n</transcript>"
|
|
375
|
+
else:
|
|
376
|
+
content = f"<criteria>\n{criteria}\n</criteria>\n\n<evidence>\n{evidence}\n</evidence>"
|
|
377
|
+
|
|
378
|
+
input_payload = {
|
|
379
|
+
"messages": [HumanMessage(content=content)],
|
|
380
|
+
"rubric_grading_operation_id": "direct_eval",
|
|
381
|
+
}
|
|
382
|
+
result = grader.invoke(input_payload)
|
|
383
|
+
if isinstance(result, GraderResponse):
|
|
384
|
+
return result
|
|
385
|
+
if isinstance(result, dict) and "structured_response" in result:
|
|
386
|
+
structured = result["structured_response"]
|
|
387
|
+
if isinstance(structured, GraderResponse):
|
|
388
|
+
return structured
|
|
389
|
+
if isinstance(structured, dict):
|
|
390
|
+
return GraderResponse.model_validate(structured)
|
|
391
|
+
|
|
392
|
+
verdict: GraderVerdict = "satisfied" if "satisfied" in str(result).lower() else "needs_revision"
|
|
393
|
+
explanation = str(result)
|
|
394
|
+
criteria_evals: list[CriterionEval]
|
|
395
|
+
if verdict == "satisfied":
|
|
396
|
+
criteria_evals = [CriterionPass(name="criteria_evaluation", passed=True)]
|
|
397
|
+
else:
|
|
398
|
+
criteria_evals = [CriterionFail(name="criteria_evaluation", passed=False, gap=explanation)]
|
|
399
|
+
|
|
400
|
+
return GraderResponse(
|
|
401
|
+
result=verdict,
|
|
402
|
+
explanation=explanation,
|
|
403
|
+
criteria=criteria_evals,
|
|
404
|
+
)
|