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
2 changes: 1 addition & 1 deletion application/single_app/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@
EXECUTOR_TYPE = 'thread'
EXECUTOR_MAX_WORKERS = 30
SESSION_TYPE = 'filesystem'
VERSION = "0.261.102"
VERSION = "0.261.103"
IS_DEVELOPMENT = is_development_env_enabled()

SESSION_COOKIE_SAMESITE = os.getenv('SESSION_COOKIE_SAMESITE', 'Lax')
Expand Down
17 changes: 7 additions & 10 deletions application/single_app/functions_orchestration_adapters.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@
lives in ``route_backend_chats``, importing which at module load would be a circular import --
so the same lazy pattern is used uniformly rather than only where it is strictly forced.

Version: 0.261.099
Version: 0.261.102
"""

import json
Expand Down Expand Up @@ -1091,17 +1091,14 @@ def _finalize_source_review(
)


def _resolve_source_review_planner(settings):
def _resolve_source_review_planner(settings, context=None):
"""The optional client for research query and link-selection planning.

perform_source_review takes a planner client/model so it can decide which discovered links
are worth reading. The context's ``invoke_prompt`` closure has already resolved a client,
but it is a ``call(prompt) -> text`` seam by design and does not expose the client object,
so we resolve one the same way the planner does. ``resolve_planner_client`` handles APIM,
managed identity and key auth and returns the planner deployment -- the right model for an
internal planning call rather than for writing the final answer. Expected configuration
failures leave the existing backup query/link planning available.
A running orchestration supplies its already-authorized, protocol-aware client.
Standalone callers retain the legacy optional planner and backup planning behavior.
"""
if getattr(context, 'planner_client', None) is not None:
return context.planner_client, context.planner_deployment
from functions_orchestration_planner import PlannerError, resolve_planner_client

try:
Expand Down Expand Up @@ -1229,7 +1226,7 @@ def run_deep_research(step, context, *, settings, user_id, emit, cancel_requeste
'Deep research is not enabled or permitted.',
)

planner_client, planner_model = _resolve_source_review_planner(settings)
planner_client, planner_model = _resolve_source_review_planner(settings, context)
if _is_cancelled(cancel_requested):
return _cancelled_result('Cancelled before deep research.')

Expand Down
3 changes: 2 additions & 1 deletion application/single_app/functions_orchestration_context.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
A user who picked a document and then watched the planner search their whole workspace
would rightly conclude the control did nothing.

Version: 0.261.099
Version: 0.261.102
"""

import hashlib
Expand Down Expand Up @@ -180,6 +180,7 @@ def resolve_seeds(request_data):
'document_filter_mode': filter_mode,
'agent': agent,
'model': model or None,
'reasoning_effort': _text(request_data.get('reasoning_effort')),
'prompt': prompt,
# A user who switched web search on has said something about intent even in
# orchestration mode, so it is carried through as a constraint rather than dropped.
Expand Down
14 changes: 13 additions & 1 deletion application/single_app/functions_orchestration_events.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
plan card ticks specific steps by id, and reverse-engineering that from prose would be
guesswork.

Version: 0.261.085
Version: 0.261.102
"""

import json
Expand Down Expand Up @@ -283,6 +283,10 @@ def build_run_done_event(
plan_summary=None,
status='completed',
agent_citations=None,
model_deployment_name=None,
model_provider=None,
model_endpoint_id=None,
model_id=None,
):
"""Terminal frame of the run endpoint.

Expand All @@ -307,6 +311,14 @@ def build_run_done_event(
'generated_artifacts': list(artifacts or ()),
'orchestration': plan_summary or {},
'status': status,
**{
key: value for key, value in {
'model_deployment_name': model_deployment_name,
'model_provider': model_provider,
'model_endpoint_id': model_endpoint_id,
'model_id': model_id,
}.items() if value is not None
},
})


Expand Down
10 changes: 7 additions & 3 deletions application/single_app/functions_orchestration_executor.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@
Two properties are worth stating because they are the reason this is an engine and not a
loop:

**A plan always produces an answer.** A gather step can fail, be skipped because its
**A plan always attempts an answer.** A gather step can fail, be skipped because its
dependency failed, or be cut off by a budget, and the run still reaches ``respond`` and
answers with whatever evidence survived. The terminal step is therefore exempt from every
skip rule; the only thing that stops it is an explicit cancellation.
skip rule except an explicit cancellation. A failed answer completion still fails the run.

**Access is re-checked at answer time, not trusted from plan time.** Between the planner
naming a document and the executor answering from it, the user's access to that document can
Expand All @@ -31,7 +31,7 @@
itself. The route owns that loop, because only the route can decide to spend another planner
round trip.

Version: 0.261.099
Version: 0.261.102
"""

import logging
Expand Down Expand Up @@ -169,6 +169,8 @@ def __init__(
user_id=None,
turn_index=0,
invoke_prompt=None,
planner_client=None,
planner_deployment=None,
user_message='',
user_message_id=None,
answered_questions=None,
Expand Down Expand Up @@ -209,6 +211,8 @@ def __init__(
self.turn_index = turn_index

self.invoke_prompt = invoke_prompt
self.planner_client = planner_client
self.planner_deployment = planner_deployment
self.user_message = user_message
self.answered_questions = deepcopy(answered_questions or [])
self.resolved_message = resolved_message if resolved_message is not None else user_message
Expand Down
Loading