Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 5708f83

Browse files
author
zhanghui
committed
Merge branch 'dev_global_user_profile' into dev
2 parents b97fa69 + 2d9d01d commit 5708f83

9 files changed

Lines changed: 1051 additions & 2 deletions

File tree

src/agentic_layer/fetch_mem_service.py

Lines changed: 70 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
from __future__ import annotations
99

10+
import asyncio
1011
import logging
1112
from abc import ABC, abstractmethod
1213
from typing import Optional, Tuple, Union
@@ -46,11 +47,16 @@
4647
from infra_layer.adapters.out.persistence.repository.user_profile_raw_repository import (
4748
UserProfileRawRepository,
4849
)
50+
from infra_layer.adapters.out.persistence.repository.global_user_profile_raw_repository import (
51+
GlobalUserProfileRawRepository,
52+
)
4953
from api_specs.dtos import FetchMemResponse
5054
from api_specs.memory_models import (
5155
MemoryType,
5256
BaseMemoryModel,
5357
ProfileModel,
58+
GlobalUserProfileModel,
59+
CombinedProfileModel,
5460
PreferenceModel,
5561
EpisodicMemoryModel,
5662
BehaviorHistoryModel,
@@ -111,6 +117,7 @@ def __init__(self):
111117
self._event_log_repo = None
112118
self._foresight_record_repo = None
113119
self._user_profile_repo = None
120+
self._global_user_profile_repo = None
114121
logger.info("FetchMemoryServiceImpl initialized")
115122

116123
def _get_repositories(self):
@@ -131,6 +138,10 @@ def _get_repositories(self):
131138
self._foresight_record_repo = get_bean_by_type(ForesightRecordRawRepository)
132139
if self._user_profile_repo is None:
133140
self._user_profile_repo = get_bean_by_type(UserProfileRawRepository)
141+
if self._global_user_profile_repo is None:
142+
self._global_user_profile_repo = get_bean_by_type(
143+
GlobalUserProfileRawRepository
144+
)
134145

135146
async def _get_user_details_cache(self, group_id: str) -> dict:
136147
"""
@@ -228,6 +239,28 @@ def _convert_user_profile(self, user_profile) -> ProfileModel:
228239
updated_at=user_profile.updated_at,
229240
)
230241

242+
def _convert_global_user_profile(
243+
self, global_user_profile
244+
) -> GlobalUserProfileModel:
245+
"""Convert global user profile document to GlobalUserProfileModel
246+
247+
Args:
248+
global_user_profile: Global user profile document
249+
250+
Returns:
251+
GlobalUserProfileModel instance
252+
"""
253+
return GlobalUserProfileModel(
254+
id=str(global_user_profile.id),
255+
user_id=global_user_profile.user_id,
256+
profile_data=global_user_profile.profile_data,
257+
custom_profile_data=global_user_profile.custom_profile_data,
258+
confidence=global_user_profile.confidence,
259+
memcell_count=global_user_profile.memcell_count,
260+
created_at=global_user_profile.created_at,
261+
updated_at=global_user_profile.updated_at,
262+
)
263+
231264
def _convert_preferences_from_core_memory(
232265
self, core_memory
233266
) -> list[PreferenceModel]:
@@ -602,14 +635,49 @@ async def find_memories(
602635
case MemoryType.PROFILE:
603636
# Profile: supports user_id and group_id filtering, no time filtering
604637
# Uses created_at/updated_at fields (not time range filterable)
605-
user_profiles = await self._user_profile_repo.find_by_filters(
638+
# Also fetches global_user_profile and returns CombinedProfileModel
639+
640+
# Fetch user_profiles and global_user_profile concurrently
641+
user_profiles_task = self._user_profile_repo.find_by_filters(
606642
user_id=user_id, group_id=group_id, limit=limit
607643
)
608644

609-
memories = [
645+
global_profile_task = None
646+
if user_id and user_id != MAGIC_ALL:
647+
global_profile_task = (
648+
self._global_user_profile_repo.get_by_user_id(
649+
user_id=user_id
650+
)
651+
)
652+
653+
# Execute concurrently
654+
if global_profile_task:
655+
user_profiles, global_user_profile = await asyncio.gather(
656+
user_profiles_task, global_profile_task
657+
)
658+
else:
659+
user_profiles = await user_profiles_task
660+
global_user_profile = None
661+
662+
profile_models = [
610663
self._convert_user_profile(up) for up in user_profiles[:limit]
611664
]
612665

666+
global_profile_model = None
667+
if global_user_profile:
668+
global_profile_model = self._convert_global_user_profile(
669+
global_user_profile
670+
)
671+
672+
# Return CombinedProfileModel containing both profiles
673+
combined_profile = CombinedProfileModel(
674+
user_id=user_id,
675+
group_id=group_id,
676+
profiles=profile_models,
677+
global_profile=global_profile_model,
678+
)
679+
memories = [combined_profile]
680+
613681
case MemoryType.BASE_MEMORY:
614682
# Base memory: extract basic information from core memory
615683
# Does NOT support group_id or time filtering (single record per user)

src/api_specs/memory_models.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,40 @@ class ProfileModel:
170170
updated_at: Optional[datetime] = None
171171

172172

173+
@dataclass
174+
class GlobalUserProfileModel:
175+
"""Global user profile model
176+
177+
Stores global user profile information (not bound to a specific group).
178+
Compatible with GlobalUserProfile document structure.
179+
"""
180+
181+
id: str
182+
user_id: str
183+
profile_data: Optional[Dict[str, Any]] = None
184+
custom_profile_data: Optional[Dict[str, Any]] = None
185+
confidence: float = 0.0
186+
memcell_count: int = 0
187+
created_at: Optional[datetime] = None
188+
updated_at: Optional[datetime] = None
189+
190+
191+
@dataclass
192+
class CombinedProfileModel:
193+
"""Combined profile model
194+
195+
Contains both group-level profile and global user profile.
196+
Used when fetching PROFILE memory type.
197+
"""
198+
199+
user_id: str
200+
group_id: Optional[str] = None
201+
# Group-level profiles (may have multiple for different groups)
202+
profiles: List[ProfileModel] = field(default_factory=list)
203+
# Global user profile (one per user per scenario)
204+
global_profile: Optional[GlobalUserProfileModel] = None
205+
206+
173207
@dataclass
174208
class PreferenceModel:
175209
"""User preference model"""
@@ -372,6 +406,8 @@ class ForesightModel:
372406
# BaseMemoryModel,
373407
# PreferenceModel,
374408
ProfileModel,
409+
GlobalUserProfileModel,
410+
CombinedProfileModel,
375411
EpisodicMemoryModel,
376412
# EntityModel,
377413
# RelationModel,
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
# -*- coding: utf-8 -*-
2+
"""
3+
Global User Profile DTO
4+
5+
Data transfer objects for global user profile API.
6+
"""
7+
8+
from typing import Any, Dict, List, Optional
9+
10+
from pydantic import BaseModel, Field
11+
12+
13+
class CustomProfileData(BaseModel):
14+
"""
15+
Custom profile data structure
16+
17+
Currently only supports initial_profile field.
18+
"""
19+
20+
initial_profile: List[str] = Field(
21+
...,
22+
description="List of profile sentences describing the user",
23+
examples=[
24+
[
25+
"User is a software engineer",
26+
"User is proficient in Python programming",
27+
"User is interested in AI technology",
28+
]
29+
],
30+
)
31+
32+
33+
class UpsertCustomProfileRequest(BaseModel):
34+
"""
35+
Upsert custom profile request
36+
37+
Request body for upserting custom profile data.
38+
Will merge with existing data, overlapping fields will be overwritten by input.
39+
"""
40+
41+
user_id: str = Field(..., description="User ID")
42+
custom_profile_data: CustomProfileData = Field(
43+
..., description="Custom profile data to upsert"
44+
)
45+
46+
47+
class UpsertCustomProfileResponse(BaseModel):
48+
"""
49+
Upsert custom profile response
50+
51+
Response for upsert custom profile API.
52+
"""
53+
54+
success: bool = Field(..., description="Whether the operation was successful")
55+
data: Optional[Dict[str, Any]] = Field(
56+
default=None, description="Created/updated profile data"
57+
)
58+
message: Optional[str] = Field(default=None, description="Message")
59+
60+
61+
class GetGlobalUserProfileResponse(BaseModel):
62+
"""
63+
Get global user profile response
64+
65+
Response for get global user profile API.
66+
"""
67+
68+
success: bool = Field(..., description="Whether the query was successful")
69+
found: bool = Field(default=False, description="Whether the profile was found")
70+
data: Optional[Dict[str, Any]] = Field(
71+
default=None, description="Global user profile data"
72+
)
73+
message: Optional[str] = Field(default=None, description="Message")
74+
75+
76+
class DeleteGlobalUserProfileResponse(BaseModel):
77+
"""
78+
Delete global user profile response
79+
80+
Response for delete global user profile API.
81+
"""
82+
83+
success: bool = Field(..., description="Whether the operation was successful")
84+
deleted_count: int = Field(default=0, description="Number of deleted records")
85+
message: Optional[str] = Field(default=None, description="Message")
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# -*- coding: utf-8 -*-
2+
"""Global user profile API module"""

0 commit comments

Comments
 (0)