letta-nightly 0.6.0.dev20241204213946__py3-none-any.whl → 0.6.0.dev20241205104308__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.
Potentially problematic release.
This version of letta-nightly might be problematic. Click here for more details.
- letta/client/client.py +10 -6
- letta/metadata.py +2 -65
- letta/orm/__init__.py +1 -0
- letta/orm/job.py +29 -0
- letta/orm/sqlalchemy_base.py +29 -10
- letta/orm/user.py +3 -3
- letta/schemas/job.py +9 -9
- letta/server/rest_api/routers/v1/jobs.py +18 -10
- letta/server/rest_api/routers/v1/sources.py +7 -5
- letta/server/server.py +8 -44
- letta/services/job_manager.py +85 -0
- letta/services/tool_execution_sandbox.py +31 -5
- letta/utils.py +19 -3
- {letta_nightly-0.6.0.dev20241204213946.dist-info → letta_nightly-0.6.0.dev20241205104308.dist-info}/METADATA +1 -1
- {letta_nightly-0.6.0.dev20241204213946.dist-info → letta_nightly-0.6.0.dev20241205104308.dist-info}/RECORD +18 -16
- {letta_nightly-0.6.0.dev20241204213946.dist-info → letta_nightly-0.6.0.dev20241205104308.dist-info}/LICENSE +0 -0
- {letta_nightly-0.6.0.dev20241204213946.dist-info → letta_nightly-0.6.0.dev20241205104308.dist-info}/WHEEL +0 -0
- {letta_nightly-0.6.0.dev20241204213946.dist-info → letta_nightly-0.6.0.dev20241205104308.dist-info}/entry_points.txt +0 -0
letta/client/client.py
CHANGED
|
@@ -2859,8 +2859,12 @@ class LocalClient(AbstractClient):
|
|
|
2859
2859
|
Returns:
|
|
2860
2860
|
job (Job): Data loading job including job status and metadata
|
|
2861
2861
|
"""
|
|
2862
|
-
|
|
2863
|
-
|
|
2862
|
+
job = Job(
|
|
2863
|
+
user_id=self.user_id,
|
|
2864
|
+
status=JobStatus.created,
|
|
2865
|
+
metadata_={"type": "embedding", "filename": filename, "source_id": source_id},
|
|
2866
|
+
)
|
|
2867
|
+
job = self.server.job_manager.create_job(pydantic_job=job, actor=self.user)
|
|
2864
2868
|
|
|
2865
2869
|
# TODO: implement blocking vs. non-blocking
|
|
2866
2870
|
self.server.load_file_to_source(source_id=source_id, file_path=filename, job_id=job.id)
|
|
@@ -2870,16 +2874,16 @@ class LocalClient(AbstractClient):
|
|
|
2870
2874
|
self.server.source_manager.delete_file(file_id, actor=self.user)
|
|
2871
2875
|
|
|
2872
2876
|
def get_job(self, job_id: str):
|
|
2873
|
-
return self.server.
|
|
2877
|
+
return self.server.job_manager.get_job_by_id(job_id=job_id, actor=self.user)
|
|
2874
2878
|
|
|
2875
2879
|
def delete_job(self, job_id: str):
|
|
2876
|
-
return self.server.delete_job(job_id)
|
|
2880
|
+
return self.server.job_manager.delete_job(job_id=job_id, actor=self.user)
|
|
2877
2881
|
|
|
2878
2882
|
def list_jobs(self):
|
|
2879
|
-
return self.server.list_jobs(
|
|
2883
|
+
return self.server.job_manager.list_jobs(actor=self.user)
|
|
2880
2884
|
|
|
2881
2885
|
def list_active_jobs(self):
|
|
2882
|
-
return self.server.
|
|
2886
|
+
return self.server.job_manager.list_jobs(actor=self.user, statuses=[JobStatus.created, JobStatus.running])
|
|
2883
2887
|
|
|
2884
2888
|
def create_source(self, name: str, embedding_config: Optional[EmbeddingConfig] = None) -> Source:
|
|
2885
2889
|
"""
|
letta/metadata.py
CHANGED
|
@@ -12,15 +12,14 @@ from letta.orm.base import Base
|
|
|
12
12
|
from letta.schemas.agent import PersistedAgentState
|
|
13
13
|
from letta.schemas.api_key import APIKey
|
|
14
14
|
from letta.schemas.embedding_config import EmbeddingConfig
|
|
15
|
-
from letta.schemas.enums import
|
|
16
|
-
from letta.schemas.job import Job
|
|
15
|
+
from letta.schemas.enums import ToolRuleType
|
|
17
16
|
from letta.schemas.llm_config import LLMConfig
|
|
18
17
|
from letta.schemas.openai.chat_completions import ToolCall, ToolCallFunction
|
|
19
18
|
from letta.schemas.tool_rule import ChildToolRule, InitToolRule, TerminalToolRule
|
|
20
19
|
from letta.schemas.user import User
|
|
21
20
|
from letta.services.per_agent_lock_manager import PerAgentLockManager
|
|
22
21
|
from letta.settings import settings
|
|
23
|
-
from letta.utils import enforce_types,
|
|
22
|
+
from letta.utils import enforce_types, printd
|
|
24
23
|
|
|
25
24
|
|
|
26
25
|
class LLMConfigColumn(TypeDecorator):
|
|
@@ -258,31 +257,6 @@ class AgentSourceMappingModel(Base):
|
|
|
258
257
|
return f"<AgentSourceMapping(user_id='{self.user_id}', agent_id='{self.agent_id}', source_id='{self.source_id}')>"
|
|
259
258
|
|
|
260
259
|
|
|
261
|
-
class JobModel(Base):
|
|
262
|
-
__tablename__ = "jobs"
|
|
263
|
-
__table_args__ = {"extend_existing": True}
|
|
264
|
-
|
|
265
|
-
id = Column(String, primary_key=True)
|
|
266
|
-
user_id = Column(String)
|
|
267
|
-
status = Column(String, default=JobStatus.pending)
|
|
268
|
-
created_at = Column(DateTime(timezone=True), server_default=func.now())
|
|
269
|
-
completed_at = Column(DateTime(timezone=True), onupdate=func.now())
|
|
270
|
-
metadata_ = Column(JSON)
|
|
271
|
-
|
|
272
|
-
def __repr__(self) -> str:
|
|
273
|
-
return f"<Job(id='{self.id}', status='{self.status}')>"
|
|
274
|
-
|
|
275
|
-
def to_record(self):
|
|
276
|
-
return Job(
|
|
277
|
-
id=self.id,
|
|
278
|
-
user_id=self.user_id,
|
|
279
|
-
status=self.status,
|
|
280
|
-
created_at=self.created_at,
|
|
281
|
-
completed_at=self.completed_at,
|
|
282
|
-
metadata_=self.metadata_,
|
|
283
|
-
)
|
|
284
|
-
|
|
285
|
-
|
|
286
260
|
class MetadataStore:
|
|
287
261
|
uri: Optional[str] = None
|
|
288
262
|
|
|
@@ -455,40 +429,3 @@ class MetadataStore:
|
|
|
455
429
|
AgentSourceMappingModel.agent_id == agent_id, AgentSourceMappingModel.source_id == source_id
|
|
456
430
|
).delete()
|
|
457
431
|
session.commit()
|
|
458
|
-
|
|
459
|
-
@enforce_types
|
|
460
|
-
def create_job(self, job: Job):
|
|
461
|
-
with self.session_maker() as session:
|
|
462
|
-
session.add(JobModel(**vars(job)))
|
|
463
|
-
session.commit()
|
|
464
|
-
|
|
465
|
-
def delete_job(self, job_id: str):
|
|
466
|
-
with self.session_maker() as session:
|
|
467
|
-
session.query(JobModel).filter(JobModel.id == job_id).delete()
|
|
468
|
-
session.commit()
|
|
469
|
-
|
|
470
|
-
def get_job(self, job_id: str) -> Optional[Job]:
|
|
471
|
-
with self.session_maker() as session:
|
|
472
|
-
results = session.query(JobModel).filter(JobModel.id == job_id).all()
|
|
473
|
-
if len(results) == 0:
|
|
474
|
-
return None
|
|
475
|
-
assert len(results) == 1, f"Expected 1 result, got {len(results)}"
|
|
476
|
-
return results[0].to_record()
|
|
477
|
-
|
|
478
|
-
def list_jobs(self, user_id: str) -> List[Job]:
|
|
479
|
-
with self.session_maker() as session:
|
|
480
|
-
results = session.query(JobModel).filter(JobModel.user_id == user_id).all()
|
|
481
|
-
return [r.to_record() for r in results]
|
|
482
|
-
|
|
483
|
-
def update_job(self, job: Job) -> Job:
|
|
484
|
-
with self.session_maker() as session:
|
|
485
|
-
session.query(JobModel).filter(JobModel.id == job.id).update(vars(job))
|
|
486
|
-
session.commit()
|
|
487
|
-
return Job
|
|
488
|
-
|
|
489
|
-
def update_job_status(self, job_id: str, status: JobStatus):
|
|
490
|
-
with self.session_maker() as session:
|
|
491
|
-
session.query(JobModel).filter(JobModel.id == job_id).update({"status": status})
|
|
492
|
-
if status == JobStatus.COMPLETED:
|
|
493
|
-
session.query(JobModel).filter(JobModel.id == job_id).update({"completed_at": get_utc_time()})
|
|
494
|
-
session.commit()
|
letta/orm/__init__.py
CHANGED
|
@@ -2,6 +2,7 @@ from letta.orm.base import Base
|
|
|
2
2
|
from letta.orm.block import Block
|
|
3
3
|
from letta.orm.blocks_agents import BlocksAgents
|
|
4
4
|
from letta.orm.file import FileMetadata
|
|
5
|
+
from letta.orm.job import Job
|
|
5
6
|
from letta.orm.organization import Organization
|
|
6
7
|
from letta.orm.sandbox_config import SandboxConfig, SandboxEnvironmentVariable
|
|
7
8
|
from letta.orm.source import Source
|
letta/orm/job.py
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
from datetime import datetime
|
|
2
|
+
from typing import TYPE_CHECKING, Optional
|
|
3
|
+
|
|
4
|
+
from sqlalchemy import JSON, String
|
|
5
|
+
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
|
6
|
+
|
|
7
|
+
from letta.orm.mixins import UserMixin
|
|
8
|
+
from letta.orm.sqlalchemy_base import SqlalchemyBase
|
|
9
|
+
from letta.schemas.enums import JobStatus
|
|
10
|
+
from letta.schemas.job import Job as PydanticJob
|
|
11
|
+
|
|
12
|
+
if TYPE_CHECKING:
|
|
13
|
+
from letta.orm.user import User
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class Job(SqlalchemyBase, UserMixin):
|
|
17
|
+
"""Jobs run in the background and are owned by a user.
|
|
18
|
+
Typical jobs involve loading and processing sources etc.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
__tablename__ = "jobs"
|
|
22
|
+
__pydantic_model__ = PydanticJob
|
|
23
|
+
|
|
24
|
+
status: Mapped[JobStatus] = mapped_column(String, default=JobStatus.created, doc="The current status of the job.")
|
|
25
|
+
completed_at: Mapped[Optional[datetime]] = mapped_column(nullable=True, doc="The unix timestamp of when the job was completed.")
|
|
26
|
+
metadata_: Mapped[Optional[dict]] = mapped_column(JSON, default=lambda: {}, doc="The metadata of the job.")
|
|
27
|
+
|
|
28
|
+
# relationships
|
|
29
|
+
user: Mapped["User"] = relationship("User", back_populates="jobs")
|
letta/orm/sqlalchemy_base.py
CHANGED
|
@@ -31,24 +31,43 @@ class SqlalchemyBase(CommonSqlalchemyMetaMixins, Base):
|
|
|
31
31
|
def list(
|
|
32
32
|
cls, *, db_session: "Session", cursor: Optional[str] = None, limit: Optional[int] = 50, **kwargs
|
|
33
33
|
) -> List[Type["SqlalchemyBase"]]:
|
|
34
|
-
"""
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
34
|
+
"""
|
|
35
|
+
List records with optional cursor (for pagination), limit, and automatic filtering.
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
db_session: The database session to use.
|
|
39
|
+
cursor: Optional ID to start pagination from.
|
|
40
|
+
limit: Maximum number of records to return.
|
|
41
|
+
**kwargs: Filters passed as equality conditions or iterable for IN filtering.
|
|
39
42
|
|
|
40
|
-
|
|
43
|
+
Returns:
|
|
44
|
+
A list of model instances matching the filters.
|
|
45
|
+
"""
|
|
46
|
+
logger.debug(f"Listing {cls.__name__} with filters {kwargs}")
|
|
47
|
+
with db_session as session:
|
|
48
|
+
# Start with a base query
|
|
49
|
+
query = select(cls)
|
|
50
|
+
|
|
51
|
+
# Apply filtering logic
|
|
52
|
+
for key, value in kwargs.items():
|
|
53
|
+
column = getattr(cls, key)
|
|
54
|
+
if isinstance(value, (list, tuple, set)): # Check for iterables
|
|
55
|
+
query = query.where(column.in_(value))
|
|
56
|
+
else: # Single value for equality filtering
|
|
57
|
+
query = query.where(column == value)
|
|
58
|
+
|
|
59
|
+
# Apply cursor for pagination
|
|
41
60
|
if cursor:
|
|
42
61
|
query = query.where(cls.id > cursor)
|
|
43
62
|
|
|
44
|
-
# Add a limit to the query if provided
|
|
45
|
-
query = query.order_by(cls.id).limit(limit)
|
|
46
|
-
|
|
47
63
|
# Handle soft deletes if the class has the 'is_deleted' attribute
|
|
48
64
|
if hasattr(cls, "is_deleted"):
|
|
49
65
|
query = query.where(cls.is_deleted == False)
|
|
50
66
|
|
|
51
|
-
#
|
|
67
|
+
# Add ordering and limit
|
|
68
|
+
query = query.order_by(cls.id).limit(limit)
|
|
69
|
+
|
|
70
|
+
# Execute the query and return results as model instances
|
|
52
71
|
return list(session.execute(query).scalars())
|
|
53
72
|
|
|
54
73
|
@classmethod
|
letta/orm/user.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from typing import TYPE_CHECKING
|
|
1
|
+
from typing import TYPE_CHECKING, List
|
|
2
2
|
|
|
3
3
|
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
|
4
4
|
|
|
@@ -7,7 +7,7 @@ from letta.orm.sqlalchemy_base import SqlalchemyBase
|
|
|
7
7
|
from letta.schemas.user import User as PydanticUser
|
|
8
8
|
|
|
9
9
|
if TYPE_CHECKING:
|
|
10
|
-
from letta.orm
|
|
10
|
+
from letta.orm import Job, Organization
|
|
11
11
|
|
|
12
12
|
|
|
13
13
|
class User(SqlalchemyBase, OrganizationMixin):
|
|
@@ -20,10 +20,10 @@ class User(SqlalchemyBase, OrganizationMixin):
|
|
|
20
20
|
|
|
21
21
|
# relationships
|
|
22
22
|
organization: Mapped["Organization"] = relationship("Organization", back_populates="users")
|
|
23
|
+
jobs: Mapped[List["Job"]] = relationship("Job", back_populates="user", doc="the jobs associated with this user.")
|
|
23
24
|
|
|
24
25
|
# TODO: Add this back later potentially
|
|
25
26
|
# agents: Mapped[List["Agent"]] = relationship(
|
|
26
27
|
# "Agent", secondary="users_agents", back_populates="users", doc="the agents associated with this user."
|
|
27
28
|
# )
|
|
28
29
|
# tokens: Mapped[List["Token"]] = relationship("Token", back_populates="user", doc="the tokens associated with this user.")
|
|
29
|
-
# jobs: Mapped[List["Job"]] = relationship("Job", back_populates="user", doc="the jobs associated with this user.")
|
letta/schemas/job.py
CHANGED
|
@@ -4,12 +4,13 @@ from typing import Optional
|
|
|
4
4
|
from pydantic import Field
|
|
5
5
|
|
|
6
6
|
from letta.schemas.enums import JobStatus
|
|
7
|
-
from letta.schemas.letta_base import
|
|
8
|
-
from letta.utils import get_utc_time
|
|
7
|
+
from letta.schemas.letta_base import OrmMetadataBase
|
|
9
8
|
|
|
10
9
|
|
|
11
|
-
class JobBase(
|
|
10
|
+
class JobBase(OrmMetadataBase):
|
|
12
11
|
__id_prefix__ = "job"
|
|
12
|
+
status: JobStatus = Field(default=JobStatus.created, description="The status of the job.")
|
|
13
|
+
completed_at: Optional[datetime] = Field(None, description="The unix timestamp of when the job was completed.")
|
|
13
14
|
metadata_: Optional[dict] = Field(None, description="The metadata of the job.")
|
|
14
15
|
|
|
15
16
|
|
|
@@ -27,12 +28,11 @@ class Job(JobBase):
|
|
|
27
28
|
"""
|
|
28
29
|
|
|
29
30
|
id: str = JobBase.generate_id_field()
|
|
30
|
-
|
|
31
|
-
created_at: datetime = Field(default_factory=get_utc_time, description="The unix timestamp of when the job was created.")
|
|
32
|
-
completed_at: Optional[datetime] = Field(None, description="The unix timestamp of when the job was completed.")
|
|
33
|
-
user_id: str = Field(..., description="The unique identifier of the user associated with the job.")
|
|
31
|
+
user_id: Optional[str] = Field(None, description="The unique identifier of the user associated with the job.")
|
|
34
32
|
|
|
35
33
|
|
|
36
34
|
class JobUpdate(JobBase):
|
|
37
|
-
|
|
38
|
-
|
|
35
|
+
status: Optional[JobStatus] = Field(None, description="The status of the job.")
|
|
36
|
+
|
|
37
|
+
class Config:
|
|
38
|
+
extra = "ignore" # Ignores extra fields
|
|
@@ -2,6 +2,8 @@ from typing import List, Optional
|
|
|
2
2
|
|
|
3
3
|
from fastapi import APIRouter, Depends, Header, HTTPException, Query
|
|
4
4
|
|
|
5
|
+
from letta.orm.errors import NoResultFound
|
|
6
|
+
from letta.schemas.enums import JobStatus
|
|
5
7
|
from letta.schemas.job import Job
|
|
6
8
|
from letta.server.rest_api.utils import get_letta_server
|
|
7
9
|
from letta.server.server import SyncServer
|
|
@@ -21,12 +23,11 @@ def list_jobs(
|
|
|
21
23
|
actor = server.get_user_or_default(user_id=user_id)
|
|
22
24
|
|
|
23
25
|
# TODO: add filtering by status
|
|
24
|
-
jobs = server.list_jobs(
|
|
26
|
+
jobs = server.job_manager.list_jobs(actor=actor)
|
|
25
27
|
|
|
26
|
-
# TODO: eventually use ORM
|
|
27
|
-
# results = session.query(JobModel).filter(JobModel.user_id == user_id, JobModel.metadata_["source_id"].astext == sourced_id).all()
|
|
28
28
|
if source_id:
|
|
29
29
|
# can't be in the ORM since we have source_id stored in the metadata_
|
|
30
|
+
# TODO: Probably change this
|
|
30
31
|
jobs = [job for job in jobs if job.metadata_.get("source_id") == source_id]
|
|
31
32
|
return jobs
|
|
32
33
|
|
|
@@ -41,32 +42,39 @@ def list_active_jobs(
|
|
|
41
42
|
"""
|
|
42
43
|
actor = server.get_user_or_default(user_id=user_id)
|
|
43
44
|
|
|
44
|
-
return server.
|
|
45
|
+
return server.job_manager.list_jobs(actor=actor, statuses=[JobStatus.created, JobStatus.running])
|
|
45
46
|
|
|
46
47
|
|
|
47
48
|
@router.get("/{job_id}", response_model=Job, operation_id="get_job")
|
|
48
49
|
def get_job(
|
|
49
50
|
job_id: str,
|
|
51
|
+
user_id: Optional[str] = Header(None, alias="user_id"),
|
|
50
52
|
server: "SyncServer" = Depends(get_letta_server),
|
|
51
53
|
):
|
|
52
54
|
"""
|
|
53
55
|
Get the status of a job.
|
|
54
56
|
"""
|
|
57
|
+
actor = server.get_user_or_default(user_id=user_id)
|
|
55
58
|
|
|
56
|
-
|
|
59
|
+
try:
|
|
60
|
+
return server.job_manager.get_job_by_id(job_id=job_id, actor=actor)
|
|
61
|
+
except NoResultFound:
|
|
62
|
+
raise HTTPException(status_code=404, detail="Job not found")
|
|
57
63
|
|
|
58
64
|
|
|
59
65
|
@router.delete("/{job_id}", response_model=Job, operation_id="delete_job")
|
|
60
66
|
def delete_job(
|
|
61
67
|
job_id: str,
|
|
68
|
+
user_id: Optional[str] = Header(None, alias="user_id"),
|
|
62
69
|
server: "SyncServer" = Depends(get_letta_server),
|
|
63
70
|
):
|
|
64
71
|
"""
|
|
65
72
|
Delete a job by its job_id.
|
|
66
73
|
"""
|
|
67
|
-
|
|
68
|
-
if not job:
|
|
69
|
-
raise HTTPException(status_code=404, detail="Job not found")
|
|
74
|
+
actor = server.get_user_or_default(user_id=user_id)
|
|
70
75
|
|
|
71
|
-
|
|
72
|
-
|
|
76
|
+
try:
|
|
77
|
+
job = server.job_manager.delete_job_by_id(job_id=job_id, actor=actor)
|
|
78
|
+
return job
|
|
79
|
+
except NoResultFound:
|
|
80
|
+
raise HTTPException(status_code=404, detail="Job not found")
|
|
@@ -16,6 +16,7 @@ from letta.schemas.file import FileMetadata
|
|
|
16
16
|
from letta.schemas.job import Job
|
|
17
17
|
from letta.schemas.passage import Passage
|
|
18
18
|
from letta.schemas.source import Source, SourceCreate, SourceUpdate
|
|
19
|
+
from letta.schemas.user import User
|
|
19
20
|
from letta.server.rest_api.utils import get_letta_server
|
|
20
21
|
from letta.server.server import SyncServer
|
|
21
22
|
from letta.utils import sanitize_filename
|
|
@@ -175,13 +176,14 @@ def upload_file_to_source(
|
|
|
175
176
|
completed_at=None,
|
|
176
177
|
)
|
|
177
178
|
job_id = job.id
|
|
178
|
-
server.
|
|
179
|
+
server.job_manager.create_job(job, actor=actor)
|
|
179
180
|
|
|
180
181
|
# create background task
|
|
181
|
-
background_tasks.add_task(load_file_to_source_async, server, source_id=source.id, file=file, job_id=job.id, bytes=bytes)
|
|
182
|
+
background_tasks.add_task(load_file_to_source_async, server, source_id=source.id, file=file, job_id=job.id, bytes=bytes, actor=actor)
|
|
182
183
|
|
|
183
184
|
# return job information
|
|
184
|
-
job
|
|
185
|
+
# Is this necessary? Can we just return the job from create_job?
|
|
186
|
+
job = server.job_manager.get_job_by_id(job_id=job_id, actor=actor)
|
|
185
187
|
assert job is not None, "Job not found"
|
|
186
188
|
return job
|
|
187
189
|
|
|
@@ -234,7 +236,7 @@ def delete_file_from_source(
|
|
|
234
236
|
raise HTTPException(status_code=404, detail=f"File with id={file_id} not found.")
|
|
235
237
|
|
|
236
238
|
|
|
237
|
-
def load_file_to_source_async(server: SyncServer, source_id: str, job_id: str, file: UploadFile, bytes: bytes):
|
|
239
|
+
def load_file_to_source_async(server: SyncServer, source_id: str, job_id: str, file: UploadFile, bytes: bytes, actor: User):
|
|
238
240
|
# Create a temporary directory (deleted after the context manager exits)
|
|
239
241
|
with tempfile.TemporaryDirectory() as tmpdirname:
|
|
240
242
|
# Sanitize the filename
|
|
@@ -246,4 +248,4 @@ def load_file_to_source_async(server: SyncServer, source_id: str, job_id: str, f
|
|
|
246
248
|
buffer.write(bytes)
|
|
247
249
|
|
|
248
250
|
# Pass the file to load_file_to_source
|
|
249
|
-
server.load_file_to_source(source_id, file_path, job_id)
|
|
251
|
+
server.load_file_to_source(source_id, file_path, job_id, actor)
|
letta/server/server.py
CHANGED
|
@@ -6,7 +6,7 @@ import warnings
|
|
|
6
6
|
from abc import abstractmethod
|
|
7
7
|
from asyncio import Lock
|
|
8
8
|
from datetime import datetime
|
|
9
|
-
from typing import Callable,
|
|
9
|
+
from typing import Callable, List, Optional, Tuple, Union
|
|
10
10
|
|
|
11
11
|
from composio.client import Composio
|
|
12
12
|
from composio.client.collections import ActionModel, AppModel
|
|
@@ -56,7 +56,7 @@ from letta.schemas.embedding_config import EmbeddingConfig
|
|
|
56
56
|
|
|
57
57
|
# openai schemas
|
|
58
58
|
from letta.schemas.enums import JobStatus
|
|
59
|
-
from letta.schemas.job import Job
|
|
59
|
+
from letta.schemas.job import Job, JobUpdate
|
|
60
60
|
from letta.schemas.letta_message import FunctionReturn, LettaMessage
|
|
61
61
|
from letta.schemas.llm_config import LLMConfig
|
|
62
62
|
from letta.schemas.memory import (
|
|
@@ -75,6 +75,7 @@ from letta.schemas.user import User
|
|
|
75
75
|
from letta.services.agents_tags_manager import AgentsTagsManager
|
|
76
76
|
from letta.services.block_manager import BlockManager
|
|
77
77
|
from letta.services.blocks_agents_manager import BlocksAgentsManager
|
|
78
|
+
from letta.services.job_manager import JobManager
|
|
78
79
|
from letta.services.organization_manager import OrganizationManager
|
|
79
80
|
from letta.services.per_agent_lock_manager import PerAgentLockManager
|
|
80
81
|
from letta.services.sandbox_config_manager import SandboxConfigManager
|
|
@@ -256,6 +257,7 @@ class SyncServer(Server):
|
|
|
256
257
|
self.agents_tags_manager = AgentsTagsManager()
|
|
257
258
|
self.sandbox_config_manager = SandboxConfigManager(tool_settings)
|
|
258
259
|
self.blocks_agents_manager = BlocksAgentsManager()
|
|
260
|
+
self.job_manager = JobManager()
|
|
259
261
|
|
|
260
262
|
# Managers that interface with parallelism
|
|
261
263
|
self.per_agent_lock_manager = PerAgentLockManager()
|
|
@@ -1469,39 +1471,12 @@ class SyncServer(Server):
|
|
|
1469
1471
|
|
|
1470
1472
|
# TODO: delete data from agent passage stores (?)
|
|
1471
1473
|
|
|
1472
|
-
def
|
|
1473
|
-
"""Create a new job"""
|
|
1474
|
-
job = Job(
|
|
1475
|
-
user_id=user_id,
|
|
1476
|
-
status=JobStatus.created,
|
|
1477
|
-
metadata_=metadata,
|
|
1478
|
-
)
|
|
1479
|
-
self.ms.create_job(job)
|
|
1480
|
-
return job
|
|
1481
|
-
|
|
1482
|
-
def delete_job(self, job_id: str):
|
|
1483
|
-
"""Delete a job"""
|
|
1484
|
-
self.ms.delete_job(job_id)
|
|
1485
|
-
|
|
1486
|
-
def get_job(self, job_id: str) -> Job:
|
|
1487
|
-
"""Get a job"""
|
|
1488
|
-
return self.ms.get_job(job_id)
|
|
1489
|
-
|
|
1490
|
-
def list_jobs(self, user_id: str) -> List[Job]:
|
|
1491
|
-
"""List all jobs for a user"""
|
|
1492
|
-
return self.ms.list_jobs(user_id=user_id)
|
|
1493
|
-
|
|
1494
|
-
def list_active_jobs(self, user_id: str) -> List[Job]:
|
|
1495
|
-
"""List all active jobs for a user"""
|
|
1496
|
-
jobs = self.ms.list_jobs(user_id=user_id)
|
|
1497
|
-
return [job for job in jobs if job.status in [JobStatus.created, JobStatus.running]]
|
|
1498
|
-
|
|
1499
|
-
def load_file_to_source(self, source_id: str, file_path: str, job_id: str) -> Job:
|
|
1474
|
+
def load_file_to_source(self, source_id: str, file_path: str, job_id: str, actor: User) -> Job:
|
|
1500
1475
|
|
|
1501
1476
|
# update job
|
|
1502
|
-
job = self.
|
|
1477
|
+
job = self.job_manager.get_job_by_id(job_id, actor=actor)
|
|
1503
1478
|
job.status = JobStatus.running
|
|
1504
|
-
self.
|
|
1479
|
+
self.job_manager.update_job_by_id(job_id=job_id, job_update=JobUpdate(**job.model_dump()), actor=actor)
|
|
1505
1480
|
|
|
1506
1481
|
# try:
|
|
1507
1482
|
from letta.data_sources.connectors import DirectoryConnector
|
|
@@ -1509,23 +1484,12 @@ class SyncServer(Server):
|
|
|
1509
1484
|
source = self.source_manager.get_source_by_id(source_id=source_id)
|
|
1510
1485
|
connector = DirectoryConnector(input_files=[file_path])
|
|
1511
1486
|
num_passages, num_documents = self.load_data(user_id=source.created_by_id, source_name=source.name, connector=connector)
|
|
1512
|
-
# except Exception as e:
|
|
1513
|
-
# # job failed with error
|
|
1514
|
-
# error = str(e)
|
|
1515
|
-
# print(error)
|
|
1516
|
-
# job.status = JobStatus.failed
|
|
1517
|
-
# job.metadata_["error"] = error
|
|
1518
|
-
# self.ms.update_job(job)
|
|
1519
|
-
# # TODO: delete any associated passages/files?
|
|
1520
|
-
|
|
1521
|
-
# # return failed job
|
|
1522
|
-
# return job
|
|
1523
1487
|
|
|
1524
1488
|
# update job status
|
|
1525
1489
|
job.status = JobStatus.completed
|
|
1526
1490
|
job.metadata_["num_passages"] = num_passages
|
|
1527
1491
|
job.metadata_["num_documents"] = num_documents
|
|
1528
|
-
self.
|
|
1492
|
+
self.job_manager.update_job_by_id(job_id=job_id, job_update=JobUpdate(**job.model_dump()), actor=actor)
|
|
1529
1493
|
|
|
1530
1494
|
return job
|
|
1531
1495
|
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
from typing import List, Optional
|
|
2
|
+
|
|
3
|
+
from letta.orm.job import Job as JobModel
|
|
4
|
+
from letta.schemas.enums import JobStatus
|
|
5
|
+
from letta.schemas.job import Job as PydanticJob
|
|
6
|
+
from letta.schemas.job import JobUpdate
|
|
7
|
+
from letta.schemas.user import User as PydanticUser
|
|
8
|
+
from letta.utils import enforce_types, get_utc_time
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class JobManager:
|
|
12
|
+
"""Manager class to handle business logic related to Jobs."""
|
|
13
|
+
|
|
14
|
+
def __init__(self):
|
|
15
|
+
# Fetching the db_context similarly as in OrganizationManager
|
|
16
|
+
from letta.server.server import db_context
|
|
17
|
+
|
|
18
|
+
self.session_maker = db_context
|
|
19
|
+
|
|
20
|
+
@enforce_types
|
|
21
|
+
def create_job(self, pydantic_job: PydanticJob, actor: PydanticUser) -> PydanticJob:
|
|
22
|
+
"""Create a new job based on the JobCreate schema."""
|
|
23
|
+
with self.session_maker() as session:
|
|
24
|
+
# Associate the job with the user
|
|
25
|
+
pydantic_job.user_id = actor.id
|
|
26
|
+
job_data = pydantic_job.model_dump()
|
|
27
|
+
job = JobModel(**job_data)
|
|
28
|
+
job.create(session, actor=actor) # Save job in the database
|
|
29
|
+
return job.to_pydantic()
|
|
30
|
+
|
|
31
|
+
@enforce_types
|
|
32
|
+
def update_job_by_id(self, job_id: str, job_update: JobUpdate, actor: PydanticUser) -> PydanticJob:
|
|
33
|
+
"""Update a job by its ID with the given JobUpdate object."""
|
|
34
|
+
with self.session_maker() as session:
|
|
35
|
+
# Fetch the job by ID
|
|
36
|
+
job = JobModel.read(db_session=session, identifier=job_id) # TODO: Add this later , actor=actor)
|
|
37
|
+
|
|
38
|
+
# Update job attributes with only the fields that were explicitly set
|
|
39
|
+
update_data = job_update.model_dump(exclude_unset=True, exclude_none=True)
|
|
40
|
+
|
|
41
|
+
# Automatically update the completion timestamp if status is set to 'completed'
|
|
42
|
+
if update_data.get("status") == JobStatus.completed and not job.completed_at:
|
|
43
|
+
job.completed_at = get_utc_time()
|
|
44
|
+
|
|
45
|
+
for key, value in update_data.items():
|
|
46
|
+
setattr(job, key, value)
|
|
47
|
+
|
|
48
|
+
# Save the updated job to the database
|
|
49
|
+
return job.update(db_session=session) # TODO: Add this later , actor=actor)
|
|
50
|
+
|
|
51
|
+
@enforce_types
|
|
52
|
+
def get_job_by_id(self, job_id: str, actor: PydanticUser) -> PydanticJob:
|
|
53
|
+
"""Fetch a job by its ID."""
|
|
54
|
+
with self.session_maker() as session:
|
|
55
|
+
# Retrieve job by ID using the Job model's read method
|
|
56
|
+
job = JobModel.read(db_session=session, identifier=job_id) # TODO: Add this later , actor=actor)
|
|
57
|
+
return job.to_pydantic()
|
|
58
|
+
|
|
59
|
+
@enforce_types
|
|
60
|
+
def list_jobs(
|
|
61
|
+
self, actor: PydanticUser, cursor: Optional[str] = None, limit: Optional[int] = 50, statuses: Optional[List[JobStatus]] = None
|
|
62
|
+
) -> List[PydanticJob]:
|
|
63
|
+
"""List all jobs with optional pagination and status filter."""
|
|
64
|
+
with self.session_maker() as session:
|
|
65
|
+
filter_kwargs = {"user_id": actor.id}
|
|
66
|
+
|
|
67
|
+
# Add status filter if provided
|
|
68
|
+
if statuses:
|
|
69
|
+
filter_kwargs["status"] = statuses
|
|
70
|
+
|
|
71
|
+
jobs = JobModel.list(
|
|
72
|
+
db_session=session,
|
|
73
|
+
cursor=cursor,
|
|
74
|
+
limit=limit,
|
|
75
|
+
**filter_kwargs,
|
|
76
|
+
)
|
|
77
|
+
return [job.to_pydantic() for job in jobs]
|
|
78
|
+
|
|
79
|
+
@enforce_types
|
|
80
|
+
def delete_job_by_id(self, job_id: str, actor: PydanticUser) -> PydanticJob:
|
|
81
|
+
"""Delete a job by its ID."""
|
|
82
|
+
with self.session_maker() as session:
|
|
83
|
+
job = JobModel.read(db_session=session, identifier=job_id) # TODO: Add this later , actor=actor)
|
|
84
|
+
job.hard_delete(db_session=session) # TODO: Add this later , actor=actor)
|
|
85
|
+
return job.to_pydantic()
|
|
@@ -159,6 +159,8 @@ class ToolExecutionSandbox:
|
|
|
159
159
|
# Set up env for venv
|
|
160
160
|
env["VIRTUAL_ENV"] = venv_path
|
|
161
161
|
env["PATH"] = os.path.join(venv_path, "bin") + ":" + env["PATH"]
|
|
162
|
+
# Suppress all warnings
|
|
163
|
+
env["PYTHONWARNINGS"] = "ignore"
|
|
162
164
|
|
|
163
165
|
# Execute the code in a restricted subprocess
|
|
164
166
|
try:
|
|
@@ -170,9 +172,31 @@ class ToolExecutionSandbox:
|
|
|
170
172
|
capture_output=True,
|
|
171
173
|
text=True,
|
|
172
174
|
)
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
175
|
+
|
|
176
|
+
# Handle error with optimistic error parsing from the string
|
|
177
|
+
# This is very brittle, so we fall back to a RuntimeError if parsing fails
|
|
178
|
+
if result.returncode != 0:
|
|
179
|
+
# Log the error
|
|
180
|
+
logger.error(f"Sandbox execution error:\n{result.stderr}")
|
|
181
|
+
|
|
182
|
+
# Parse and raise the actual error from stderr
|
|
183
|
+
tb_lines = result.stderr.strip().splitlines()
|
|
184
|
+
exception_line = tb_lines[-1] # The last line contains the exception
|
|
185
|
+
|
|
186
|
+
try:
|
|
187
|
+
# Split exception type and message
|
|
188
|
+
exception_type, exception_message = exception_line.split(": ", 1)
|
|
189
|
+
exception_type = exception_type.strip()
|
|
190
|
+
exception_message = exception_message.strip()
|
|
191
|
+
|
|
192
|
+
# Dynamically raise the exception
|
|
193
|
+
exception_class = eval(exception_type) # Look up the exception type
|
|
194
|
+
|
|
195
|
+
except Exception:
|
|
196
|
+
# Fallback to RuntimeError if parsing fails
|
|
197
|
+
raise RuntimeError(result.stderr)
|
|
198
|
+
|
|
199
|
+
raise exception_class(exception_message)
|
|
176
200
|
|
|
177
201
|
func_result, stdout = self.parse_out_function_results_markers(result.stdout)
|
|
178
202
|
func_return, agent_state = self.parse_best_effort(func_result)
|
|
@@ -182,9 +206,11 @@ class ToolExecutionSandbox:
|
|
|
182
206
|
except subprocess.TimeoutExpired:
|
|
183
207
|
raise TimeoutError(f"Executing tool {self.tool_name} has timed out.")
|
|
184
208
|
except subprocess.CalledProcessError as e:
|
|
185
|
-
|
|
209
|
+
logger.error(f"Executing tool {self.tool_name} has process error: {e}")
|
|
210
|
+
raise e
|
|
186
211
|
except Exception as e:
|
|
187
|
-
|
|
212
|
+
logger.error(f"Executing tool {self.tool_name} has an unexpected error: {e}")
|
|
213
|
+
raise e
|
|
188
214
|
|
|
189
215
|
def run_local_dir_sandbox_runpy(
|
|
190
216
|
self, sbx_config: SandboxConfig, env_vars: Dict[str, str], temp_file_path: str, old_stdout: TextIO
|
letta/utils.py
CHANGED
|
@@ -15,7 +15,7 @@ import uuid
|
|
|
15
15
|
from contextlib import contextmanager
|
|
16
16
|
from datetime import datetime, timedelta, timezone
|
|
17
17
|
from functools import wraps
|
|
18
|
-
from typing import List, Union, _GenericAlias, get_type_hints
|
|
18
|
+
from typing import List, Union, _GenericAlias, get_args, get_origin, get_type_hints
|
|
19
19
|
from urllib.parse import urljoin, urlparse
|
|
20
20
|
|
|
21
21
|
import demjson3 as demjson
|
|
@@ -529,16 +529,32 @@ def enforce_types(func):
|
|
|
529
529
|
# Pair each argument with its corresponding type hint
|
|
530
530
|
args_with_hints = dict(zip(arg_names[1:], args[1:])) # Skipping 'self'
|
|
531
531
|
|
|
532
|
+
# Function to check if a value matches a given type hint
|
|
533
|
+
def matches_type(value, hint):
|
|
534
|
+
origin = get_origin(hint)
|
|
535
|
+
args = get_args(hint)
|
|
536
|
+
|
|
537
|
+
if origin is list and isinstance(value, list): # Handle List[T]
|
|
538
|
+
element_type = args[0] if args else None
|
|
539
|
+
return all(isinstance(v, element_type) for v in value) if element_type else True
|
|
540
|
+
elif origin is Union and type(None) in args: # Handle Optional[T]
|
|
541
|
+
non_none_type = next(arg for arg in args if arg is not type(None))
|
|
542
|
+
return value is None or matches_type(value, non_none_type)
|
|
543
|
+
elif origin: # Handle other generics like Dict, Tuple, etc.
|
|
544
|
+
return isinstance(value, origin)
|
|
545
|
+
else: # Handle non-generic types
|
|
546
|
+
return isinstance(value, hint)
|
|
547
|
+
|
|
532
548
|
# Check types of arguments
|
|
533
549
|
for arg_name, arg_value in args_with_hints.items():
|
|
534
550
|
hint = hints.get(arg_name)
|
|
535
|
-
if hint and not
|
|
551
|
+
if hint and not matches_type(arg_value, hint):
|
|
536
552
|
raise ValueError(f"Argument {arg_name} does not match type {hint}")
|
|
537
553
|
|
|
538
554
|
# Check types of keyword arguments
|
|
539
555
|
for arg_name, arg_value in kwargs.items():
|
|
540
556
|
hint = hints.get(arg_name)
|
|
541
|
-
if hint and not
|
|
557
|
+
if hint and not matches_type(arg_value, hint):
|
|
542
558
|
raise ValueError(f"Argument {arg_name} does not match type {hint}")
|
|
543
559
|
|
|
544
560
|
return func(*args, **kwargs)
|
|
@@ -13,7 +13,7 @@ letta/cli/cli.py,sha256=fpcpBKEAzKU6o9gSS5pe6YRTkiybIik5CC9mCAVl_bA,16928
|
|
|
13
13
|
letta/cli/cli_config.py,sha256=tB0Wgz3O9j6KiCsU1HWfsKmhNM9RqLsAxzxEDFQFGnM,8565
|
|
14
14
|
letta/cli/cli_load.py,sha256=x4L8s15GwIW13xrhKYFWHo_y-IVGtoPDHWWKcHDRP10,4587
|
|
15
15
|
letta/client/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
16
|
-
letta/client/client.py,sha256=
|
|
16
|
+
letta/client/client.py,sha256=hQiTmkmddoGONuSlNG0Yrbo1Y2k9VRTRDFAOtHRbOPI,124143
|
|
17
17
|
letta/client/streaming.py,sha256=Hh5pjlyrdCuO2V75ZCxSSOCPd3BmHdKFGaIUJC6fBp0,4775
|
|
18
18
|
letta/client/utils.py,sha256=OJlAKWrldc4I6M1WpcTWNtPJ4wfxlzlZqWLfCozkFtI,2872
|
|
19
19
|
letta/config.py,sha256=AF4XY6grcu87OLjrWXh1ufnyKWsCL0qER-_9jQCAlU0,18947
|
|
@@ -85,12 +85,12 @@ letta/local_llm/webui/settings.py,sha256=gmLHfiOl1u4JmlAZU2d2O8YKF9lafdakyjwR_ft
|
|
|
85
85
|
letta/log.py,sha256=FxkAk2f8Bl-u9dfImSj1DYnjAsmV6PL3tjTSnEiNP48,2218
|
|
86
86
|
letta/main.py,sha256=5guUzYyxID3FDlegk3dNUev7vjPMglcIw-xqdyHdhKI,19175
|
|
87
87
|
letta/memory.py,sha256=YupXOvzVJXH59RW4XWBrd7qMNEYaMbtWXCheKeWZwpU,17873
|
|
88
|
-
letta/metadata.py,sha256=
|
|
88
|
+
letta/metadata.py,sha256=jv_s2Y6M8aE3bpOOTr-xwBBu58zZHx9tYtAekVa_Dz4,16233
|
|
89
89
|
letta/o1_agent.py,sha256=jTMlP7LxR4iUDWaGHMy8SiZtlzn6_RqP0H1HaEWXydQ,3078
|
|
90
90
|
letta/openai_backcompat/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
91
91
|
letta/openai_backcompat/openai_object.py,sha256=Y1ZS1sATP60qxJiOsjOP3NbwSzuzvkNAvb3DeuhM5Uk,13490
|
|
92
92
|
letta/orm/__all__.py,sha256=2gh2MZTkA3Hw67VWVKK3JIStJOqTeLdpCvYSVYNeEDA,692
|
|
93
|
-
letta/orm/__init__.py,sha256=
|
|
93
|
+
letta/orm/__init__.py,sha256=yXCse66-Va1tuVhoDcHbaLIffBiJKLKF4JR6SAFiIZU,412
|
|
94
94
|
letta/orm/agents_tags.py,sha256=Qa7Yt9imL8xbGP57fflccAMy7Z32CQiU_7eZKSSPngc,1119
|
|
95
95
|
letta/orm/base.py,sha256=K_LpNUURbsj44ycHbzvNXG_n8pBOjf1YvDaikIPDpQA,2716
|
|
96
96
|
letta/orm/block.py,sha256=xymYeCTJJFkzADW6wjfP2LXNZZN9yg4mCSybbvEEMMM,2356
|
|
@@ -98,13 +98,14 @@ letta/orm/blocks_agents.py,sha256=o6cfblODja7so4444npW0vusqKcvDPp8YJdsWsOePus,11
|
|
|
98
98
|
letta/orm/enums.py,sha256=KfHcFt_fR6GUmSlmfsa-TetvmuRxGESNve8MStRYW64,145
|
|
99
99
|
letta/orm/errors.py,sha256=nv1HnF3z4-u9m_g7SO5TO5u2nmJN677_n8F0iIjluUI,460
|
|
100
100
|
letta/orm/file.py,sha256=FtfZlJLXfac4ntaw3kC0N9VRoD255m8EK4p-pC2lcHk,1519
|
|
101
|
+
letta/orm/job.py,sha256=If-qSTJW4t5h-6Jolw3tS3-xMZEaPIbXe3S0GMf_FXI,1102
|
|
101
102
|
letta/orm/mixins.py,sha256=LfwePamGyOwCtAEUm-sZpIBJjODIMe4MnA_JTUcppLs,1155
|
|
102
103
|
letta/orm/organization.py,sha256=mliw4q7-SRfRcGIG8paNfCNn6ITTjR7nFalZzlRszqU,2272
|
|
103
104
|
letta/orm/sandbox_config.py,sha256=PCMHE-eJPzBT-90OYtXjEMRF4f9JB8AJIGETE7bu-f0,2870
|
|
104
105
|
letta/orm/source.py,sha256=Ib0XHCMt345RjBSC30A398rZ21W5mA4PXX00XNXyd24,2021
|
|
105
|
-
letta/orm/sqlalchemy_base.py,sha256=
|
|
106
|
+
letta/orm/sqlalchemy_base.py,sha256=CmCwVCHVyhzDAd-xIUFh72bzSX-bA0mwvMUD7FrIlUk,10672
|
|
106
107
|
letta/orm/tool.py,sha256=d0GclU_7qg8Z6ZE6kkH1kmrUAMCiV-ZM8BGaT1mnBU4,2089
|
|
107
|
-
letta/orm/user.py,sha256=
|
|
108
|
+
letta/orm/user.py,sha256=bUZzyBQXfR0w7mZAkThPAQE6kKx6W--Rw6uAiPEUW3s,1133
|
|
108
109
|
letta/persistence_manager.py,sha256=sEnNdNisK7StqIzG8eIY0YMag6S9hZFbkDfmY7L2Ikc,5268
|
|
109
110
|
letta/personas/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
110
111
|
letta/personas/examples/anna_pa.txt,sha256=zgiNdSNhy1HQy58cF_6RFPzcg2i37F9v38YuL1CW40A,1849
|
|
@@ -139,7 +140,7 @@ letta/schemas/embedding_config.py,sha256=1kD6NpiXeH4roVumxqDAKk7xt8SpXGWNhZs_XXU
|
|
|
139
140
|
letta/schemas/enums.py,sha256=F396hXs57up4Jqj1vwWVknMpoVo7MkccVBALvKGHPpE,1032
|
|
140
141
|
letta/schemas/file.py,sha256=ChN2CWzLI2TT9WLItcfElEH0E8b7kzPylF2OQBr6Beg,1550
|
|
141
142
|
letta/schemas/health.py,sha256=zT6mYovvD17iJRuu2rcaQQzbEEYrkwvAE9TB7iU824c,139
|
|
142
|
-
letta/schemas/job.py,sha256=
|
|
143
|
+
letta/schemas/job.py,sha256=vRVVpMCHTxot9uaalLS8RARnqzJWvcLB1XP5XRBioPc,1398
|
|
143
144
|
letta/schemas/letta_base.py,sha256=QlCY5BBSjEPNpUvSHDl_P0TXQIPlr7xfhK_2SqKqadQ,3567
|
|
144
145
|
letta/schemas/letta_message.py,sha256=RuVVtwFbi85yP3dXQxowofQ6cI2cO_CdGtgpHGQzgHc,6563
|
|
145
146
|
letta/schemas/letta_request.py,sha256=E6OwKiceNffpdGdQMI1qc0jEfpL_e7O9BTzklOkbt6Y,1019
|
|
@@ -181,16 +182,16 @@ letta/server/rest_api/routers/v1/__init__.py,sha256=RZc0fIHNN4BGretjU6_TGK7q49Ry
|
|
|
181
182
|
letta/server/rest_api/routers/v1/agents.py,sha256=Tj7QyHjem_tOgzDzTyEJREDH2rzDgpE6S5azBDrhY_o,24884
|
|
182
183
|
letta/server/rest_api/routers/v1/blocks.py,sha256=UCVfMbb8hzOXI6a8OYWKuXyOropIxw6PYKZkwwAh1v0,4880
|
|
183
184
|
letta/server/rest_api/routers/v1/health.py,sha256=pKCuVESlVOhGIb4VC4K-H82eZqfghmT6kvj2iOkkKuc,401
|
|
184
|
-
letta/server/rest_api/routers/v1/jobs.py,sha256=
|
|
185
|
+
letta/server/rest_api/routers/v1/jobs.py,sha256=gnu__rjcd9SpKdQkSD_sci-xJYH0fw828PuHMcYwsCw,2678
|
|
185
186
|
letta/server/rest_api/routers/v1/llms.py,sha256=TcyvSx6MEM3je5F4DysL7ligmssL_pFlJaaO4uL95VY,877
|
|
186
187
|
letta/server/rest_api/routers/v1/organizations.py,sha256=tyqVzXTpMtk3sKxI3Iz4aS6RhbGEbXDzFBB_CpW18v4,2080
|
|
187
188
|
letta/server/rest_api/routers/v1/sandbox_configs.py,sha256=4tkTH8z9vpuBiGzxrS_wxkFdznnWZx-U-9F08czHMP8,5004
|
|
188
|
-
letta/server/rest_api/routers/v1/sources.py,sha256=
|
|
189
|
+
letta/server/rest_api/routers/v1/sources.py,sha256=1zIkopcyHDMarOGJISy5yXpi9-yeBRBitJ6_yiIJGdY,9818
|
|
189
190
|
letta/server/rest_api/routers/v1/tools.py,sha256=TP16cpuTF2HYLFZVmabExw9gziB-PtkExtWVkjxrRes,9553
|
|
190
191
|
letta/server/rest_api/routers/v1/users.py,sha256=M1wEr2IyHzuRwINYxLXTkrbAH3osLe_cWjzrWrzR1aw,3729
|
|
191
192
|
letta/server/rest_api/static_files.py,sha256=NG8sN4Z5EJ8JVQdj19tkFa9iQ1kBPTab9f_CUxd_u4Q,3143
|
|
192
193
|
letta/server/rest_api/utils.py,sha256=6c5a_-ZFTlwZ1IuzpRQtqxSG1eD56nNhKhWlrdgBYWk,3103
|
|
193
|
-
letta/server/server.py,sha256=
|
|
194
|
+
letta/server/server.py,sha256=ag3y5efyi0zzNC_TTJwJ9-xCbzzIgI2Orz36UDb8zoY,80381
|
|
194
195
|
letta/server/startup.sh,sha256=wTOQOJJZw_Iec57WIu0UW0AVflk0ZMWYZWg8D3T_gSQ,698
|
|
195
196
|
letta/server/static_files/assets/index-3ab03d5b.css,sha256=OrA9W4iKJ5h2Wlr7GwdAT4wow0CM8hVit1yOxEL49Qw,54295
|
|
196
197
|
letta/server/static_files/assets/index-9fa459a2.js,sha256=wtfkyHnEIMACHKL3UgN_jZNOKWEcOFjmWoeRHLngPwk,1815584
|
|
@@ -207,11 +208,12 @@ letta/services/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
|
207
208
|
letta/services/agents_tags_manager.py,sha256=zNqeXDpaf4dQ77jrRHiQfITdk4FawBzcND-9tWrj8gw,3127
|
|
208
209
|
letta/services/block_manager.py,sha256=TrbStwHAREwnybA6jZSkNPe-EYUa5rdiuliPR2PTV-M,5426
|
|
209
210
|
letta/services/blocks_agents_manager.py,sha256=mfO3EMW9os_E1_r4SRlC2wmBFFLpt8p-yhdOH_Iotaw,5627
|
|
211
|
+
letta/services/job_manager.py,sha256=FrkSXloke48CZKuzlYdysxM5gKWoTu7FRigPrs_YW4A,3645
|
|
210
212
|
letta/services/organization_manager.py,sha256=OfE2_NMmhqXURX4sg7hCOiFQVQpV5ZiPu7J3sboCSYc,3555
|
|
211
213
|
letta/services/per_agent_lock_manager.py,sha256=porM0cKKANQ1FvcGXOO_qM7ARk5Fgi1HVEAhXsAg9-4,546
|
|
212
214
|
letta/services/sandbox_config_manager.py,sha256=9BCu59nHR4nIMFXgFyEMOY2UTmZvBMS3GlDBWWCHB4I,12648
|
|
213
215
|
letta/services/source_manager.py,sha256=StX5Wfd7XSCKJet8qExIu3GMoI-eMIbEarAeTv2gq0s,6555
|
|
214
|
-
letta/services/tool_execution_sandbox.py,sha256=
|
|
216
|
+
letta/services/tool_execution_sandbox.py,sha256=GTWdfAKIMIuODEFbmReyEYkOnE62uzDF-3FHWee1s3A,21295
|
|
215
217
|
letta/services/tool_manager.py,sha256=FVCB9R3NFahh-KE5jROzf6J9WEgqhqGoDk5RpWjlgjg,7835
|
|
216
218
|
letta/services/tool_sandbox_env/.gitkeep,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
217
219
|
letta/services/user_manager.py,sha256=UJa0hqCjz0yXtvrCR8OVBqlSR5lC_Ejn-uG__58zLds,4398
|
|
@@ -219,9 +221,9 @@ letta/settings.py,sha256=ZcUcwvl7hStawZ0JOA0133jNk3j5qBd7qlFAAaIPsU8,3608
|
|
|
219
221
|
letta/streaming_interface.py,sha256=_FPUWy58j50evHcpXyd7zB1wWqeCc71NCFeWh_TBvnw,15736
|
|
220
222
|
letta/streaming_utils.py,sha256=329fsvj1ZN0r0LpQtmMPZ2vSxkDBIUUwvGHZFkjm2I8,11745
|
|
221
223
|
letta/system.py,sha256=buKYPqG5n2x41hVmWpu6JUpyd7vTWED9Km2_M7dLrvk,6960
|
|
222
|
-
letta/utils.py,sha256=
|
|
223
|
-
letta_nightly-0.6.0.
|
|
224
|
-
letta_nightly-0.6.0.
|
|
225
|
-
letta_nightly-0.6.0.
|
|
226
|
-
letta_nightly-0.6.0.
|
|
227
|
-
letta_nightly-0.6.0.
|
|
224
|
+
letta/utils.py,sha256=7FGZYp8JCEtP0PBSfV03Zj4kW3pwV7qpNki4AOU3a94,32913
|
|
225
|
+
letta_nightly-0.6.0.dev20241205104308.dist-info/LICENSE,sha256=mExtuZ_GYJgDEI38GWdiEYZizZS4KkVt2SF1g_GPNhI,10759
|
|
226
|
+
letta_nightly-0.6.0.dev20241205104308.dist-info/METADATA,sha256=gz6eo82cNek6syp2o1gS4FSmOQTzVty0a41NPRc13Hs,11413
|
|
227
|
+
letta_nightly-0.6.0.dev20241205104308.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
|
|
228
|
+
letta_nightly-0.6.0.dev20241205104308.dist-info/entry_points.txt,sha256=2zdiyGNEZGV5oYBuS-y2nAAgjDgcC9yM_mHJBFSRt5U,40
|
|
229
|
+
letta_nightly-0.6.0.dev20241205104308.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|