-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathworkflow.py
More file actions
122 lines (102 loc) · 4.54 KB
/
Copy pathworkflow.py
File metadata and controls
122 lines (102 loc) · 4.54 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
from collections.abc import Callable
from datetime import UTC, datetime
from typing import Literal, Protocol
from azure.ai.projects import AIProjectClient
from .config import Settings
from .identity import build_credential
from .models import Actor, PromptEnvelope, RunEvent, TraceContext, WorkflowResult
from .telemetry import LoopStage, current_traceparent, start_loop_span
RunStatus = Literal["queued", "running", "succeeded", "failed", "cancelled"]
class WorkflowAgentService(Protocol):
"""Application boundary implemented by local and Foundry Next Gen workflows."""
def run(self, envelope: PromptEnvelope) -> str: ...
class WorkflowAgentServiceError(RuntimeError):
"""Stable application error raised when an external workflow backend fails."""
class LocalWorkflowAgentService:
def run(self, envelope: PromptEnvelope) -> str:
return (
f"Reference workflow accepted '{envelope.intent}' "
f"with {len(envelope.constraints)} constraints."
)
class FoundryWorkflowAgentService:
"""Invoke a Foundry Next Gen agent reference through the project Responses client."""
def __init__(self, settings: Settings) -> None:
endpoint = settings.foundry_project_endpoint
agent_name = settings.foundry_agent_name
if not settings.foundry_ready or endpoint is None or agent_name is None:
raise ValueError("Foundry backend requires a valid project endpoint and agent name")
self._agent_name = agent_name
self._client = AIProjectClient(
endpoint=endpoint,
credential=build_credential(settings.environment),
).get_openai_client()
def run(self, envelope: PromptEnvelope) -> str:
with start_loop_span(LoopStage.FOUNDRY, envelope.correlationId):
try:
response = self._client.responses.create(
input=envelope.prompt,
extra_body={
"agent_reference": {
"name": self._agent_name,
"type": "agent_reference",
}
},
)
except Exception as error:
raise WorkflowAgentServiceError("Foundry workflow invocation failed") from error
return response.output_text
def build_workflow_agent_service(settings: Settings) -> WorkflowAgentService:
if settings.workflow_backend == "foundry":
return FoundryWorkflowAgentService(settings)
return LocalWorkflowAgentService()
class WorkflowOrchestrator:
def __init__(
self,
service: WorkflowAgentService,
repository: str,
clock: Callable[[], datetime] = lambda: datetime.now(UTC),
) -> None:
self._service = service
self._repository = repository
self._clock = clock
def execute(self, envelope: PromptEnvelope) -> WorkflowResult:
with start_loop_span(LoopStage.WORKER, envelope.correlationId) as span:
span.set_attribute("cas.correlation_id", envelope.correlationId)
events = [self._event(envelope, 0, "workflow.started", "running", "Workflow started.")]
try:
output = self._service.run(envelope)
events.append(
self._event(
envelope, 1, "workflow.completed", "succeeded", "Workflow completed."
)
)
return WorkflowResult(runId=envelope.runId, output=output, events=events)
except Exception:
events.append(
self._event(envelope, 1, "workflow.failed", "failed", "Workflow failed.")
)
raise
def _event(
self,
envelope: PromptEnvelope,
sequence: int,
event_type: str,
status: RunStatus,
message: str,
) -> RunEvent:
trace_context = {"traceparent": current_traceparent(envelope.traceContext.traceparent)}
if envelope.traceContext.tracestate is not None:
trace_context["tracestate"] = envelope.traceContext.tracestate
return RunEvent(
correlationId=envelope.correlationId,
promptId=envelope.promptId,
runId=envelope.runId,
repo=self._repository,
actor=Actor(id="cas-reference-workflow", type="workflow"),
timestamp=self._clock(),
traceContext=TraceContext.model_validate(trace_context),
eventType=event_type,
sequence=sequence,
status=status,
message=message,
)