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
4 changes: 4 additions & 0 deletions provider/foundryprovider/agent.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@ type AgentConfig struct {

// DisableStoreOutput disables service-side Responses output storage.
// Use this when local session history providers own conversation state.
// By default, store-disabled runs also request `reasoning.encrypted_content`
// so reasoning items can be replayed statelessly; use
// [openaiprovider.ResponsesIncludeReasoningEncryptedContent] in RunOptions to
// opt out.
DisableStoreOutput bool

// OpenAIOptions configure the OpenAI-compatible per-agent client. Foundry-owned
Expand Down
28 changes: 28 additions & 0 deletions provider/foundryprovider/agent_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (

"github.com/microsoft/agent-framework-go/agent"
"github.com/microsoft/agent-framework-go/provider/foundryprovider"
"github.com/microsoft/agent-framework-go/provider/openaiprovider"
"github.com/openai/openai-go/v3/option"
)

Expand Down Expand Up @@ -84,6 +85,33 @@ func TestNewAgentDisableStoreOutputSetsStoreFalse(t *testing.T) {
}
}

func TestNewAgentRunOptionCanDisableReasoningEncryptedContentInclude(t *testing.T) {
server := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
body := jsonMap(t, mustReadBody(t, r))
if body["store"] != false {
t.Fatalf("store = %#v, want false", body["store"])
}
if _, ok := body["include"]; ok {
t.Fatalf("include = %#v, want omitted", body["include"])
}
writeResponsesOK(w)
}))
defer server.Close()

foundryAgent := newFoundryAgent(t, server, foundryprovider.ModelDeployment("gpt-4o-mini"), foundryprovider.AgentConfig{
DisableStoreOutput: true,
Config: agent.Config{
RunOptions: []agent.Option{
openaiprovider.ResponsesIncludeReasoningEncryptedContent(false),
},
},
})

if _, err := foundryAgent.RunText(t.Context(), "hello").Collect(); err != nil {
t.Fatalf("RunText error = %v", err)
}
}

func TestNewAgentRunsAgainstFoundryAgentEndpoint(t *testing.T) {
server := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/projects/proj/agents/my-agent/endpoint/protocols/openai/responses" {
Expand Down
5 changes: 4 additions & 1 deletion provider/openaiprovider/chat.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,10 @@ type AgentConfig struct {

// DisableStoreOutput is used only by [NewResponsesAgent]. It disables
// service-side output storage and prevents response IDs from being saved into
// agent sessions for later continuation.
// agent sessions for later continuation. While enabled, Responses requests
// also include `reasoning.encrypted_content` by default so reasoning items
// can be replayed statelessly; use [ResponsesIncludeReasoningEncryptedContent]
// to opt out per run.
// It is ignored by [NewChatCompletionsAgent].
DisableStoreOutput bool

Expand Down
17 changes: 16 additions & 1 deletion provider/openaiprovider/responses.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,11 +72,25 @@ func (o responsesNewParamsOpt) MAFValue() any {
return responses.ResponseNewParams(o)
}

type responsesIncludeReasoningEncryptedContentOpt bool

func (o responsesIncludeReasoningEncryptedContentOpt) MAFValue() any {
return bool(o)
}

// ResponsesNewParams allows passing custom parameters to the underlying OpenAI Responses API calls.
func ResponsesNewParams(params responses.ResponseNewParams) agent.Option {
return responsesNewParamsOpt(params)
}

// ResponsesIncludeReasoningEncryptedContent controls whether [NewResponsesAgent]
// automatically requests `reasoning.encrypted_content` while Responses output
// storage is disabled. The default is true so reasoning items can be replayed
// across stateless turns.
func ResponsesIncludeReasoningEncryptedContent(enabled bool) agent.Option {
return responsesIncludeReasoningEncryptedContentOpt(enabled)
}

func (a *responsesClient) formatOf(v any) (agent.ResponseFormat, error) {
return jsonformat.ForType(reflect.TypeOf(v))
}
Expand Down Expand Up @@ -233,7 +247,8 @@ func responsesBuildCompletionParams(config AgentConfig, messages []*message.Mess
if param.IsOmitted(params.Store) {
params.Store = openai.Bool(false)
}
if !slices.Contains(params.Include, responses.ResponseIncludableReasoningEncryptedContent) {
if include, ok := agent.GetOption(opts, ResponsesIncludeReasoningEncryptedContent); (!ok || include) &&
!slices.Contains(params.Include, responses.ResponseIncludableReasoningEncryptedContent) {
params.Include = append(params.Include, responses.ResponseIncludableReasoningEncryptedContent)
}
}
Expand Down
49 changes: 49 additions & 0 deletions provider/openaiprovider/responses_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5979,6 +5979,55 @@ func TestResponsesNewParamsStoreFalseDoesNotDuplicateReasoningInclude(t *testing
}
}

func TestResponsesIncludeReasoningEncryptedContentFalseSkipsAutomaticInclude(t *testing.T) {
const input = `
{
"store":false,
"model":"gpt-4o-mini",
"input":[{
"type":"message",
"role":"user",
"content":[{"type":"input_text","text":"hello"}]
}]
}
`

const output = `
{
"id": "resp_67890",
"object": "response",
"created_at": 1741891428,
"status": "completed",
"model": "gpt-4o-mini-2024-07-18",
"output": [{
"type": "message",
"id": "msg_67d32764fcdc8191bcf2e444d4088804058a5e08c46a181d",
"status": "completed",
"role": "assistant",
"content": [{"type": "output_text", "text": "Hello!", "annotations": []}]
}]
}
`

server := newTestResponsesServer(t, input, output)
defer server.Close()

a := openaiprovider.NewResponsesAgent(
openai.NewClient(option.WithBaseURL(server.URL)),
openaiprovider.AgentConfig{
Model: "gpt-4o-mini",
},
)

_, err := a.RunText(t.Context(), "hello",
openaiprovider.ResponsesNewParams(responses.ResponseNewParams{Store: openai.Bool(false)}),
openaiprovider.ResponsesIncludeReasoningEncryptedContent(false),
).Collect()
if err != nil {
t.Fatalf("error = %v", err)
}
}

func TestResponsesConversationId_AsConversationId_NonStreaming(t *testing.T) {
const input = `
{
Expand Down
Loading