jaf-py 2.5.2__py3-none-any.whl → 2.5.4__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.
jaf/server/server.py CHANGED
@@ -18,6 +18,7 @@ from fastapi.responses import StreamingResponse
18
18
 
19
19
  from ..core.engine import run
20
20
  from ..core.streaming import run_streaming
21
+ from ..core.regeneration import regenerate_conversation, get_regeneration_points
21
22
  from ..core.types import (
22
23
  ApprovalValue,
23
24
  CompletedOutcome,
@@ -29,6 +30,8 @@ from ..core.types import (
29
30
  RunState,
30
31
  create_run_id,
31
32
  create_trace_id,
33
+ create_message_id,
34
+ RegenerationRequest,
32
35
  )
33
36
  from ..memory.types import MemoryConfig
34
37
  from .types import (
@@ -52,8 +55,15 @@ from .types import (
52
55
  PendingApprovalData,
53
56
  PendingApprovalsData,
54
57
  PendingApprovalsResponse,
58
+ RegenerationHttpRequest,
59
+ RegenerationData,
60
+ RegenerationResponse,
61
+ RegenerationPointData,
62
+ RegenerationHistoryData,
63
+ RegenerationHistoryResponse,
55
64
  ServerConfig,
56
65
  ToolCallInterruption,
66
+ validate_regeneration_request,
57
67
  )
58
68
 
59
69
  Ctx = TypeVar('Ctx')
@@ -854,4 +864,157 @@ def create_jaf_server(config: ServerConfig[Ctx]) -> FastAPI:
854
864
  }
855
865
  )
856
866
 
867
+ # Regeneration endpoints
868
+ if config.default_memory_provider:
869
+ @app.post("/conversations/{conversation_id}/regenerate", response_model=RegenerationResponse)
870
+ async def regenerate_conversation_endpoint(conversation_id: str, request: RegenerationHttpRequest):
871
+ """Regenerate conversation from a specific message."""
872
+ request_start_time = time.time()
873
+
874
+ try:
875
+ # Validate agent exists
876
+ if request.agent_name not in config.agent_registry:
877
+ return RegenerationResponse(
878
+ success=False,
879
+ error=f"Agent '{request.agent_name}' not found. Available agents: {', '.join(config.agent_registry.keys())}"
880
+ )
881
+
882
+ # Create regeneration request
883
+ regen_request = RegenerationRequest(
884
+ conversation_id=conversation_id,
885
+ message_id=create_message_id(request.message_id),
886
+ context=request.context
887
+ )
888
+
889
+ # Create run config with memory
890
+ memory_config = MemoryConfig(
891
+ provider=config.default_memory_provider,
892
+ auto_store=True,
893
+ store_on_completion=True
894
+ )
895
+
896
+ run_config_with_memory = replace(
897
+ config.run_config,
898
+ memory=memory_config,
899
+ conversation_id=conversation_id,
900
+ max_turns=request.max_turns or 10
901
+ )
902
+
903
+ # Execute regeneration
904
+ result = await regenerate_conversation(
905
+ regen_request,
906
+ run_config_with_memory,
907
+ request.context or {},
908
+ request.agent_name
909
+ )
910
+
911
+ # Convert result to HTTP format
912
+ http_messages = [_convert_core_message_to_http(msg) for msg in result.final_state.messages]
913
+
914
+ # Create outcome data
915
+ if isinstance(result.outcome, CompletedOutcome):
916
+ outcome_data = BaseOutcomeData(
917
+ status='completed',
918
+ output=result.outcome.output
919
+ )
920
+ elif isinstance(result.outcome, ErrorOutcome):
921
+ error_info = result.outcome.error
922
+ outcome_data = BaseOutcomeData(
923
+ status='error',
924
+ error={
925
+ 'type': error_info.__class__.__name__,
926
+ 'message': str(error_info)
927
+ }
928
+ )
929
+ elif isinstance(result.outcome, InterruptedOutcome):
930
+ interruptions = []
931
+ for interruption in result.outcome.interruptions:
932
+ if hasattr(interruption, 'tool_call') and hasattr(interruption, 'type'):
933
+ tool_call_data = ToolCallInterruption(
934
+ id=interruption.tool_call.id,
935
+ function={
936
+ 'name': interruption.tool_call.function.name,
937
+ 'arguments': interruption.tool_call.function.arguments
938
+ }
939
+ )
940
+ interruptions.append(InterruptionData(
941
+ type='tool_approval',
942
+ tool_call=tool_call_data,
943
+ session_id=interruption.session_id or str(result.final_state.run_id)
944
+ ))
945
+
946
+ outcome_data = InterruptedOutcomeData(
947
+ status='interrupted',
948
+ interruptions=interruptions
949
+ )
950
+ else:
951
+ outcome_data = BaseOutcomeData(status='error', error='Unknown outcome type')
952
+
953
+ # Get regeneration metadata from conversation
954
+ conversation_result = await config.default_memory_provider.get_conversation(conversation_id)
955
+ regeneration_id = f"regen_{int(time.time() * 1000)}_{request.message_id}"
956
+ original_message_count = 0
957
+ truncated_at_index = 0
958
+
959
+ if hasattr(conversation_result, 'data') and conversation_result.data:
960
+ conversation_data = conversation_result.data
961
+ regeneration_points = conversation_data.metadata.get('regeneration_points', []) if conversation_data.metadata else []
962
+ if regeneration_points:
963
+ latest_regen = regeneration_points[-1]
964
+ original_message_count = latest_regen.get('original_message_count', len(conversation_data.messages))
965
+ truncated_at_index = latest_regen.get('truncated_at_index', 0)
966
+ regeneration_id = latest_regen.get('regeneration_id', regeneration_id)
967
+
968
+ return RegenerationResponse(
969
+ success=True,
970
+ data=RegenerationData(
971
+ regeneration_id=regeneration_id,
972
+ conversation_id=conversation_id,
973
+ original_message_count=original_message_count,
974
+ truncated_at_index=truncated_at_index,
975
+ regenerated_message_id=request.message_id,
976
+ messages=http_messages,
977
+ outcome=outcome_data,
978
+ turn_count=result.final_state.turn_count,
979
+ execution_time_ms=int((time.time() - request_start_time) * 1000)
980
+ )
981
+ )
982
+
983
+ except Exception as e:
984
+ return RegenerationResponse(success=False, error=str(e))
985
+
986
+ @app.get("/conversations/{conversation_id}/regeneration-history", response_model=RegenerationHistoryResponse)
987
+ async def get_regeneration_history(conversation_id: str):
988
+ """Get regeneration history for a conversation."""
989
+ try:
990
+ regeneration_points = await get_regeneration_points(conversation_id, config.run_config)
991
+
992
+ if regeneration_points is None:
993
+ return RegenerationHistoryResponse(
994
+ success=False,
995
+ error="Failed to get regeneration history"
996
+ )
997
+
998
+ # Convert to response format
999
+ regeneration_data = []
1000
+ for point in regeneration_points:
1001
+ regeneration_data.append(RegenerationPointData(
1002
+ regeneration_id=point.get('regeneration_id', ''),
1003
+ message_id=point.get('message_id', ''),
1004
+ timestamp=point.get('timestamp', 0),
1005
+ original_message_count=point.get('original_message_count', 0),
1006
+ truncated_at_index=point.get('truncated_at_index', 0)
1007
+ ))
1008
+
1009
+ return RegenerationHistoryResponse(
1010
+ success=True,
1011
+ data=RegenerationHistoryData(
1012
+ conversation_id=conversation_id,
1013
+ regeneration_points=regeneration_data
1014
+ )
1015
+ )
1016
+
1017
+ except Exception as e:
1018
+ return RegenerationHistoryResponse(success=False, error=str(e))
1019
+
857
1020
  return app
jaf/server/types.py CHANGED
@@ -11,7 +11,7 @@ import base64
11
11
 
12
12
  from pydantic import BaseModel, Field, field_validator, model_validator
13
13
 
14
- from ..core.types import Agent, RunConfig, Attachment, MessageContentPart, get_text_content
14
+ from ..core.types import Agent, RunConfig, Attachment, MessageContentPart, get_text_content, MessageId, RegenerationRequest
15
15
  from ..memory.types import MemoryProvider
16
16
 
17
17
  Ctx = TypeVar('Ctx')
@@ -267,8 +267,56 @@ class PendingApprovalsResponse(BaseModel):
267
267
  data: Optional[PendingApprovalsData] = None
268
268
  error: Optional[str] = None
269
269
 
270
+ # Regeneration types
271
+ class RegenerationHttpRequest(BaseModel):
272
+ """HTTP request format for conversation regeneration."""
273
+ message_id: str = Field(..., description="ID of the message to regenerate from")
274
+ agent_name: str = Field(..., description="Name of the agent to use for regeneration")
275
+ context: Optional[Dict[str, Any]] = Field(default=None, description="Optional context override for regeneration")
276
+ max_turns: Optional[int] = Field(default=10, description="Maximum number of turns for regeneration")
277
+
278
+ class RegenerationData(BaseModel):
279
+ """Data for successful regeneration response."""
280
+ regeneration_id: str
281
+ conversation_id: str
282
+ original_message_count: int
283
+ truncated_at_index: int
284
+ regenerated_message_id: str
285
+ messages: List[HttpMessage]
286
+ outcome: BaseOutcomeData
287
+ turn_count: int
288
+ execution_time_ms: int
289
+
290
+ class RegenerationResponse(BaseModel):
291
+ """Response format for regeneration endpoints."""
292
+ success: bool
293
+ data: Optional[RegenerationData] = None
294
+ error: Optional[str] = None
295
+
296
+ class RegenerationPointData(BaseModel):
297
+ """Data for a regeneration point."""
298
+ regeneration_id: str
299
+ message_id: str
300
+ timestamp: int
301
+ original_message_count: int
302
+ truncated_at_index: int
303
+
304
+ class RegenerationHistoryData(BaseModel):
305
+ """Data for regeneration history response."""
306
+ conversation_id: str
307
+ regeneration_points: List[RegenerationPointData]
308
+
309
+ class RegenerationHistoryResponse(BaseModel):
310
+ """Response format for regeneration history endpoint."""
311
+ success: bool
312
+ data: Optional[RegenerationHistoryData] = None
313
+ error: Optional[str] = None
270
314
 
271
315
  # Validation schemas
272
316
  def validate_chat_request(data: Dict[str, Any]) -> ChatRequest:
273
317
  """Validate and parse a chat request."""
274
318
  return ChatRequest.model_validate(data)
319
+
320
+ def validate_regeneration_request(data: Dict[str, Any]) -> RegenerationHttpRequest:
321
+ """Validate and parse a regeneration request."""
322
+ return RegenerationHttpRequest.model_validate(data)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: jaf-py
3
- Version: 2.5.2
3
+ Version: 2.5.4
4
4
  Summary: A purely functional agent framework with immutable state and composable tools - Python implementation
5
5
  Author: JAF Contributors
6
6
  Maintainer: JAF Contributors
@@ -42,13 +42,13 @@ Requires-Dist: fastmcp>=0.1.0
42
42
  Requires-Dist: opentelemetry-api>=1.22.0
43
43
  Requires-Dist: opentelemetry-sdk>=1.22.0
44
44
  Requires-Dist: opentelemetry-exporter-otlp>=1.22.0
45
- Requires-Dist: langfuse<4.0.0,>=3.6.1
45
+ Requires-Dist: langfuse<3.0.0
46
46
  Requires-Dist: litellm>=1.76.3
47
47
  Provides-Extra: tracing
48
48
  Requires-Dist: opentelemetry-api>=1.22.0; extra == "tracing"
49
49
  Requires-Dist: opentelemetry-sdk>=1.22.0; extra == "tracing"
50
50
  Requires-Dist: opentelemetry-exporter-otlp>=1.22.0; extra == "tracing"
51
- Requires-Dist: langfuse<4.0.0,>=3.6.1; extra == "tracing"
51
+ Requires-Dist: langfuse<3.0.0; extra == "tracing"
52
52
  Provides-Extra: attachments
53
53
  Requires-Dist: PyPDF2>=3.0.0; extra == "attachments"
54
54
  Requires-Dist: python-docx>=1.1.0; extra == "attachments"
@@ -82,7 +82,7 @@ Dynamic: license-file
82
82
 
83
83
  <!-- ![JAF Banner](docs/cover.png) -->
84
84
 
85
- [![Version](https://img.shields.io/badge/version-2.5.2-blue.svg)](https://github.com/xynehq/jaf-py)
85
+ [![Version](https://img.shields.io/badge/version-2.5.4-blue.svg)](https://github.com/xynehq/jaf-py)
86
86
  [![Python](https://img.shields.io/badge/python-3.10%2B-blue.svg)](https://www.python.org/)
87
87
  [![Docs](https://img.shields.io/badge/Docs-Live-brightgreen)](https://xynehq.github.io/jaf-py/)
88
88
 
@@ -1,4 +1,4 @@
1
- jaf/__init__.py,sha256=SbhsrrcWZmeOfOlOOpIzmd1jCGYs3T4ckoJYr15Q3sQ,8260
1
+ jaf/__init__.py,sha256=05QV74KFtLBXDOyJnhFS4uco0WLSL22ipBRFc4gBMtY,8260
2
2
  jaf/cli.py,sha256=Af4di_NZ7rZ4wFl0R4EZh611NgJ--TL03vNyZ2M1_FY,8477
3
3
  jaf/exceptions.py,sha256=nl8JY355u7oTXB3PmC_LhnUaL8fzk2K4EaWM4fVpMPE,9196
4
4
  jaf/a2a/__init__.py,sha256=p4YVthZH0ow1ZECqWTQ0aQl8JWySYZb25jlzZJ09na4,7662
@@ -42,7 +42,7 @@ jaf/core/__init__.py,sha256=1VHV2-a1oJXIWcg8n5G5g2cmjw2QXv7OezncNB59KLw,1988
42
42
  jaf/core/agent_tool.py,sha256=tfLNaTIcOZ0dR9GBP1AHLPkLExm_dLbURnVIN4R84FQ,11806
43
43
  jaf/core/analytics.py,sha256=zFHIWqWal0bbEFCmJDc4DKeM0Ja7b_D19PqVaBI12pA,23338
44
44
  jaf/core/composition.py,sha256=IVxRO1Q9nK7JRH32qQ4p8WMIUu66BhqPNrlTNMGFVwE,26317
45
- jaf/core/engine.py,sha256=wt5RW8ntEV8Prq5IoNiTIqAe6PBZM4XLTXYrwCUONTo,58252
45
+ jaf/core/engine.py,sha256=5SUYyUbLTEXm9sk56n21PRdgkhrbPoJiU2i55ZETqcE,57914
46
46
  jaf/core/errors.py,sha256=5fwTNhkojKRQ4wZj3lZlgDnAsrYyjYOwXJkIr5EGNUc,5539
47
47
  jaf/core/guardrails.py,sha256=nv7pQuCx7-9DDZrecWO1DsDqFoujL81FBDrafOsXgcI,26179
48
48
  jaf/core/handoff.py,sha256=ttjOQ6CSl34J4T_1ejdmq78gZ-ve07_IQE_DAbz2bmo,6002
@@ -50,22 +50,23 @@ jaf/core/parallel_agents.py,sha256=ahwYoTnkrF4xQgV-hjc5sUaWhQWQFENMZG5riNa_Ieg,1
50
50
  jaf/core/performance.py,sha256=jedQmTEkrKMD6_Aw1h8PdG-5TsdYSFFT7Or6k5dmN2g,9974
51
51
  jaf/core/proxy.py,sha256=_WM3cpRlSQLYpgSBrnY30UPMe2iZtlqDQ65kppE-WY0,4609
52
52
  jaf/core/proxy_helpers.py,sha256=i7a5fAX9rLmO4FMBX51-yRkTFwfWedzQNgnLmeLUd_A,4370
53
+ jaf/core/regeneration.py,sha256=jlV2PwobP_kAhafaP8zuwZcYyZiuh7QbWgA9_4ST8xM,17904
53
54
  jaf/core/state.py,sha256=oNCVXPWLkqnBQObdQX10TcmZ0eOF3wKG6DtL3kF6ohw,9649
54
55
  jaf/core/streaming.py,sha256=h_lYHQA9ee_D5QsDO9-Vhevgi7rFXPslPzd9605AJGo,17034
55
56
  jaf/core/tool_results.py,sha256=-bTOqOX02lMyslp5Z4Dmuhx0cLd5o7kgR88qK2HO_sw,11323
56
57
  jaf/core/tools.py,sha256=84N9A7QQ3xxcOs2eUUot3nmCnt5i7iZT9VwkuzuFBxQ,16274
57
- jaf/core/tracing.py,sha256=2YF266jIwRHjDd5C-D5l_9T_Ts0jfY5md20odc5ygQw,47464
58
- jaf/core/types.py,sha256=GE7Q2s6jglAFfkpc1MIgVkC9XWaWT46OOQd7L17ACxw,27955
58
+ jaf/core/tracing.py,sha256=aIPhDtugRhdym5_IO8ES4Cm5qzc1zYDjdMsE2wsJ_as,53367
59
+ jaf/core/types.py,sha256=GaAjeLimd8LNRIu6QnpsWGPtNCdlk77HdXoPtJdQ9eY,32154
59
60
  jaf/core/workflows.py,sha256=Ul-82gzjIXtkhnSMSPv-8igikjkMtW1EBo9yrfodtvI,26294
60
61
  jaf/memory/__init__.py,sha256=-L98xlvihurGAzF0DnXtkueDVvO_wV2XxxEwAWdAj50,1400
61
62
  jaf/memory/approval_storage.py,sha256=HHZ_b57kIthdR53QE5XNSII9xy1Cg-1cFUCSAZ8A4Rk,11083
62
63
  jaf/memory/factory.py,sha256=Fh6JyvQtCKe38DZV5-NnC9vPRCvzBgSSPFIGaX7Nt5E,2958
63
- jaf/memory/types.py,sha256=rAilMYhAzJLceGkdk1U_worRTNw75ZJV5ZuXYR6oyLA,8484
64
- jaf/memory/utils.py,sha256=t_sH2txnbYLO_noT6ysmJfiU6lX7b_sp_g0lNQ2B61A,6881
64
+ jaf/memory/types.py,sha256=pEFaofSISJ_FipiNpju0qPImpB_Pqy8yY5MSpMMi59E,9614
65
+ jaf/memory/utils.py,sha256=TTiLdP6-fEzy2GjF-KyimOLfeEla8bIHcscAETA4_Ic,6966
65
66
  jaf/memory/providers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
66
- jaf/memory/providers/in_memory.py,sha256=5hiCumGK2c6SlEFdPAacC493JY1hDDfY1CaMxx8pS2E,12558
67
- jaf/memory/providers/postgres.py,sha256=mq6nB0FNet62QFh_NP-luKKmHLhkD8en5B1hhI512rA,12379
68
- jaf/memory/providers/redis.py,sha256=_eU_1vMQzhskp0_yAqyANXrdb1ZjXm7kP6vqrz0YgOg,9274
67
+ jaf/memory/providers/in_memory.py,sha256=aL1a1vttvu6WKmJFWMN6T6OMdpdOaQL0a0tkq2YUfks,20413
68
+ jaf/memory/providers/postgres.py,sha256=gxGnoQyBeYMg3GVE1Y6aN45w-RLgyefOJBzhVK3kF84,20989
69
+ jaf/memory/providers/redis.py,sha256=eSUmR66j_DyOE13Hug9bC2DdK1n6D85ERYNPcZFpyZs,17106
69
70
  jaf/plugins/__init__.py,sha256=8GMtLld3UpkJVJ05gfVthw8tVXW0ALL6e6s88cKiCwY,796
70
71
  jaf/plugins/base.py,sha256=clQohPxT19zj-AjRtDoevE26xS5-cm7qdUOW2VX2InY,12575
71
72
  jaf/policies/__init__.py,sha256=tfbUgIPMwMKvZsyDItP_zvbhGXGY5scKSo78LIomgDU,416
@@ -76,8 +77,8 @@ jaf/providers/mcp.py,sha256=WxcC8gUFpDBBYyhorMcc1jHq3xMDMBtnwyRPthfL0S0,13074
76
77
  jaf/providers/model.py,sha256=bN2Hhr0N3soZzMrCdJ1pJa4rvo80oedCphDPcNgrVMY,39336
77
78
  jaf/server/__init__.py,sha256=fMPnLZBRm6t3yQrr7-PnoHAQ8qj9o6Z1AJLM1M6bIS0,392
78
79
  jaf/server/main.py,sha256=CTb0ywbPIq9ELfay5MKChVR7BpIQOoEbPjPfpzo2aBQ,2152
79
- jaf/server/server.py,sha256=K8XKNyadP_YqkCRSK9jCVZh52d2_IbHp_jHkKzBeB9Q,38786
80
- jaf/server/types.py,sha256=Gg8z1bkA7IYg94lp31iB92-4VkJr9syKA41uVCfNZBc,10544
80
+ jaf/server/server.py,sha256=cbdhVTPoWa7rVIX3DDLoXjppeGTKQWNFj3NA1jrZN88,46830
81
+ jaf/server/types.py,sha256=Evsq9yaZp_mleGRBXCj39fRKBCwBaBDbORCB2x9r5aw,12405
81
82
  jaf/utils/__init__.py,sha256=4Lte7HPIpmEuGvWd9lSH9gljV11wy-yNFjECPhcejAY,1236
82
83
  jaf/utils/attachments.py,sha256=9xNzzQanCwJnBR1L6P79YQtbuRupiDluDn46SYUlHok,13542
83
84
  jaf/utils/document_processor.py,sha256=FSALtq-RLMWJPmxRSDs32Nys4XAvacSbRf211-mjIeA,20150
@@ -87,9 +88,9 @@ jaf/visualization/functional_core.py,sha256=zedMDZbvjuOugWwnh6SJ2stvRNQX1Hlkb9Ab
87
88
  jaf/visualization/graphviz.py,sha256=WTOM6UP72-lVKwI4_SAr5-GCC3ouckxHv88ypCDQWJ0,12056
88
89
  jaf/visualization/imperative_shell.py,sha256=GpMrAlMnLo2IQgyB2nardCz09vMvAzaYI46MyrvJ0i4,2593
89
90
  jaf/visualization/types.py,sha256=QQcbVeQJLuAOXk8ynd08DXIS-PVCnv3R-XVE9iAcglw,1389
90
- jaf_py-2.5.2.dist-info/licenses/LICENSE,sha256=LXUQBJxdyr-7C4bk9cQBwvsF_xwA-UVstDTKabpcjlI,1063
91
- jaf_py-2.5.2.dist-info/METADATA,sha256=bpMawyewNtufu4o4eQWuPRCwiqhLqKqw0ZZNbeEMtl8,27759
92
- jaf_py-2.5.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
93
- jaf_py-2.5.2.dist-info/entry_points.txt,sha256=OtIJeNJpb24kgGrqRx9szGgDx1vL9ayq8uHErmu7U5w,41
94
- jaf_py-2.5.2.dist-info/top_level.txt,sha256=Xu1RZbGaM4_yQX7bpalo881hg7N_dybaOW282F15ruE,4
95
- jaf_py-2.5.2.dist-info/RECORD,,
91
+ jaf_py-2.5.4.dist-info/licenses/LICENSE,sha256=LXUQBJxdyr-7C4bk9cQBwvsF_xwA-UVstDTKabpcjlI,1063
92
+ jaf_py-2.5.4.dist-info/METADATA,sha256=rI-ngB2xNPg5gIk_L5hq9j5pi_XdAyV3htqTpgtq0Pk,27743
93
+ jaf_py-2.5.4.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
94
+ jaf_py-2.5.4.dist-info/entry_points.txt,sha256=OtIJeNJpb24kgGrqRx9szGgDx1vL9ayq8uHErmu7U5w,41
95
+ jaf_py-2.5.4.dist-info/top_level.txt,sha256=Xu1RZbGaM4_yQX7bpalo881hg7N_dybaOW282F15ruE,4
96
+ jaf_py-2.5.4.dist-info/RECORD,,
File without changes